Creating a placement system without having a part for each grid increment

Hello!

I was wondering how I could make a placement system with a single part for the player’s plot instead of having parts for each grid increment? I currently want a 40x40 grid of 2.5x2.5 stud wide squares, however having 1,600 parts is really inefficient.

Here’s my current placement system for walls, which places them at the edge of each part:

function module:SnapToGrid(mouse, model, testing)
	local targ = mouse.Target
	if targ then
		if model then
			if string.find(targ.Name, 'GRID_PART') then
				model.Parent = targ
			else
				return
			end
			if model:FindFirstChild('Type').Value == 'Wall' then
				for i,v in pairs(model:GetDescendants()) do
					if v:IsA('BasePart') then
						v.Transparency = 0.5
						v.CanCollide = false
					end
				end
				
				local walls = {
					wallN = Vector3.new(targ.Position.X + (targ.Size.X / 2), targ.Position.Y, targ.Position.Z);
					wallE = Vector3.new(targ.Position.X, targ.Position.Y, targ.Position.Z + (targ.Size.Z / 2));
					wallS = Vector3.new(targ.Position.X - (targ.Size.Z / 2), targ.Position.Y, targ.Position.Z);
					wallW = Vector3.new(targ.Position.X, targ.Position.Y, targ.Position.Z - (targ.Size.Z / 2))
				}
				
				local smallest = Vector3.new(math.huge, math.huge, math.huge)
				local smallestWall = ''
				local smallestMagnitude = math.huge
				
				for i,v in pairs(walls) do
					local testingMagnitude = (v - mouse.Hit.Position).Magnitude
					if testingMagnitude < smallestMagnitude then
						smallestMagnitude = testingMagnitude
						smallest = v
						smallestWall = tostring(i)
					end
				end
				
				local smallestVector = smallest + Vector3.new(0, smallest.Y + (model.PrimaryPart.Size.Y / 2), 0)
				
				if not string.find(targ.Name, 'GRID') then
					return
				end

				local cframe = CFrame.new(smallestVector)
				if smallest == walls.wallN or smallest == walls.wallS then
					cframe = CFrame.new(smallestVector) * CFrame.Angles(0, math.rad(90), 0)
				end
				
				model:SetPrimaryPartCFrame(cframe)
			end
		end
	end
end
1 Like

I got a placement sytem but its for terrain

1 Like

You can just round to the nearest 2.5 within object space. In general, the way to round to a custom increment like 2.5 is the following equation: math.round(num / increment) * increment

So, given your grid part, assuming it’s laying flat on the ground, you could transform a world position into the grid position as follows:

local function SnapTo(num, inc)
   return math.round(num / inc) * inc
end

local gridPart = somePart
local placementPosWorld = someVector3InTheWorld
local gridNodeSize = 2.5

local yPos = gridPart.Position.Y + (gridPart.CFrame.UpVector * (gridPart.Size.Y / 2))

-- Get relative position:
local relativePos = gridPart.CFrame:PointToObjectSpace(placementPosWorld)

-- Snap the relative position to the grid:
local pos = Vector3.new(SnapTo(relativePos.X, gridNodeSize), yPos, SnapTo(relativePos.Z, gridNodeSize))

-- Transform back to world pos:
local worldPos = gridPart.CFrame:PointToWorldSpace(pos)

If it looks like it’s snapping to the edge of the grid nodes, just offset the pos X and Z values by half of the increment (gridNodeSize / 2).

The reason for transforming to object space is to handle grid rotations. Your grid can be at any angle and it will still snap to that grid.

4 Likes

Thanks! That’s a really good idea.

I’m a little confused about the placementPosWorld variable, what should that be? Also, where do I incorporate the Vector3 of the tested position?

That would be something like the mouse position in the game

1 Like

Right, I’m sending the players mouse in the module as a variable, so would mouse.Hit be the placementWorldPos?

Yup pretty much. I believe mouse.Hit is a CFrame value though, so you will probably need to use mouse.Hit.Position.

1 Like

Alrighty, that one worked. Thanks so much!

1 Like