New Viewport Camera System

Viewport camera systems have been a bit of an obsession of mine, as they can be really useful in games, but usually don’t end up being performant enough.

However, I don’t think this needs to remain the case. Recently, I made a viewport camera system that seems to not affect the player’s FPS at all in-game. In addition, it’s pretty straightforward in concept. It definitely can be fine-tuned a lot more, but from what I can tell from my tests, it’s definitely the best camera system I’ve seen yet.

Performance-wise, with 15 of these running simultaneously on the user, each cameras’ FPS drops to 4-5 FPS on my machine. The actual game, however, remained pinned at 60 FPS, meaning the user feels essentially no lag from this.

This camera system also fixes a lot of issues I noticed with previous systems I have played around with, especially around character models, accessories, and 3d clothing. This new system has essentially no issues with any of these, except a couple of slightly off CFrame placements in more complex Accessories that use Weld Constraints.

Link to the model:

Issues I'm aware of

I’m going to ignore any issues that are generally inherent with all Viewport systems

  • Accessories with multiple parts using Weld Constraints sometimes have a weird offset applied to them (though not drastic)
  • Any changes in part size, color, or other properties besides position and orientation are not reflected (though this would be a fairly easy addition)
  • Parts that move visually without actually updating their CFrame will not be updated in the camera
  • Parts that are destroyed won’t be destroyed in the camera (this is also likely a relatively easy addition)
  • Parts won’t be updated if they are outside the range of the player
  • It’s all client-sided, so there may be replication issues. In addition, if StreamingEnabled is being used, and the player hasn’t loaded the area the camera is in, nothing will be shown.

This is a pretty early stage project, so it’s not going to be fully fleshed out.

Feel free to critique the code; I’m well aware it’s not very clean.
Feel free to make different versions of this system or use it however you want. Just credit me somewhere in the code.

Code
resolution = 100 -- Size in studs of the cube to be rendered and updated around the player

fps = 30

fov = 90

static_map = false

static_camera = false

camera = game.Players.LocalPlayer.Character:WaitForChild("Head") -- The camera. Must have a valid CFrame

offset = -5 -- Offset. Default is -5, and places the actual camera 5 studs behind the camera part.

viewport = script.Parent.ViewportFrame

loaded_items = {}
frame_items = {}
humanoids = {}
frame_humanoids = {}



creation_time = math.round(tick()*1000000000) -- Give a unique identifier to each camera display. 

function check_include(item)
	if item.ClassName == "Part"
		or item.ClassName == "BasePart" 
		or item.ClassName == "Attatchment" 
		or item.ClassName == "SurfaceAppearance" 
		or item.ClassName == "Weld" 
		or item.ClassName == "WeldConstraint"
		or item.ClassName == "MeshPart" 
		or item.ClassName == "UnionOperation" 
		or item.ClassName == "Texture" 
		or item.ClassName == "Decal" 
		or item.ClassName == "Mesh"
		or item.ClassName == "Accessory" then
		
		return true
	end
	return false
end

function has_cframe(item)
	if item.ClassName == "Part" 
		or item.ClassName == "BasePart" 
		or item.ClassName == "MeshPart"
		or item.ClassName == "UnionOperation" then
		
		return true
	end
	return false
end

function clean(object)
	for _, item in object:GetDescendants() do
		if check_include(item) == false then
			item:Destroy()
		end
	end
	return object
end

function update_cframes(model)
	for _, item in model.clone:GetChildren() do
		local corresponding_item = model.original:FindFirstChild(item.Name)
		if corresponding_item == nil then
			item:Destroy()
		end
		if has_cframe(item) then
			item.CFrame = corresponding_item.CFrame
		else
			if item.ClassName == "Accessory" then
				for _, item2 in item:GetChildren() do
					if item2.ClassName == "Part" or item2.ClassName == "BasePart" or item2.ClassName == "MeshPart" or item2.ClassName == "UnionOperation" then
						item2.CFrame = corresponding_item:FindFirstChild(item2.Name).CFrame
						item2.Transparency = corresponding_item:FindFirstChild(item2.Name).Transparency						
					end
				end
			end
		end
	end
	
	for _, item in model.original:GetChildren() do
		local corresponding_item = model.clone:FindFirstChild(item.Name)
		if corresponding_item == nil then
			if check_include(item) then
				item:Clone().Parent = model.clone
			end
		end
	end
end

function castBox()
	local parts = workspace:GetPartBoundsInBox(camera.CFrame, Vector3.new(resolution,resolution,resolution))
	for _, part in parts do
		if part:GetAttribute("ViewportCloned"..creation_time) then
			frame_items[#frame_items + 1] = loaded_items[part:GetAttribute("ViewportCloned"..creation_time)]
		else
			local model = part:FindFirstAncestorWhichIsA("Model")
			if model then
				if model:FindFirstChildWhichIsA("Humanoid") ~= nil then
					if model:GetAttribute("ViewportCloned"..creation_time) then
						update_cframes(humanoids[model:GetAttribute("ViewportCloned"..creation_time)])
						frame_humanoids[#frame_humanoids + 1] = humanoids[model:GetAttribute("ViewportCloned"..creation_time)]
					else
						model:SetAttribute("ViewportCloned"..creation_time,#humanoids+1)
						humanoids[#humanoids+1] = {clone = model:Clone(), original = model}
						humanoids[#humanoids].clone.Parent = viewport
						frame_humanoids[#frame_humanoids + 1] = humanoids[#humanoids]
					end
					continue
				end
			end
			part:SetAttribute("ViewportCloned"..creation_time,#loaded_items+1)
			loaded_items[#loaded_items+1] = {clone = clean(part:Clone()), original = part}
			loaded_items[#loaded_items].clone.Parent = viewport
			frame_items[#frame_items + 1] = loaded_items[#loaded_items]
		end
		if #frame_items > 0 then
			frame_items[#frame_items].clone.CFrame = frame_items[#frame_items].original.CFrame
		end
	end
end



-- Mainloop

viewport.CurrentCamera = Instance.new("Camera", viewport)
viewport.CurrentCamera.FieldOfView = fov

while true do
	local start = tick()
	frame_items = {}
	frame_humanoids = {}
	viewport.CurrentCamera.CFrame = camera.CFrame + camera.CFrame.LookVector * offset
	castBox()
	wait()
	local frame_time = (tick()-start)
	if frame_time > 1/fps then
		script.Parent.TextLabel.Text = "FPS: "..math.round((1/frame_time))
	else
		script.Parent.TextLabel.Text = "FPS: "..fps
		wait((1/fps)-frame_time)
	end
end
3 Likes