Problem with snapping for placement system

my SnapToGrid function handles the snapping for my placement system but sometimes it’s slightly off. here’s an example:
thank you for any help! :slight_smile:

local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {character, GhostBlock}

local function SnapToGrid(position: Vector3, SnapIncrement: number)
	return Vector3.new(
		math.floor(position.X / SnapIncrement + 0.5) * SnapIncrement,
		math.floor(position.Y / SnapIncrement + 0.5) * SnapIncrement,
		math.floor(position.Z / SnapIncrement + 0.5) * SnapIncrement
	)
end

local function GetPointerHit()
	if UserInputService.MouseEnabled then
		local MouseLocation = UserInputService:GetMouseLocation()
		local mouseRay = Camera:ViewportPointToRay(MouseLocation.X, MouseLocation.Y)
		local ray = workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 100, raycastParams)

		if ray and ray.Position then
			return ray
		end
	end
end

ghostPlacementConnection = runService.RenderStepped:Connect(function(deltaTime)
	local Hit : RaycastResult = GetPointerHit()
	if Hit then
		local LerpAlpha = 0.2
		local snappedPosition = SnapToGrid(Hit.Position + Hit.Normal, 1)
		local GhostPartCFrame = CFrame.new(snappedPosition)
		GhostBlock.CFrame = GhostBlock.CFrame:Lerp(GhostPartCFrame, LerpAlpha)
	end
end)

UserInputService.InputBegan:Connect(function(input, gameprocessed)
	if gameprocessed then return end
	if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
		local PlaceEvent = AssetsModule.RemoteEvents:WaitForChild("Place")
		PlaceEvent:FireServer(GhostBlock.CFrame, GhostBlockFromStorage)
	end
end)

solution found!

i was passing in the interpolated (in between) CFrame into the :FireServer parameter instead of the final snapped CFrame. i just added a new ‘GhostPartCFrame’ variable to track my fully snapped CFrame.

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local Mouse = player:GetMouse()
local Camera = workspace.CurrentCamera

local runService = game:GetService("RunService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")

local Assets = replicatedStorage.Assets
local Scripts = replicatedStorage.Scripts
local AssetsModule = require(Scripts.AssetsModule)

local GhostBlockModule = require(script.GhostBlock)
local GhostBlock, GhostBlockFromStorage = GhostBlockModule.GhostBlock, GhostBlockModule.BlockTemplate
local ghostPlacementConnection = nil
local GhostPartCFrame : CFrame = nil

local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {character, GhostBlock}

local function SnapToGrid(position: Vector3, SnapIncrement: number)
	return Vector3.new(
		math.floor(position.X / SnapIncrement + 0.5) * SnapIncrement,
		math.floor(position.Y / SnapIncrement + 0.5) * SnapIncrement,
		math.floor(position.Z / SnapIncrement + 0.5) * SnapIncrement
	)
end

local function GetPointerHit()
	if UserInputService.MouseEnabled then
		local MouseLocation = UserInputService:GetMouseLocation()
		local mouseRay = Camera:ViewportPointToRay(MouseLocation.X, MouseLocation.Y)
		local ray = workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 100, raycastParams)

		if ray and ray.Position then
			return ray
		end
	end
end

ghostPlacementConnection = runService.RenderStepped:Connect(function(deltaTime)
	local Hit : RaycastResult = GetPointerHit()
	if Hit then
		local LerpAlpha = 0.2
		local snappedPosition = SnapToGrid(Hit.Position + Hit.Normal, 1)
		GhostPartCFrame = CFrame.new(snappedPosition)
		GhostBlock.CFrame = GhostBlock.CFrame:Lerp(GhostPartCFrame, LerpAlpha)
	end
end)

UserInputService.InputBegan:Connect(function(input, gameprocessed)
	if gameprocessed then return end
	if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
		local PlaceEvent = AssetsModule.RemoteEvents:WaitForChild("Place")
		PlaceEvent:FireServer(GhostPartCFrame, GhostBlockFromStorage)
	end
end)

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.