How would i be able to make a part snap to a grid

hello ive been trying to make a quite unique placement system as a side project, i want to make my blocks snap to a grid but i dont know how to make it so, ive tried searching on the dev forum and couldnt find anything useful related to grid systems and stuff how would i be able to make one?

current code i have

-- Please note that its a very early version and a lot is subjected to change, so its quite simple for now
-- Services
local runService = game:GetService("RunService")
local tweenService = game:GetService("TweenService")
local repStore = game:GetService("ReplicatedStorage")
local UIS = game:GetService("UserInputService")
local players = game:GetService("Players")

-- Player
local plr = players.LocalPlayer
local character = plr.Character or plr.CharacterAdded:Wait()

-- Bools
local isPlacing = false
local isCollided = false
local itemIsLoaded = false
local canPlace = true

-- Numbers
local PlacementTransparency = 0.5

-- nil Variables
local object = nil
local selectedObj = nil

-- Variables 
local camera = game.Workspace.CurrentCamera
local mouse = plr:GetMouse()
local eventsFolder = repStore.Events
local PlacementHud = plr.PlayerGui:WaitForChild("PlacementHud")

-- Functions
function LoadItem(Item) -- Loads the item into workspace and sets its transaprency to true
	if Item == nil then
		
		error("item is nil") -- will error if the item is nil
		
	end
	
	local LoadedItem = game.ReplicatedStorage.Objects[Item]:Clone()
	object = LoadedItem
	selectedObj = game.ReplicatedStorage.Objects[Item]
	itemIsLoaded = true
	LoadedItem.Parent = game.Workspace.Objects
	camera.CameraSubject = LoadedItem.Hitbox
	for i, ItemParts in pairs(LoadedItem:GetDescendants()) do -- Sets transparency of all the parts in the object model to the desired value
		if ItemParts:IsA("BasePart") then
		ItemParts.Transparency = PlacementTransparency
		ItemParts.CanCollide = false
		end
	end
	
end

function CleanUp() -- Cleans up the objects folder after ending placement
	itemIsLoaded = false
	object = nil
	for i, v in pairs(game.Workspace.Objects:GetChildren()) do
		
		v:Destroy()
		
	end
	
end

function startPlacement(Item) -- Will start the placement and make the camera orbit the object
	
	PlacementHud.Enabled = true
	CleanUp()
	LoadItem(Item)

	
end

function stopPlacement() -- Will stop placement and set the camera to its orginal position
	
	PlacementHud.Enabled = false
	camera.CameraSubject = character
	CleanUp()

	
end

function move(direction, ammount) -- Moves the object to the desired position on the world
	if itemIsLoaded == true then
	if direction == "X" then
		
		object:PivotTo(object.PrimaryPart.CFrame * CFrame.new(ammount, 0, 0)) -- Moves the object by the ammount in the direction
			
		elseif	direction == "Y" then
			
			object:PivotTo(object.PrimaryPart.CFrame * CFrame.new(0, ammount, 0)) -- Moves the object by the ammount in the direction

			elseif	direction == "Z" then

			object:PivotTo(object.PrimaryPart.CFrame * CFrame.new(0, 0, ammount)) -- Moves the object by the ammount in the direction
			
	end
	end
end

function place(item, position)	-- Places the object in the desired position,) -- Places the object in the desired position
	if canPlace == true then
	eventsFolder.Place:FireServer(item, position)
	else
		print("cant place")
	end
end

-- Function events
UIS.InputBegan:Connect(function(key) -- Detects when a key is pressed
	
	if key.KeyCode == Enum.KeyCode.B then -- Checks if the key pressed was the B key
		if isPlacing == false then -- If the placement is not active it will start the placement
			isPlacing = true
			startPlacement("3X3Block")
			
		elseif isPlacing == true then -- or if the placement is active it will stop the placement
			isPlacing = false
			stopPlacement()
		end
		
		
	end
	
	------------------------------------------------------------
	-- Move Keycodes
	if key.KeyCode == Enum.KeyCode.Q then
		
		move("Y", -3)
		
	end
	
	if key.KeyCode == Enum.KeyCode.E then

		move("Y", 3)

	end
	
	if key.KeyCode == Enum.KeyCode.A then

		move("X", -3)

	end
	
	if key.KeyCode == Enum.KeyCode.D then

		move("X", 3)

	end
	
	if key.KeyCode == Enum.KeyCode.W then

		move("Z", -3)

	end
	
	if key.KeyCode == Enum.KeyCode.S then

		move("Z", 3)

	end
	
	------------------------------------------------------------
	
	if key.UserInputType == Enum.UserInputType.MouseButton1  then
		
		place(selectedObj, object.PrimaryPart.CFrame)
		
	end
	
end)

PlacementHud.Block.MouseButton1Click:Connect(function() -- placeholder
	
	startPlacement("3X3Block")
	
end)

PlacementHud.Beam.MouseButton1Click:Connect(function() -- placeholder

	startPlacement("3MBEAM")

end)

PlacementHud.Frame.MouseEnter:Connect(function()
	
	canPlace = false
	
end)

PlacementHud.Frame.MouseLeave:Connect(function()

	canPlace = true

end)

atleast telling me how to start or how to make it would help, ty!

Hey, you can check out @BRicey763’s tutorial on snap to grid placement system
I see you have a move function that pivot’s the object, you could have a simple function like in the video that rounds up the position and scales it by the grid size and then apply that to the CFrame before pivoting

1 Like

– Basic Snap System I made

function Snap(posy)
	PosX = math.floor(Mouse.Hit.p.X / StudSize)*StudSize+0.5*StudSize -- Grid Stuff
	PosY = Plate.Position.Y+(Plate.Size.Y/2)+(Part.PrimaryPart.Size.Y/2)
	PosZ = math.floor(Mouse.Hit.p.Z / StudSize)*StudSize+0.5*StudSize -- Grid Stuff
end```
1 Like

alr so ive tried replicating whats show in the video, ive used only the x axis for now just for testing, when i press the keys to move the block this happens

robloxapp-20241106-2148190.wmv (841.0 KB)

the block moves in a diagonal dierection instead of moving in a single direction like i want to

local function SnapToGrid(cf, gridSize) -- Will snap the object to the grid
	
	local vector = CFrame.new(
		
		cf / gridSize * GridSize,
		cf / gridSize * GridSize,
		cf / gridSize * GridSize
	)
	
	return vector
	
end

function move(direction, ammount) -- Moves the object to the desired position on the world
	if itemIsLoaded == true then
	if direction == "X" then
			local snap = SnapToGrid(object.PrimaryPart.CFrame.X, GridSize)
		object:PivotTo(snap * CFrame.new(ammount, 0, 0)) -- Moves the object by the ammount in the direction
			
				
			
		elseif	direction == "Y" then
			local snap = SnapToGrid(object.PrimaryPart.CFrame.Y, GridSize)
			object:PivotTo(object.PrimaryPart.CFrame * CFrame.new(0, ammount, 0)) -- Moves the object by the ammount in the direction
			
				
			
			elseif	direction == "Z" then
			local snap = SnapToGrid(object.PrimaryPart.CFrame.Z, GridSize)
			object:PivotTo(object.PrimaryPart.CFrame * CFrame.new(0, 0, ammount)) -- Moves the object by the ammount in the direction
			
				
			
	end
	end
end
1 Like

this code would cause problems as you would only be passing in one coordinate and using that for all the coordinates:

		cf / gridSize * GridSize,
		cf / gridSize * GridSize,
		cf / gridSize * GridSize

and you don’t seem to be using the snap inside of the pivot:

local snap = SnapToGrid(object.PrimaryPart.CFrame.Z, GridSize)
			object:PivotTo(object.PrimaryPart.CFrame * CFrame.new(0, 0, ammount))

you can try this:

local function SnapToGrid(cf: CFrame, gridSize) -- Will snap the object to the grid
	local vector = CFrame.new(
		cf.X / gridSize * GridSize,
		cf.Y / gridSize * GridSize,
		cf.Z / gridSize * GridSize
	)
	return vector

end

function move(direction, ammount) -- Moves the object to the desired position on the world
	if itemIsLoaded == false then
		return
	end
	
	local movedCFrame = CFrame.new()
	movedCFrame[direction] = ammount
	
	local snap = SnapToGrid(object.PrimaryPart.CFrame * movedCFrame, GridSize)
	object:PivotTo(snap) -- Moves the object by the ammount in the direction

end
1 Like

well ive tried that solution and it just returns me a error

X cannot be assigned to - Client - PlacementSystem:99

ive tried assinging values to the empty cframe of the movedCFrame, nothing seems to work out

local movedCFrame = CFrame.new(0, 0, 0)

do i need to change this params to something or fix the code?

	if key.KeyCode == Enum.KeyCode.D then

		move("X", 3)

	end

mb, Vector3 and CFrame’s are immutable in roblox so this code would be invalid:

	movedCFrame[direction] = ammount

here’s the new code which constructs a new Vector3 for each direction:

function move(direction, amount) -- Moves the object to the desired position on the world
	if not itemIsLoaded then
		return
	end
	
	local moveDirection = Vector3.new()
	if direction == "X" then
		moveDirection = Vector3.new(amount, 0, 0)
	elseif direction == "Y" then
		moveDirection = Vector3.new(0, amount, 0)
	elseif direction == "Z" then
		moveDirection = Vector3.new(0, 0, amount)
	end
	
	local movedCFrame = object.PrimaryPart.CFrame * CFrame.new(moveDirection)
	
	local snap = SnapToGrid(movedCFrame, GridSize)
	object:PivotTo(snap) 
end
1 Like

lol thats the exact thing i tought to do while i was out, imma try this new code and update you on it

Here’s the code for grid rounding. Note, this also accounts for differently sized things, so that odd-sized objects can also align.


-- Function to round the position and size to the nearest grid point
local function gridRounding(pos: Vector3, size: Vector3, gridSize: number)
	-- Calculate the rounded size based on the grid size
	local roundedSize = Vector3.new(
		math.round(size.X / gridSize) * gridSize,
		math.round(size.Y / gridSize) * gridSize,
		math.round(size.Z / gridSize) * gridSize
	)

	-- Round the position to the nearest grid point and apply the offset
	local roundedPosition = Vector3.new(
		math.round(pos.X / (gridSize / 2)) * (gridSize / 2),
		math.round(pos.Y / (gridSize / 2)) * (gridSize / 2),
		math.round(pos.Z / (gridSize / 2)) * (gridSize / 2)
	)

	return roundedPosition, roundedSize
end
1 Like

well it worked ty, now i need to figure out how to snap it to a craft but i think i can do it myself, if i need help will make another post

1 Like

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