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