How can I make a surface grid building system?

Basically, I want to make a building system that snaps objects to the surface they are placed on while keeping them in a grid on the surface itself.

Best example I could give is these bricks with a grid texture:

The idea is that the object would snap to the bricks surface and would follow the grid texture no matter the rotation.

1 Like

I don’t know if this is the best way to do it but you could make it move with the mouse and use math.round(), clone this script into your game and see if this is close to what you want. This is only an example of it.

local RunService = game:GetService("RunService")

local plr = game.Players.LocalPlayer
local mouse = plr:GetMouse()
local part = Instance.new("Part")


part.Anchored = true
part.Parent = game.Workspace
part.CanQuery = false
part.CanTouch = false
part.CanCollide = false

local connection = RunService.RenderStepped:Connect(function ()
	part.Position = Vector3.new(math.round(mouse.Hit.X), math.round(mouse.Hit.Y), math.round(mouse.Hit.Z))
end)
2 Likes

This isn’t exactly what I am trying to achieve.
I am trying to make the object snap to the surface and follow the grid only on the x and z axis while the y axis is the surface normal.

2 Likes

make a raycast and then use raycastresult.Instance to get the part that the raycast hit

then you can add the orientation of the part to your building when creating it

im still thinking about how to find which spot on the grid the building would be placed on
but in 2d the point that the mouse hits would be located at ( cos(angle), sin(angle) ) relative to where the point would be located at if it were a flat plane

you could also get the parts cframe and use its lookvector and rightvector multiplied in increments to find points located on the grid, and then search for the closest point to the mouse

Adding the orientation of the part wouldn’t really help in anything. Since let’s say that a square is rotated 180 degrees, then the object would also get turned 180 degrees without a reason.

if its 180 degrees then it should be the same as having 0 degrees

but in a plane rotated 15 degrees the building also has to align with it at 15 degrees

Still, the point is that just adding rotation won’t achieve anything, since the surfaces and parts that I am trying to stick to can be very different.

what do you mean they are very different
are you saying there are like triangles and spheres

the building still has to rotate with the parts to look like it sticks to them

Let’s say we’re trying to place the object on a sphere. I can rotate the sphere all kinds of ways and it would still look like a sphere, but the rotation would still be applied to the object.

does this work for you?

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

local plr = Players.LocalPlayer
local mouse = plr:GetMouse()
local part = Instance.new("Part")

local RoundSize = 4

part.Anchored = true
part.Parent = game.Workspace
part.CanQuery = false
part.CanTouch = false
part.CanCollide = false

local function alignPartToSurface(hitPosition, hitNormal)
	part.Position = hitPosition
	local newCFrame

	if math.abs(hitNormal.Y) > 0.5 then
		
		newCFrame = CFrame.new(hitPosition) * CFrame.Angles(0, 0, 0)
	else
		
		local upVector = hitNormal
		local forwardVector = Vector3.new(0, 1, 0):Cross(upVector).Unit
		local rightVector = upVector:Cross(forwardVector).Unit

		newCFrame = CFrame.fromMatrix(hitPosition, rightVector, upVector, forwardVector)
	end

	part.CFrame = newCFrame
end

local connection = RunService.RenderStepped:Connect(function()
	local ray = Ray.new(mouse.UnitRay.Origin, mouse.UnitRay.Direction * 1000)
	local hit, position, normal = workspace:FindPartOnRay(ray)
	if hit then
		alignPartToSurface(position, normal)
	else
		part.Position = mouse.Hit.Position
	end
end)

1 Like

Really sorry for the late response!
Looking through your code, it does help, more specifically the CFrame.fromMatrix(), although it still doesn’t help with the grid.

like making it snap to the grid?

Yeah, I tried snapping it before and after the matrix, but it didn’t work.

1 Like

this is as close as i got it:

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

local plr = Players.LocalPlayer
local mouse = plr:GetMouse()
local part = Instance.new("Part")

local RoundSize = 4

part.Anchored = true
part.Parent = game.Workspace
part.CanQuery = false
part.CanTouch = false
part.CanCollide = false

local function roundToGrid(position, size)
	return Vector3.new(
		math.floor(position.X / size + 0.5) * size,
		math.floor(position.Y / size + 0.5) * size,
		math.floor(position.Z / size + 0.5) * size
	)
end

local function alignPartToSurface(hitPosition, hitNormal)
	local roundedPosition = roundToGrid(hitPosition, RoundSize)
	part.Position = roundedPosition
	local newCFrame

	if math.abs(hitNormal.Y) > 0.5 then
		newCFrame = CFrame.new(roundedPosition) * CFrame.Angles(0, 0, 0)
	else
		local upVector = hitNormal
		local forwardVector = Vector3.new(0, 1, 0):Cross(upVector).Unit
		local rightVector = upVector:Cross(forwardVector).Unit

		newCFrame = CFrame.fromMatrix(roundedPosition, rightVector, upVector, forwardVector)
	end

	part.CFrame = newCFrame
end

local connection = RunService.RenderStepped:Connect(function()
	local ray = Ray.new(mouse.UnitRay.Origin, mouse.UnitRay.Direction * 1000)
	local hit, position, normal = workspace:FindPartOnRay(ray)
	if hit then
		alignPartToSurface(position, normal)
	else
		part.Position = roundToGrid(mouse.Hit.Position, RoundSize)
	end
end)

the only thing is when you snap it to the grid it does snap to the grid but it wont stick to the part.

That’s the issue I faced, I want it to stick to the surface while also snapping to the grid.

weld it to the other part or something but i dont think thats good

How will welding it help? I am not trying to physically attach it to the part.
I am only trying to place it so that it’s attached to the surface while also snapping to a grid.

division and rounding. example: vector3.new(math.round(positionx / 4) * 4, math.round(positiony / 4) * 4, math.round(positionz / 4) * 4)

this gives you the position.

I’ve done a building game that I think has what you are looking for BloxBox [Experimental 0.06.P] - Roblox

How it works is, using CFrames (:Inverse() or :ToObjectSpace()), I retrieve the position of the mouse relative to the closest corner of the target part (which includes). Then I round the relative position as you would in a simple grid building system, and finally transform it back into world coordinates using CFrame multiplication (equivalent to :ToWorldSpace())
There is more math to make the placing part’s rotation more consistent (like if the target part is rotated 195 degress, I want the part I’m pacing to only be rotated by 15 degrees (so it matches the surface it is being placed on), if the orientation setting is set to 0, 0, 0)

Doing such a system does require a good knowledge of CFrames

I can go more in depth with code examples if you’d like, and if this is what you are looking for

1 Like