How to replicate everything in workspace to a viewport without significant performance tradeoffs?

Hey there folks! I wasn’t sure whether this belonged in Scripting Support (Since it involved scripting) or Game Design Support (Since it also involves game designing) so I just decided to put it here.

CONTEXT:
I’m working on a co-op game where players are inside of a tank and can’t see the outside world. Instead, they rely on screens inside of the tank to see the outside, sort of like an immersive stress-inducing experience.

My problem however is that currently, the way I have the screen set up causes some minor performance issues. Now, I believe that this is studio-only and that it won’t cause that much issues in actual gameplay, but I still want to see if I can optimize this even more, how would I go about optimizing the replication of everything in workspace to a viewportframe without trading a significant amount of performance?

tl;dr: I’m using a LocalScript to copy everything in workspace and paste it into a viewportframe every RenderStepped to create a camera monitor system, any ideas on how to go about optimizing this even more?

Code:

local RunService = game:GetService("RunService")

local SurfaceGui = script.Parent
local ViewportFrame = SurfaceGui.ViewportFrame

local CurrentCamera = Instance.new("Camera")
CurrentCamera.Parent = SurfaceGui
ViewportFrame.CurrentCamera = CurrentCamera

local ScreenAdornee = SurfaceGui.Adornee

local Essentials = workspace:WaitForChild("Essentials")
local PhysicalCamera = Essentials:WaitForChild("PhysicalCamera")

local function GetVisibleInstances(cameraInstance : Camera)
	local instances = {}
	for _, part in pairs(workspace:GetDescendants()) do
		if part:IsA("BasePart") and not part:IsA("Terrain") then
			table.insert(instances, part)
		end
	end
	
	return instances
end

RunService.RenderStepped:Connect(function()
	ViewportFrame:ClearAllChildren()
	CurrentCamera.CFrame = PhysicalCamera.CFrame
	local instances = GetVisibleInstances()
	
	for _, instance in pairs(instances) do
		if instance then
			local clonedInstance = instance:Clone()
			
			clonedInstance.Parent = ViewportFrame
		end
	end
end)

(If you’re wondering why GetVisibleInstances isn’t using anything like WorldToViewportFrame, it’s because functions like those only returned the instance if it’s ACTUAL position was in the camera’s view, if only a sliver of the instance was in the camera and not its true origin point/vector position then it wouldn’t count as “visible.”)

image

(While testing, once I opened the actual viewportframe in the Studio Explorer it would start to lag my studio even if I hid it.)

image

If you absolutely need to clone every object, then you should be able to just clone the parent instances and that’ll automatically clone the children instances. With your current method, you are creating many times more instances than you need to.

local function getVisibleInstances()
    local instances = {}
    for _, part in pairs(workspcae:GetChildren()) do
        if not part:IsA("Terrain") and not part:IsA("Camera") then
            table.insert(instances, part)
        end
    end
end

Additionally, you can calculate the dot product between the camera’s LookVector and the part’s position before cloning, and if the dot product is negative, it’s behind the player. This won’t work with the previous code example as it does not verify there is a Position attribute, but should work with your code example.

local function GetVisibleInstances(cameraInstance : Camera)
	local instances = {}
	for _, part in pairs(workspace:GetDescendants()) do
		if part:IsA("BasePart") and not part:IsA("Terrain") 
            and workspace.CurrentCamera.CFrame.Positon:Dot(part.Position) > -0.1 then

            -- Checking `-0.1` because it calculates at the center of parts, not the nearest edge
			table.insert(instances, part)
		end
	end
	
	return instances
end

If view distance could be shortened, you additionally could make a large part out front of the tank that will be its view range, and only copy parts that are in this part’s bounding box with workspace:GetPartsInPart(part).

1 Like

OOOOOOOOH! That is such a small mistake on my part that caused such a rippling effect! Whoopsie! You’re right! I can just use :GetChildren() instead of :GetDescendants()!

Also, I unfortunately can’t implement the “checking -0.1” idea because the camera of the player isn’t actually the camera of the tank, the tank uses it’s own camera whose cframe depends on a physical part in workspace.Essentials called “PhysicalCamera”

Think of it like the camera system in the game “Iron Lung” where the player and the tank itself aren’t moving and are just an enclosed room, while the camera of the screen is the only thing that’s actually moving in the game world.

Anyways, thank you very much for the help! Much appreciated!!!

You’re welcome!
You should still be able to do the dot product check, just with the tank’s camera instead.

1 Like

I don’t know if I’m being stupid (I probably am) but it isn’t working, fortunately I found an alternative solution through this: How would I detect if a part is infront or behind the player?

However it still has the problem of not being accurate with larger baseparts so I might drop it entirely!

Thank you anyways though! You’ve been a great help and the lag has significantly lessened from before!

1 Like

If you need the accuracy with larger parts, then yeah, just ignore it for now, any angle calculation will always have that issue.
Hopefully the lag is lessened enough to a workable level now for the rest of the game!

1 Like