How to make a Grid-Placement System?

Hello there, im a new developer and not very good, im currently working on a game and i need some help as i cant find any way to make what i want to do.

So, i have a script that allows players to press a button which then shows a ghost object of the object they chose when they hover with their mouse over a surface, The Problem is that theres no grid-system
Which means that u can place the object freely anywhere on the surface and even inside eachother

also by grid system i mean something like this


But that it appears on the surface when you press one of the buttons and disappears when u finished the placement:

it would also be nice if it would be so that u cant place the objects inside eachother

so, i have 2 codes, one is the placement and the other one is the script for the buttons and ghost objects to show

heres the script for the buttons and all that:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ContextActionService = game:GetService("ContextActionService")

local player = Players.LocalPlayer
local camera = game.Workspace.Camera
local mouse = player:GetMouse()

local playergui = player:WaitForChild("PlayerGui")
local plopScreen = playergui:WaitForChild("ScreenGui")
local plopChairButton = plopScreen:WaitForChild("PlopChairButton")
local plopTableButton = plopScreen:WaitForChild("PlopTableButton")

local raycastParameters = RaycastParams.new()
raycastParameters.FilterType = Enum.RaycastFilterType.Whitelist
raycastParameters.FilterDescendantsInstances = { game.Workspace.Surfaces }

local ghostObjects = ReplicatedStorage:WaitForChild("GhostObjects")
local ghostChair = ghostObjects:WaitForChild("GhostChairTest")
local ghostTable = ghostObjects:WaitForChild("GhostTable")

local events = ReplicatedStorage:WaitForChild("Events")
local plopEvent = events:WaitForChild("PlopEvent")

local plopCFrame = nil
local activeGhostObject = nil
local rotationAngle = 0

local PLOP_CLICK = "PLOP_CLICK"
local PLOP_ROTATE = "PLOP_ROTATE"
local PLOP_MODE = "PLOP_MODE"
local RAYCAST_DISTANCE = 200
local ROTATION_STEP = 90

local function onRenderStepped()
	local mouseRay = camera:ScreenPointToRay(mouse.X, mouse.Y, 0)
	local raycastResults = game.Workspace:Raycast(mouseRay.Origin,
		mouseRay.Direction * RAYCAST_DISTANCE, raycastParameters)
	if raycastResults then
		local rotationAngleRads = math.rad(rotationAngle)
		local rotationCFrame = CFrame.Angles(0, rotationAngleRads, 0)
		plopCFrame = CFrame.new(raycastResults.Position) * rotationCFrame
		activeGhostObject:SetPrimaryPartCFrame(plopCFrame)
		activeGhostObject.Parent = game.Workspace
	else
		plopCFrame = nil
		activeGhostObject.Parent = ReplicatedStorage
	end
end

local function onMouseInput(actionName, inputState)
	if inputState == Enum.UserInputState.End then
		activeGhostObject.Parent = ReplicatedStorage
		RunService:UnbindFromRenderStep(PLOP_MODE)
		ContextActionService:UnbindAction(PLOP_CLICK)
		ContextActionService:UnbindAction(PLOP_ROTATE)
		plopChairButton.Visible = true
		plopTableButton.Visible = true
		rotationAngle = 0
		if plopCFrame and activeGhostObject then
			plopEvent:FireServer(plopCFrame, activeGhostObject.Name)
		end
	end
end

local function onRotate(actionName, inputState)
	if inputState == Enum.UserInputState.End then
		rotationAngle += ROTATION_STEP
		if rotationAngle >= 360 then
			rotationAngle = 0
		end
	end
end

local function onPlopButtonActivated(activeGhostObj)
	plopChairButton.Visible = false
	plopTableButton.Visible = false
	RunService:BindToRenderStep(PLOP_MODE,
		Enum.RenderPriority.Camera.Value + 1, onRenderStepped)
	ContextActionService:BindAction(PLOP_CLICK, function(actionName, inputState)
		onMouseInput(actionName, inputState)
	end, false, Enum.UserInputType.MouseButton1)
	ContextActionService:BindAction(PLOP_ROTATE, onRotate, false, Enum.KeyCode.R)
end

local function onPlopChairButtonActivated()
	activeGhostObject = ghostChair
	onPlopButtonActivated(activeGhostObject)
end

local function onPlopTableButtonActivated()
	activeGhostObject = ghostTable
	onPlopButtonActivated(activeGhostObject)
end

plopChairButton.Activated:Connect(onPlopChairButtonActivated)
plopTableButton.Activated:Connect(onPlopTableButtonActivated)

and heres the script for the placement, however i dont think that u will need to edit it, but if u have to u can do it:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")

local events = ReplicatedStorage:WaitForChild("Events")
local plopEvent = events:WaitForChild("PlopEvent")

local ploppables = ServerStorage:WaitForChild("Ploppables")
local chair = ploppables:WaitForChild("ChairTest")
local table = ploppables:WaitForChild("Table")

local function onPlop(player, cframe, objectType)
	local objectCopy
	if objectType == "GhostChairTest" then
		objectCopy = chair:Clone()
	elseif objectType == "GhostTable" then
		objectCopy = table:Clone()
	else
		warn("Invalid objectType received.")
		return
	end

	objectCopy:SetPrimaryPartCFrame(cframe)
	objectCopy.Parent = game.Workspace
end

plopEvent.OnServerEvent:Connect(onPlop)

Thanks to everyone that will try to help me, and if anything is in bad grammar or isn’t written correctly, then im sorry, my english isnt that good.

3 Likes

Hello,
I’m also not good developer to design systems like that, but i think this maybe is this what you need.

EgoMoose once created tutorial on devforum where he showed how to create placing system. It’s called ‘Creating A Furniture Placement System’. (link).

I hope this would help to you.

4 Likes

Thank you very much for trying to help me, however it wasnt very helpfull since its very hard to understand how it works, by that i mean i dont see any explanation on what to add, where to add it, and all that, i only see code and explanation for what the code does, but still thanks for trying to help me

4 Likes

So basically if im understanding correctly you want to create like a gird placement system where it has collisions and it snaps to a certain amount of grid cells on that grid depending on how big the object is?

2 Likes

yes, that is exactly what i want to make, it might sound easy but its not for me, as im a new developer trying to learn a bit of lua code

4 Likes

Yeah dont worry this is actually a very advanced topic, the link that pixel sent earlier to the tutorial is in my honest opinion way to complicated and just complicates everything;

With that being said… I have created this exact sort of system and it’s actually very well made; you’ve given me the idea to actually make it a module that people can actually use

4 Likes

Your talking about something like this right

5 Likes

Np, its nice to hear that i gave you a idea, and i also think that the link That pixel sent is very complicated
and yep im talking about something like the video u sent, and i really like the gui :+1:
(idk why but this might sound like that i want to have that exact thing, no it doesnt mean that, but well if u will of course allow me to use it i would, but this doesnt mean i want to, so like… yea i just thought that it kinda sounded like that i just wanted to say that its nice)

5 Likes

Thank you man means alot its from a project I was working on back in September but the scope was too large and I decided to abandon it but I still have the placement system, so im going too try to create a nice module that people can use to make it super easy to setup, thank you for giving my old project a purpose again

6 Likes

Np, i hope that you will find fun trying to make it so that it can easily help people like me, it would be nice if you could reply when you have a solution, have a great day/night/evening/afternoon and what ever time names there are.

5 Likes

Not sure if I can contribute a lot here, but you for starters need a snapping function.

local grid_size = 4

function snap(x)
 return math.floor((x / grid_size) + 0.5) * grid_size
end

function snapVector(v)
 return Vector3.new(snap(v.X), snap(v.Y), snap(v.Z))
end

It’s very short and simple but this should make sure positions are always rounded off and snapped to the specified grid size.

For collision detection you can either use Raycasts / Shapecasts or GetPartBoundsInBox().

5 Likes

do i have to change something? or add something?
cuz it doesnt really work, but still thanks for trying to help me

2 Likes

Yeah, these functions weren’t exactly intended to be directly copy-pasted.
But you can copy and change up things to adapt them to your code.

I usually just write my code snippets like that because I find it easier to show/explain things.

2 Likes

alright, thank you very much for trying to help me, sadly i only know 1% Lua Code, so… i cant really do much with the code that you gave me, but still thanks that you tryed to help me

2 Likes

I’m making impressive progress on the module!

Basically I’m porting alot of functions and stuff and just packing them into a seamless class that the server and client can use!

Current features:

Plot Claiming
Plot Rotation
Visualize Grid Points
Automatic Piece To Grid Dimensions
Grid Creation (specificed by gridSize)

3 Likes


This is the snapping used in my building game. My game also has relative grid so that’s what the relative position stuff is

But in a summarized way:
image
Position here is not a vector, you need to do the math for every axis. You can also have a different offset and grid size per axis
You’d also usually want the offset to be (by default) half of the grid size or the part size, or well, it will depend on the height of the surface you are building on, if the build system doesn’t use a relative grid

One other thing, you’ll have to play a bit with Mouse.Hit, to prevent the grid system from placing parts inside of the hovered part, basically rounding the wrong way

(Video of the issue I am talking about)

Here are the two “best” options (in my opinion)

– Make it so position is a bit before where the mouse ray collides with the hovered part

 -- This returns a CFrame 0.0001 studs closer to the camera
local TranslatedCFrame = Mouse.Hit * Vector3.new(0,0,0.0001)

Mouse.Hit points towards the camera (on the Z axis), which makes it very easy to translate

– Use the normal of the mouse hit to offset the position
This solution is quite good, as it’s perfect for making it so you can place parts on spheres or on top of wedges, while the previous solution works, it has the downside of allowing cubes to be placed diagonally to other cubes, which isn’t ideal
This is how it looks in my game


Here it returns a CFrame, but it should work just fine with a vector

What I mean by cube being placed diagonally

Hope this helps :wink:

2 Likes

I’d say you should definitely try familiarizing yourself with Luau a bit more before approaching something like a grid placement system. These types of systems are complex, and usually involve lots of advanced concepts such as object-oriented-programming. Try working on some smaller projects first, and work your way up to the big ones!

1 Like

I have made two resources that might help you make a placement system for your game:

  1. Placement Service
    I created Placement Service so it’s easy to make a grid placement system. The module is well documented and also has its own dev forum tutorial.

  2. My YouTube series on this
    I actually made a full series on making a placement system based on my module Placement Service. If learning how the system actually works is your thing, I explain everything you need to know to make a basic placement system from scratch!

2 Likes

that sounds very nice, and the Plot Claiming Feature will help me in the future too, i thank you very much for trying/helping me

1 Like

i know, the problem is i got no other ideas, and i find it kinda hard to learn Lua code, sadly the Offical Roblox Lua Code Guide book didnt helped that much, and another book i ordered to help me learn lua code turned out to be a fake book so a scam, in the future i will probably do other Methods to learn lua code, but currently i cant do much to learn it, still thanks for your respond

1 Like