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)
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)