Grid Building System Issue

Hey all!

I am trying to create a grid building system for my game, and I thought I was being smart here.

The idea is to check if the mouse has moved 4 studs before moving the “temporary” object, which does work, however it’s not like the snap-to-grid type system that I was hoping for and only realized after I started placing objects down.

Here is what it currently looks like

As you can see they are not exactly on the “grid” per se and are all over the place.

Here is my code
(p.s please do not do a code review on it, I know the code isn’t clean, doesn’t look good and probably doesn’t use best practices. I am cleaning it up as I go, I am just trying to make a simple system as of right now to get a working prototype out.)


local ContextAction = game:GetService("ContextActionService")
local Run = game:GetService("RunService")
if Run:IsClient() == false then error("This module should only be run on the client!") end
local Workspace = game:GetService("Workspace")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

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

--/ Settings
local SETTINGS = {
	Interpolation = true,
	MoveByGrid = true,
	BuildModePlacement = true,
	RotationStep = 90,
	MaxHeight = 90,
	LerpSpeed = 0.7
}

local Placement = {}
Placement.__index = Placement

-- / Constructor
function Placement.init()
	local self = {
		GRID_SIZE = 4,
		ITEM_LOCATION = nil,
		ROTATE_KEY = nil,
		TERMINATE_KEY = nil,
		BUILDING_ACTIVE = false,
		SELECTED_BUILDING = nil,
		MousePos = Vector3.new(0,0,0),
		LastMousePos = Vector3.new(0,0,0)
	}
	
	self.StartBuildAction = ContextAction:BindAction("BuildToggle", function(action, userinputstate, inputobject)
		if action == "BuildToggle" and userinputstate == Enum.UserInputState.End then
			self.BUILDING_ACTIVE = not self.BUILDING_ACTIVE
			print("Building Active: ", self.BUILDING_ACTIVE)
		end
	end, false, Enum.KeyCode.B)
	
	self.RunService = Run.PreRender:Connect(function()
		if self.BUILDING_ACTIVE == false then
			if self.SELECTED_BUILDING ~= nil then
				self.SELECTED_BUILDING:Destroy()
				self.SELECTED_BUILDING = nil
			end
			return
		end
		if self.SELECTED_BUILDING == nil then
			self.SELECTED_BUILDING = self:GetPlacementObject("BasicConveyer")
			self.SELECTED_BUILDING.Parent = game.Workspace
			self:CreateHighlight(self.SELECTED_BUILDING)
			self.LastMousePos = self.MousePos
			self.SELECTED_BUILDING:PivotTo(CFrame.new(self.MousePos))
		end
		if (self.MousePos - self.LastMousePos).Magnitude >= 4 then
			self.LastMousePos = self.MousePos
			self.SELECTED_BUILDING:PivotTo(CFrame.new(self.MousePos))
		end
	end)
	
	self.MouseMoveConnect = Mouse.Move:Connect(function()
		Mouse.TargetFilter = self.SELECTED_BUILDING
		self.MousePos = Mouse.Hit.Position --+ Vector3.new(0,0.5,0)
	end)
	
	self.MouseClick = Mouse.Button1Down:Connect(function()
		local obj = self:GetPlacementObject(self.SELECTED_BUILDING.Name)
		obj.Parent = game.Workspace
		obj:PivotTo(self.SELECTED_BUILDING.PrimaryPart.CFrame)
	end)
	
	return setmetatable(self, Placement)
end


function Placement:GetPlacementObject(id: string)
	local Builds = ReplicatedStorage:FindFirstChild("Builds")
	local SelectedObject = Builds:FindFirstChild(id, true)
	
	return SelectedObject:Clone()
end

function Placement:CreateHighlight(model: Model)
	if self.BuildingHighlight then
		self:DeleteHighlight()
	end
	self.BuildingHighlight = Instance.new("Highlight") :: Highlight
	self.BuildingHighlight.Adornee = model
	self.BuildingHighlight.FillColor = Color3.new(0.152941, 0.505882, 0.0470588)
	self.BuildingHighlight.OutlineColor = Color3.new(1,1,1)
	self.BuildingHighlight.FillTransparency = 0.5
	self.BuildingHighlight.Enabled = true
	self.BuildingHighlight.DepthMode = Enum.HighlightDepthMode.Occluded
	self.BuildingHighlight.OutlineTransparency = 0
	self.BuildingHighlight.Parent = model
end

function Placement:DeleteHighlight()
	self.BuildingHighlight:Destroy()
	self.BuildingHighlight = nil
end


return Placement

AGAIN FOR CLARIFICATION, THIS IS NOT A CODE REVIEW REQUEST, I JUST NEED MY SYSTEM FIXED RIGHT NOW

1 Like

For applying a grid, I round the mouse position like this:

local function getGridPosition(pos : Vector3, gridUnit : number) : Vector3
    local x, z = math.abs(pos.X), math.abs(pos.Z)

    local xRounded = math.ceil(x / gridUnit) * gridUnit
    local zRounded = math.ceil(z / gridUnit) * gridUnit

    return Vector3.new(xRounded * math.sign(pos.X), pos.Y, zRounded * math.sign(pos.Z))
end

Using absolute values with math.ceil is so that negative numbers aren’t rounded weirdly, since it returns an integer equal to or “larger” than the input.

2 Likes

Yea, I figured this out from a youtube short. I appreciate the comment and since what you posted is the solution that I used, I gave you the solve. Good looks and thanks !

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.