Placement system not working

I want to create a placement system using OOP. For some reason the module script does not work.

I did not look for any help on the DevForum.

PlacementSystem:

--!nonstrict

--Grandpa mod
--Placement stuff

--Erro messages
local ERROR_MESSAGE_INVAILD_BLOCK = "Given block called %s is invaild (%s does not exist)."
local ERROR_MESSAGE_NO_ACTIVATION = "Forgot to run :Activate() thus can't place block."

--Numbers
local GRID_SIZE = 4

--Services
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

--Instances
local Blocks = script.Parent:WaitForChild("Blocks")
local Camera = workspace.CurrentCamera

local PlacementSystem = {}
PlacementSystem.__index = PlacementSystem


local function SnapToGrid(Number)
	--Grid snapping
	
	local x = (Number / GRID_SIZE) + 0.5
	
	return math.floor(x) * GRID_SIZE
end

local function GetMouseWorldPosition(MousePosition)
	--Gets a Vector2 mouse position and spits out a Vector3 one.
	
	local WorldPosition = Camera:ScreenPointToRay(MousePosition.X, MousePosition.Y + 36, 0)
	
	return WorldPosition
end


function PlacementSystem.new(CancelKey, GridSize)
	--OOP stuff
	
	local self = setmetatable({}, PlacementSystem)
	
	self.CancelKey = CancelKey
	self.Placing = false
	self.CurrentBlock = nil
	
	GRID_SIZE = GridSize
	
	return self
end

function PlacementSystem:Activate(BlockName)
	--Turns on placing mode
	
	local Block = Blocks:FindFirstChild(BlockName)
	
	assert(Block, ERROR_MESSAGE_INVAILD_BLOCK:format(BlockName, BlockName))
	Block = Block:Clone()
	
	self.Placing = true
	self.CurrentBlock = Block
	
	Block.Parent = workspace:WaitForChild("PlacementBlocks")
end

function PlacementSystem:Deactivate(BlockName)
	--Turns off placing mode and destroys the block
	--ONLY SHOULD BE USED WHEN CANCELING A PLACEMENT!!!!
	
	local Block = workspace:WaitForChild("PlacementBlocks"):FindFirstChild(BlockName)

	assert(Block, ERROR_MESSAGE_INVAILD_BLOCK:format(BlockName, BlockName))
	
	self.Placing = false
	self.CurrentBlock = nil
	
	Block:Destroy()
end

function PlacementSystem:Placing()
	--Handles the block moving stuff
	
	--Bad idea
	--assert(self.CurrentBlock, ERROR_MESSAGE_NO_ACTIVATION)
	--assert(self.Placing, ERROR_MESSAGE_NO_ACTIVATION)
	
	if self.CurrentBlock and self.Placing then
		local MousePosition = UserInputService:GetMouseLocation()

		MousePosition = GetMouseWorldPosition(MousePosition)
		local PosX, PosY, PosZ = SnapToGrid(MousePosition.X), MousePosition.Y, SnapToGrid(MousePosition.Z)

		self.CurrentBlock:PivotTo(CFrame.new(PosX, PosY, PosZ))
	end
end

task.spawn(function()
	RunService:BindToRenderStep("Placing", Enum.RenderPriority.Input.Value, function()
		PlacementSystem:Placing()
	end)
end)

return PlacementSystem

LocalScript:

local placement = require(game:GetService("ReplicatedStorage").PlacementSystem).new("C", 4)

wait(2)

placement:Activate("Block")

You don’t need to wrap this in a task.spawn since BindToRenderStepped doesn’t block the thread. The reason it doesn’t work though is because you’re effectively just calling PlacementSystem.Placing(PlacementSystem), which is not how you wrote the Placing instance method to work.

During this check in Placing:

if self.CurrentBlock and self.Placing then

self is PlacementSystem the class, and not an instance of said class. The class doesn’t have those properties, causing them to be nil which are falsey so Placing just does nothing because you called it wrong but doesn’t throw an error because you don’t do any checks to verify the right parameters are passed.

To fix it, you need to call Placing for every instance that exists each frame. Since it appears that PlacementSystem should be a singleton, you actually don’t need to use any of the fancy stuff that’s usually associated with Lua OOP. I found it easiest to just modify your script and add comments explaining the edits:

local PlacementSystem = {}
--Since there's only one instance, these fields don't need to be stored in a table. 
--This has the added benefit that outsiders won't be able to change them, effectively making them private.
Placing = false
CurrentBlock = nil

--Don't need to set __index anymore, because we're not creating instances that need to automatically reference the class sometimes.
--Since no instances should be created we don't need a .new class method.

function PlacementSystem.Initialize(cancelKey, gridSize) --Uses . syntax instead of :, since we don't need a way of getting a reference to the instance (it's always just PlacementSystem)
	CANCEL_KEY = cancelKey
	GRID_SIZE = gridSize
	--Preventing Initialize from being called again prevents outsiders from changing constants, making "constant" a bit more meaningful
	PlacementSystem.Initialize = function() error("Cannot re-initialize singleton class 'PlacementSystem'") end
end

function PlacementSystem.Activate(blockName)
	--Turns on placing mode
	
	local block = Blocks:FindFirstChild(blockName)
	
	assert(block, ERROR_MESSAGE_INVAILD_BLOCK:format(blockName, blockName))
	block = block:Clone()
	
	Placing = true
	CurrentBlock = block
	
	block.Parent = workspace:WaitForChild("PlacementBlocks")
end

function PlacementSystem.Deactivate()
	--Turns off placing mode and destroys the block
	--ONLY SHOULD BE USED WHEN CANCELING A PLACEMENT!!!!
	
	--local block = workspace:WaitForChild("PlacementBlocks"):FindFirstChild(blockName)
	local block = CurrentBlock --Simpler and less error-prone
	assert(block, "some other error message")
	
	Placing = false
	CurrentBlock = nil
	
	block:Destroy()
end

function PlacementSystem.UpdatePlacing(dt) --A better method name	
	if not Placing then --[[If we're placing but CurrentBlock is nil, surely something is wrong?]] 
		return 
	end
	if not CurrentBlock then
		--This is a sensible way of handling CurrentBlock missing, except Deactivate throws when CurrentBlock is nil.
		--Probably just allow Deactivate to work if CurrentBlock is nil?
		PlacementSystem.Deactivate()
		return
	end

	local MousePosition = UserInputService:GetMouseLocation()
	MousePosition = GetMouseWorldPosition(MousePosition)
	local PosX, PosY, PosZ = SnapToGrid(MousePosition.X), MousePosition.Y, SnapToGrid(MousePosition.Z)
	CurrentBlock:PivotTo(CFrame.new(PosX, PosY, PosZ))
end

RunService:BindToRenderStep("Placing", Enum.RenderPriority.Input.Value, function(dt)
	PlacementSystem:UpdatePlacing(dt)
end)

return PlacementSystem

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