Hello everyone,
I have been following EgoMoose’s Creating a furniture placement system tutorial but I have run into a big issue. I realized that the for loop in my local script does not detect the children of the folder in Workspace which contains the canvases. As a result of this, the rest of the script breaks and I cant place the furniture properly. I tried a print statement to debug this by doing print(canvases:GetChildren())
and it prints an empty table.
Edit: I have also checked the explorer while the game was running and the children were still there, so it’s not an issue about them being deleted.
Here are all the scripts for the system:
Local script
--local canvas = game.Workspace:WaitForChild("ExampleCanvas")
local remotes = game:GetService("ReplicatedStorage"):WaitForChild("Remotes")
local furniture = game:GetService("ReplicatedStorage"):WaitForChild("Furniture")
local pieces = furniture:GetChildren()
local GRID_UNIT = 2
local placement = require(game:GetService("ReplicatedStorage"):WaitForChild("Placement"))
local multiPlacementClass = require(game:GetService("ReplicatedStorage"):WaitForChild("MultiPlacement"))
local canvases = game.Workspace:WaitForChild("Canvases")
local canvasObjects = game.Workspace.CanvasObjects
local placements = {}
local indexLookup = {}
for k, canvas in next, canvases:GetChildren() do
print(tostring(canvas))
indexLookup[canvas] = k
placements[k] = placement.new(canvas)
placements[k].GridUnit = GRID_UNIT
end
print(canvases:GetChildren()) --Here the script prints an empty table...
print(placements)
print(indexLookup)
local multiPlace = multiPlacementClass.new(placements)
local mouse = game.Players.LocalPlayer:GetMouse()
mouse.TargetFilter = canvasObjects
local pivotPoint = CFrame.new()
local isRotating = false
local rotation = 0
local modelCount = 0
local model = nil
local placement = placements[1]
--
local function onSwitch(actionName, userInputState, input)
if (userInputState == Enum.UserInputState.Begin) then
if (model) then
model:Destroy()
end
modelCount = modelCount + 1 > #pieces and 1 or modelCount + 1
model = pieces[modelCount]:Clone()
model.Parent = canvasObjects
end
end
local function onRotate(actionName, userInputState, input)
if (userInputState == Enum.UserInputState.Begin) then
--rotation = rotation + math.pi/2
isRotating = true
elseif (userInputState == Enum.UserInputState.End) then
isRotating = false
end
end
local function onPlace(actionName, userInputState, input)
if (userInputState == Enum.UserInputState.Begin) then
local cf = placement:CalcPlacementCFrame(model, pivotPoint, rotation)
placement:Place(furniture[model.Name], cf, placement:isColliding(model))
end
end
local function onClear(actionName, userInputState, input)
if (userInputState == Enum.UserInputState.Begin) then
model.Parent = nil
placement:Clear()
model.Parent = mouse.TargetFilter
end
end
local function onSave(actionName, userInputState, input)
if (userInputState == Enum.UserInputState.Begin) then
placement:Save()
end
end
--
onSwitch(nil, Enum.UserInputState.Begin, nil)
game:GetService("ContextActionService"):BindAction("switch", onSwitch, false, Enum.KeyCode.E)
game:GetService("ContextActionService"):BindAction("rotate", onRotate, false, Enum.KeyCode.R)
game:GetService("ContextActionService"):BindAction("place", onPlace, false, Enum.UserInputType.MouseButton1)
game:GetService("ContextActionService"):BindAction("clear", onClear, false, Enum.KeyCode.C)
game:GetService("ContextActionService"):BindAction("save", onSave, false, Enum.KeyCode.F)
game:GetService("RunService").RenderStepped:Connect(function(dt)
print(mouse.Target)
if (indexLookup[mouse.Target] and not isRotating) then
placement = placements[indexLookup[mouse.Target]]
end
local cf
print(isRotating)
if (not isRotating) then
cf = placement:CalcPlacementCFrame(model, mouse.Hit.p, rotation)
pivotPoint = cf.p
rotation = 0
placement.GridUnit = GRID_UNIT
else
local canvasCF = placement:CalcCanvas()
local v = canvasCF:vectorToObjectSpace(mouse.Hit.p - pivotPoint)
rotation = -math.atan2(v.y, v.x)
placement.GridUnit = 0
cf = placement:CalcPlacementCFrame(model, pivotPoint, rotation)
end
model:SetPrimaryPartCFrame(cf)
end)
ModuleScript “Placement”
local isServer = game:GetService("RunService"):IsServer()
local furniture = game:GetService("ReplicatedStorage"):WaitForChild("Furniture")
local remotes = game:GetService("ReplicatedStorage"):WaitForChild("Remotes")
local initPlacement = remotes:WaitForChild("InitPlacement")
local invokePlacement = remotes:WaitForChild("InvokePlacement")
local dsPlacement = remotes:WaitForChild("DSPlacement")
local worldBoundingBox = require(game:GetService("ReplicatedStorage"):WaitForChild("AABB")).worldBoundingBox
--
local Placement = {}
Placement.__index = Placement
-- constructor
function Placement.new(canvasPart, canvasObjects)
local self = setmetatable({}, Placement)
self.CanvasPart = canvasPart
if (isServer) then
self.CanvasObjects = canvasObjects or Instance.new("Folder")
self.CanvasObjects.Name = "CanvasObjects"
self.CanvasObjects.Parent = canvasObjects and canvasObjects.Parent or canvasPart
else
self.CanvasObjects = initPlacement:InvokeServer(canvasPart)
end
self.Surface = Enum.NormalId.Top
self.GridUnit = 1
return self
end
function Placement.fromSerialization(canvasPart, data)
local self = Placement.new(canvasPart)
local canvasCF = canvasPart.CFrame
data = data or {}
for cf, name in pairs(data) do
local model = furniture:FindFirstChild(name)
if (model) then
local components = {}
for num in string.gmatch(cf, "[^%s,]+") do
components[#components+1] = tonumber(num)
end
self:Place(model, canvasCF * CFrame.new(unpack(components)), false)
end
end
return self
end
-- methods
function Placement:CalcCanvas()
local canvasSize = self.CanvasPart.Size
local up = Vector3.new(0, 1, 0)
local back = -Vector3.FromNormalId(self.Surface)
local dot = back:Dot(Vector3.new(0, 1, 0))
local axis = (math.abs(dot) == 1) and Vector3.new(-dot, 0, 0) or up
local right = CFrame.fromAxisAngle(axis, math.pi/2) * back
local top = back:Cross(right).unit
local cf = self.CanvasPart.CFrame * CFrame.fromMatrix(-back*canvasSize/2, right, top, back)
local size = Vector2.new((canvasSize * right).magnitude, (canvasSize * top).magnitude)
return cf, size
end
function Placement:CalcPlacementCFrame(model, position, rotation)
local cf, size = self:CalcCanvas()
local modelSize = worldBoundingBox(CFrame.Angles(0, rotation, 0), model.PrimaryPart.Size)
local lpos = cf:pointToObjectSpace(position);
local size2 = (size - Vector2.new(modelSize.x, modelSize.z))/2
local x = math.clamp(lpos.x, -size2.x, size2.x);
local y = math.clamp(lpos.y, -size2.y, size2.y);
local g = self.GridUnit
if (g > 0) then
x = math.sign(x)*((math.abs(x) - math.abs(x) % g) + (size2.x % g))
y = math.sign(y)*((math.abs(y) - math.abs(y) % g) + (size2.y % g))
end
return cf * CFrame.new(x, y, -modelSize.y/2) * CFrame.Angles(-math.pi/2, rotation, 0)
end
function Placement:isColliding(model)
local isColliding = false
local touch = model.PrimaryPart.Touched:Connect(function() end)
local touching = model.PrimaryPart:GetTouchingParts()
for i = 1, #touching do
if (not touching[i]:IsDescendantOf(model)) then
isColliding = true
break
end
end
touch:Disconnect()
return isColliding
end
function Placement:Place(model, cf, isColliding)
if (not isColliding and isServer) then
local clone = model:Clone()
clone:SetPrimaryPartCFrame(cf)
clone.Parent = self.CanvasObjects
end
if (not isServer) then
invokePlacement:FireServer("Place", model, cf, isColliding)
end
end
function Placement:Serialize()
local serial = {}
local cfi = self.CanvasPart.CFrame:inverse()
local children = self.CanvasObjects:GetChildren()
for i = 1, #children do
serial[tostring(cfi * children[i].PrimaryPart.CFrame)] = children[i].Name
end
return serial
end
function Placement:Save()
local success = dsPlacement:InvokeServer(true, true)
if (success) then
print("Saved")
end
end
function Placement:Clear()
self.CanvasObjects:ClearAllChildren()
if (not isServer) then
invokePlacement:FireServer("Clear")
local success = dsPlacement:InvokeServer(true, false)
if (success) then
print("Cleared")
end
end
end
--
return Placement
ServerScript
--local datastore = game:GetService("DataStoreService"):GetDataStore("PlacementSystem")
local placementClass = require(game:GetService("ReplicatedStorage"):WaitForChild("Placement"))
local placementObjects = {}
local remotes = game:GetService("ReplicatedStorage"):WaitForChild("Remotes")
--
function remotes.InitPlacement.OnServerInvoke(player, canvasPart)
placementObjects[player] = placementClass.new(canvasPart, game.Workspace.CanvasObjects)
return placementObjects[player].CanvasObjects
end
remotes.InvokePlacement.OnServerEvent:Connect(function(player, func, ...)
if (placementObjects[player]) then
placementClass[func](placementObjects[player], ...)
end
end)
function remotes.DSPlacement.OnServerInvoke(player, saving, useData)
local key = "player_"..player.UserId
local success, result = pcall(function()
if (saving and placementObjects[player]) then
if (useData) then
datastore:SetAsync(key, placementObjects[player]:Serialize())
else
datastore:SetAsync(key, {})
end
elseif (not saving) then
return datastore:GetAsync(key)
end
end)
if (success) then
return saving or result
else
warn(result)
end
end
Any help is appreciated, I really want to learn how to make a grid placement system.