Placement System With Grid

Hello devs,

I am looking for some reedback and help fo the following. This script works perfectly fine, but I am trying to implement a grid into the placement system. I would like it to be so when a player moves their cursor, blocks snap to a 1 stud by 1 stud grid in order to make it easy align items. At the moment, there is no grid what so ever so it is difficult to place walls beside each other.

Script: (localscript)

wait(2)

local event = game.ReplicatedStorage.PlacementEvent
local Objects = game.ReplicatedStorage:WaitForChild("ObjectFolder")

local plr = game.Players.LocalPlayer
local char = plr.Character or plr.CharacterAdded:Wait()

local mouse = plr:GetMouse()

local frame = script.Parent:WaitForChild("Frame")
local textBox = script.Parent:WaitForChild("Frame").SizeBox
local ColorBox = script.Parent:WaitForChild("Frame").ColorBox
local MaterialBox = script.Parent:WaitForChild("Frame").MaterialBox

local uis = game:GetService("UserInputService")
local rs = game:GetService("RunService")

local PlacingObject = false
local RotatingObject = false
local FlippingObject = false

for i,v in pairs(frame:GetChildren()) do
	if v:IsA("GuiButton") then
		v.MouseButton1Click:Connect(function()
			if PlacingObject == false then
				PlacingObject = true
				
				frame.Visible = false
				
				local RotationAmount = 0
				local FlippingAmount = 0
				
				local PreviewObject = Objects:FindFirstChild(v.Name):Clone()
				PreviewObject.Parent = game.Workspace
				
				for a,b in pairs(PreviewObject:GetDescendants()) do
					if b:IsA("BasePart") then
						if b.Name == "MainPart" then
							b.CanCollide = false
						else
							if b.Parent.Name == "Ball" then
								b.Transparency = 1
								b.CanCollide = false
							else
								b.Transparency = 0.5
								b.CanCollide = false
							end
						end
					end
				end
				
				uis.InputBegan:Connect(function(input)
					if input.KeyCode == Enum.KeyCode.R then
						RotatingObject = true
						
						while RotatingObject == true do
							wait()
							RotationAmount += 2
							
						end
					end
					
					if input.KeyCode == Enum.KeyCode.T then
						FlippingObject = true

						while RotatingObject == true do
							wait()
							FlippingAmount += 2
						end
					end
				end)
				
				uis.InputEnded:Connect(function(input)
					if input.KeyCode == Enum.KeyCode.R then
						RotatingObject = false
					end
					
					if input.KeyCode == Enum.KeyCode.T then
						FlippingObject = false
					end
				end)
				
				rs.RenderStepped:Connect(function()
					if true == true then --this is where grid stuff will go
						if PlacingObject == true then
							mouse.TargetFilter = PreviewObject

							if PreviewObject:FindFirstChild("MainPart") then
								local objCFrame = CFrame.new(mouse.Hit.Position.X,mouse.Hit.Position.Y + PreviewObject.PrimaryPart.Size.Y / 2,mouse.Hit.Position.Z)
								local objAngles = CFrame.Angles(0,math.rad(RotationAmount),math.rad(FlippingAmount))

								PreviewObject:SetPrimaryPartCFrame(objCFrame * objAngles)

								local SizeValues = textBox.Text:split(", ")

								if #SizeValues == 3 then
									PreviewObject.PrimaryPart.Size = Vector3.new(SizeValues[1],SizeValues[2],SizeValues[3])
								end

								local ColorValues = ColorBox.Text:split(", ")

								if #ColorValues == 3 then
									PreviewObject.PrimaryPart.Color = Color3.fromRGB(ColorValues[1],ColorValues[3],ColorValues[3])
								end

								mouse.Button1Up:Connect(function()
									if PlacingObject == true then
										PlacingObject = false
										
										if #SizeValues == 3 then
											if #ColorValues == 3 then
												event:FireServer(PreviewObject.Name,PreviewObject.PrimaryPart.CFrame,SizeValues[1],SizeValues[2],SizeValues[3],ColorValues[1],ColorValues[2],ColorValues[3],MaterialBox.Text)
											else
												event:FireServer(PreviewObject.Name,PreviewObject.PrimaryPart.CFrame,SizeValues[1],SizeValues[2],SizeValues[3])
											end
										else
											event:FireServer(PreviewObject.Name,PreviewObject.PrimaryPart.CFrame,MaterialBox.Text)
										end

										frame.Visible = true
										PreviewObject:Destroy()
									end
								end)
							end
						end
					end
				end)
			end
		end)
	end
end

There is also a serverscript, but let me know if that would help, although I think the grid logic would be placed in here.

I would appreciate any help!
Thank you,

1 Like

You can use math.round() to round decimal values to the nearest integer. This should round the thing you’re placing to the nearest stud.

Replace this line with:

local objCFrame = CFrame.new(math.round(mouse.Hit.Position.X),mouse.Hit.Position.Y + PreviewObject.PrimaryPart.Size.Y / 2,math.round(mouse.Hit.Position.Z))
1 Like

Hello, so it works fine, although how could I go about making it like 2 studs by 2 studs?

1 Like
local Part = Instance.new("Part")
Part.Anchored = true
Part.Parent = workspace

local Players = game:GetService("Players")
local Player = Players.LocalPlayer
local Mouse = Player:GetMouse()
Mouse.TargetFilter = Part

local GridOffset = 10

local function GetCoordinate(Position)
	return math.round(Position) - (math.round(Position) % GridOffset)
end

Mouse.Move:Connect(function()
	local X, Y, Z = GetCoordinate(Mouse.Hit.Position.X), Mouse.Hit.Position.Y + Part.Size.Y/2, GetCoordinate(Mouse.Hit.Position.Z)
	Part.Position = Vector3.new(X, Y, Z)
end)

Here’s a script which moves a BasePart instance along a grid locked to every n studs, “n” in this case is any number value stored inside the variable named “GridOffset”.

2 Likes

What could I set the ObjCFrame variable to in order to call that function?

The grid snapping systems that @Apicphis and @Forummer suggested are pretty good, but there are two things I would warn you about when using these systems:

1.) These systems work best for objects that are uniform sizes. (Like in a block game, having all blocks 4x4x4, for instance). Objects that overflow into multiple grid units can cause weird intersections with nearby objects. You can make a simple intersection test with :GetTouchingParts() to help alleviate this problem.

2.) Be warned about weird orientations on objects (like let’s say vector3.new(42.114,38.189,86.130) because they could potentially mess with the grid system with weird intersections and such too. :GetTouchingParts() may help with this too.

Just some thoughts

1 Like

That makes sense.

I just want a simple grid system that i can set an offset for, for example 4x4 grid spaces in order to make it super easy to align blocks, and other objects which would be in the size of the gridoffset. I am not sure how to implement the GridOffset GetCooridinates function that was posted above, so any help from anyone would be greatly appreciated.

Thanks!

I’ve tweaked your script a bit to incorporate a custom grid size. I’ve set it to 4 studs, but you can change that in the variable at the top.

wait(2)

local gridSize = 4

local event = game.ReplicatedStorage.PlacementEvent
local Objects = game.ReplicatedStorage:WaitForChild("ObjectFolder")

local plr = game.Players.LocalPlayer
local char = plr.Character or plr.CharacterAdded:Wait()

local mouse = plr:GetMouse()

local frame = script.Parent:WaitForChild("Frame")
local textBox = script.Parent:WaitForChild("Frame").SizeBox
local ColorBox = script.Parent:WaitForChild("Frame").ColorBox
local MaterialBox = script.Parent:WaitForChild("Frame").MaterialBox

local uis = game:GetService("UserInputService")
local rs = game:GetService("RunService")

local PlacingObject = false
local RotatingObject = false
local FlippingObject = false

for i,v in pairs(frame:GetChildren()) do
	if v:IsA("GuiButton") then
		v.MouseButton1Click:Connect(function()
			if PlacingObject == false then
				PlacingObject = true
				
				frame.Visible = false
				
				local RotationAmount = 0
				local FlippingAmount = 0
				
				local PreviewObject = Objects:FindFirstChild(v.Name):Clone()
				PreviewObject.Parent = game.Workspace
				
				for a,b in pairs(PreviewObject:GetDescendants()) do
					if b:IsA("BasePart") then
						if b.Name == "MainPart" then
							b.CanCollide = false
						else
							if b.Parent.Name == "Ball" then
								b.Transparency = 1
								b.CanCollide = false
							else
								b.Transparency = 0.5
								b.CanCollide = false
							end
						end
					end
				end
				
				uis.InputBegan:Connect(function(input)
					if input.KeyCode == Enum.KeyCode.R then
						RotatingObject = true
						
						while RotatingObject == true do
							wait()
							RotationAmount += 2
							
						end
					end
					
					if input.KeyCode == Enum.KeyCode.T then
						FlippingObject = true

						while RotatingObject == true do
							wait()
							FlippingAmount += 2
						end
					end
				end)
				
				uis.InputEnded:Connect(function(input)
					if input.KeyCode == Enum.KeyCode.R then
						RotatingObject = false
					end
					
					if input.KeyCode == Enum.KeyCode.T then
						FlippingObject = false
					end
				end)
				
				rs.RenderStepped:Connect(function()
					if true == true then --this is where grid stuff will go
						if PlacingObject == true then
							mouse.TargetFilter = PreviewObject

							if PreviewObject:FindFirstChild("MainPart") then
								local objCFrame = CFrame.new(math.floor(mouse.Hit.Position.X/gridSize+0.5)*gridSize,mouse.Hit.Position.Y + PreviewObject.PrimaryPart.Size.Y / 2,math.floor(mouse.Hit.Position.Z/gridSize+0.5)*gridSize)
								local objAngles = CFrame.Angles(0,math.rad(RotationAmount),math.rad(FlippingAmount))

								PreviewObject:SetPrimaryPartCFrame(objCFrame * objAngles)

								local SizeValues = textBox.Text:split(", ")

								if #SizeValues == 3 then
									PreviewObject.PrimaryPart.Size = Vector3.new(SizeValues[1],SizeValues[2],SizeValues[3])
								end

								local ColorValues = ColorBox.Text:split(", ")

								if #ColorValues == 3 then
									PreviewObject.PrimaryPart.Color = Color3.fromRGB(ColorValues[1],ColorValues[3],ColorValues[3])
								end

								mouse.Button1Up:Connect(function()
									if PlacingObject == true then
										PlacingObject = false
										
										if #SizeValues == 3 then
											if #ColorValues == 3 then
												event:FireServer(PreviewObject.Name,PreviewObject.PrimaryPart.CFrame,SizeValues[1],SizeValues[2],SizeValues[3],ColorValues[1],ColorValues[2],ColorValues[3],MaterialBox.Text)
											else
												event:FireServer(PreviewObject.Name,PreviewObject.PrimaryPart.CFrame,SizeValues[1],SizeValues[2],SizeValues[3])
											end
										else
											event:FireServer(PreviewObject.Name,PreviewObject.PrimaryPart.CFrame,MaterialBox.Text)
										end

										frame.Visible = true
										PreviewObject:Destroy()
									end
								end)
							end
						end
					end
				end)
			end
		end)
	end
end