Enhancing Game Performance with Procedural Generation and Efficient Memory Management

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.
image

Objects are classified into different categories and placed accordingly.

Render Objects of different default sizes are used.
image
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

3 Likes