I’m working on a game that is procedurally generated. The issue was the server gets too much memory usage from generating copies of objects from different libraries.
Solution? Compress the objects not in view of the player to a reference of their source object.
Objects are classified into different categories and placed accordingly.
Render Objects of different default sizes are used.
Then check the distance of objects to see if they are within the range you want and do the targeting of all npcs and players. Then create a render object and recognize the source object.
Through testing the bottleneck for performance of this system was touch connections
So i ended up using the Zone plus module after using it for something else.
ZonePlus v3.2.0 | Construct dynamic zones and effectively determine players and parts within their boundaries - Resources / Community Resources - Developer Forum | Roblox
Occlusion culling and Payload Execution,
I run this code on the client and cache all the entered zones and fire them to the server as a payload every 1.2-2.4 seconds and check each object with a custom Attribute called “Zoned”
-- This creates a zone for every ambient group, then listens for when the local player enters and exits
--task.wait(1.2)
local Zone = require(game:GetService("ReplicatedStorage").Zone)
local localPlayer = game.Players.LocalPlayer
local playerGui = localPlayer:WaitForChild("PlayerGui")
local Remote=game.ReplicatedStorage.GlobalSpells.ZoneRemote
local zonearray={}
local id=0
local payloadarray={}
local function payloadcache(container)
table.insert(payloadarray,container)
end
local camera=workspace.CurrentCamera
local function IsInView(object,cameraViewportSize)
--if math.abs(object.Position.Y-localPlayer.Character.HumanoidRootPart.Position.Y)>50 then return false end
--print(object.Position)
local objectPosition = camera:WorldToViewportPoint(object.Position)
-- Check if the object is within the camera's viewport
--print(objectPosition)
if objectPosition.X >= 0 and objectPosition.X <= cameraViewportSize.X and
objectPosition.Y >= 0 and objectPosition.Y <= cameraViewportSize.Y and
objectPosition.Z > 0 then -- Z > 0 means the object is in front of the camera
return true
else
return false
end
end
local objects=workspace.TouchBox
local function checkobjects(pos)--check if object is in view and
local cameraViewportSize = camera.ViewportSize
for i,reference in objects:GetChildren() do
if reference:IsA("BasePart") then
if IsInView(reference,cameraViewportSize) then
reference.CanQuery=true
else
reference.CanQuery=false
end
end
end
end
local function RegisterLoop()
spawn(function()
local RefreshRate=2.4--one payload per second
local prevpos=localPlayer.Character.HumanoidRootPart.Position
while true do
if prevpos~=localPlayer.Character.HumanoidRootPart.Position then
checkobjects(localPlayer.Character.HumanoidRootPart.Position)
prevpos=localPlayer.Character.HumanoidRootPart.Position
Remote:FireServer(payloadarray)
payloadarray={}
end
task.wait(RefreshRate)
end
end)
end
local enum = require(game:GetService("ReplicatedStorage").Zone.Enum)
local connections={}
local function Areatitle(container,zone)
if container:GetAttribute("Zoned")==false then
container:SetAttribute("Zoned",true)
local zonearray = Zone.new(container)
local container=container
local connection=nil
--local contain=nil
--local remove=nil
--zonearray.accuracy=enum.enums.Accuracy.Low
connection=zonearray.localPlayerEntered:Connect(function()
--print("Zone entered "..tostring(container))
print("Zone interaction")
container.CanQuery=false
payloadcache(container)
connection:Disconnect()
zonearray:Destroy()
connection=nil
zonearray=nil
container=nil
end)
end
end
local ambientAreas = workspace.TouchBox
for _, container in pairs(ambientAreas:GetChildren()) do
--task.wait()
Areatitle(container)
end
ambientAreas.ChildAdded:Connect(Areatitle)
--ambientAreas.ChildRemoved:Connect(Areas)
RegisterLoop()
Finally, this is handled by the server like this
local renderer = game.ServerStorage.DarknessNPCs.ModuleScript
local render = require(renderer)
local functionarray = {}
-- Function to interact with all objects within a 20 stud radius
local function interactWithObjectsInRadius(player, object)
-- Get the position of the object
local position = object.Position
-- Find all parts within a 20 stud radius of the object
local objectsInRadius = workspace.TouchBox:GetDescendants()
for _, obj in pairs(objectsInRadius) do
if obj:IsA('Part') and (obj.Position - position).magnitude <= 30 then
-- Check if the object has a corresponding function in functionarray
local func = functionarray[obj]
if func then
-- Trigger the interact function
func.interact(player)
end
end
end
end
-- Populate the functionarray with functions from the ModuleScript
for i, v in pairs(workspace.TouchBox:GetChildren()) do
local func = render[v.Name]
v.CanTouch=false
if func then
functionarray[v] = func(v.Actor:FindFirstChild("DarknessSpawner"))
end
end
-- Connect the ChildAdded event to update the functionarray
workspace.TouchBox.ChildAdded:Connect(function(v)
v.CanTouch=false
local func = render[v.Name]
if func then
--functionarray[v] =
func(v.Actor:FindFirstChild("DarknessSpawner"))
end
end)
-- Connect the ZoneRemote OnServerEvent to interact with objects within a radius
game.ReplicatedStorage.GlobalSpells.ZoneRemote.OnServerEvent:Connect(function(player, objectsInRadius )
--print(objectsInRadius)
for _, obj in pairs(objectsInRadius) do
--local func = functionarray[obj]
-- if func then
-- Trigger the interact function
local func = render[obj.Name]
if func then
--functionarray[v] =
local func= func(obj.Actor:FindFirstChild("DarknessSpawner"))
func.interact(player.Character.HumanoidRootPart)
end
end
end)
In conclusion this brought the performance of my procedurally generated world from 15FPS to 60FPS on a laptop and allows it to run on edge devices.
If you would like to check out the demo you can see it here on my testing server.
Epic RPG with AI +Optimized Performance - Roblox