Local script cant see the children of a folder

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.

Try disabling StreamingEnabled under workspace.

1 Like

How did that work? I never expected StreamingEnabled to interfere with folders

It will interfere with any renderable object as streaming essentially limits whats replicated to the client to a radius around the player in order to save bandwidth.

Ideally, you should leave it enabled and refactor as its good for performance, but if that’s not currently a concern then leaving it disabled for now is fine. :slight_smile:

So I should only disable it when the player is actively in the placing mode?

I don’t believe you can set it within a script.

I’m not sure what exactly your system entails so i’ll throw out two ideas that may get you going:

  1. Store the canvases in replicated storage and clone them in from the client. If your stuff isn’t all client side already, then you might need to do some networking stuff with remotes to tell the server about placements.

  2. Refactor the code to not rely on the canvases at the start. Instead, only register a canvas when it is added. You might be able to use something like tags for this :slight_smile:

Alright, thank you for the help! I’ll keep this in mind.

1 Like

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