Hey programmers and developers,
I’ve been working on a part placing system for my building game. The core features are mostly working, but I keep running into imperfections with grid snapping and surface alignment.
When I try to snap parts to a grid or align them to surfaces:
- Sometimes there’s a small gap or overlap.
- It only works properly with very specific stud sizes (like multiples of the part size).
- When rotating parts before placement, the alignment can shift, leaving hollow gaps of about 1 stud (or some oddly specific fraction).
This makes the placement feel inconsistent and “off” compared to how Roblox Studio itself handles alignment.
What I’m Looking For
- Is there a better math/logic approach to make placement truly “flush” with surfaces?
- How do I properly calculate the pivot so that rotations + grid snapping don’t create these awkward gaps?
- Is there a reliable way to match surface normals and part extents so everything fits together seamlessly?
My Current Script
Here’s the part of my current implementation:
local function spawnPreview(materialName)
Gui.EditFrame.AddFrame.MaterialsFrame.Visible = false
clearPreview()
Gui.EditFrame.ez.Visible = true
local newPart = ReplicatedStorage:WaitForChild("Materials"):FindFirstChild(materialName):Clone()
newPart.Anchored = true
newPart.CanCollide = false
newPart.Transparency = 0.5
newPart.Parent = workspace.Camera
previewPart = newPart
if not isMobile then
placingConnection = RunService.RenderStepped:Connect(updatePreviewPC)
inputConnection = UserInputService.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed or not previewPart then return end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
placeBlock()
elseif input.KeyCode == Enum.KeyCode.T then
rotX += 90
updatePreviewPC()
elseif input.KeyCode == Enum.KeyCode.R then
rotY += 90
updatePreviewPC()
elseif input.KeyCode == Enum.KeyCode.Y then
rotZ += 90
updatePreviewPC()
end
end)
else
local mobileUI = Gui.EditFrame:WaitForChild("IAmMobileWoohoo")
mobileUI.Visible = true
local MoveMe = mobileUI:WaitForChild("MoveMe")
MoveMe.Visible = true
mobileUI.Tip.Visible = true
local X = mobileUI:WaitForChild("X")
local Y = mobileUI:WaitForChild("Y")
local Z = mobileUI:WaitForChild("Z")
X.Visible = true
Y.Visible = true
Z.Visible = true
X.Activated:Connect(function() rotX += 90 updatePreviewMobile() end)
Y.Activated:Connect(function() rotY += 90 updatePreviewMobile() end)
Z.Activated:Connect(function() rotZ += 90 updatePreviewMobile() end)
placingConnection = RunService.RenderStepped:Connect(updatePreviewMobile)
local AcceptButton = Gui.EditFrame.AddFrame:WaitForChild("Accept")
AcceptButton.Visible = true
if AcceptButtonConnection then AcceptButtonConnection:Disconnect() end
AcceptButtonConnection = AcceptButton.Activated:Connect(placeBlock)
end
end
local function Build()
local materials = Gui:WaitForChild("EditFrame")
:WaitForChild("AddFrame")
:WaitForChild("MaterialsFrame")
:WaitForChild("Materials")
local function startRotation(viewportFrame)
local currentRotation = CFrame.new()
task.spawn(function()
while viewportFrame do
currentRotation = currentRotation * CFrame.Angles(0, math.rad(2.5), 0)
local part
for _, child in ipairs(viewportFrame:GetChildren()) do
if child:IsA("BasePart") then
part = child
end
end
part.CFrame = CFrame.new(part.Position) * currentRotation
task.wait(0.02)
end
end)
end
for _, Material in ipairs(materials:GetChildren()) do
if Material:IsA("ViewportFrame") then
Material.TheButton.Activated:Connect(function()
spawnPreview(Material.Name)
end)
startRotation(Material)
end
end
end
My Question to the Community
How do you make part placement systems perfectly snap to grid and surfaces
(like Roblox Studio does), even with rotation?
Any advice, examples, or resources would be greatly appreciated
