ViewportFrame Handler - Custom updating objects & humanoids in VPFs

Hey, it’s me, that viewportframe guy! I’ve improved a lot since my last one and wanted to share a far more robust module I just wrote for ViewportFrames!


Features:

  • Handles multiple ViewportFrames

  • Allows picking specific FPS refresh rates for individual objects

  • Ability to remove, pause, hide, or change FPS refresh rates of objects post-creation

  • Support for NPCs/Characters

API:

Possibly most OOP-styled module I’ve done so far, so there’s a lot of API. I’ll do my best to keep this organized.


Module API:

function ViewportHandler.new(Frame)

Creates and returns a VF_Handler object for the given ViewportFrame

Parameters:

  • Frame [ViewportFrame]
    The ViewportFrame for the VF_Handler

Returns:

  • VF_Handler [Custom Object]
    The handler created for the ViewportFrame

This allows you to use the module on multiple ViewportFrames, simply by creating and using multiple VF_Handlers.


function VF_Handler:RenderObject(Object,FPS,Parent)

Creates and returns an Obj_Handler for the given Object

Parameters:

  • Object [BasePart]
    The Object for the Obj_Handler

  • FPS [Optional Number]
    The refresh rate for the object. If left out or set to 0, object will never refresh. (Useful for static objects like map parts)

  • Parent [Optional Instance]
    The Parent for the render object clone. Defaults to the Frame of the VF_Handler. (Used mainly internally, you’ll probably never use it)

Returns:

  • Obj_Handler [Custom Object]
    The handler created for the Object

function VF_Handler:RenderHumanoid(Character,FPS,Parent)

Creates and returns an Hum_Handler for the given Character

Parameters:

  • Character [Model]
    The Model for the Hum_Handler

  • FPS [Optional Number]
    The FPS for the humanoid refresh rate. Defaults to maximum FPS.

  • Parent [Optional Instance]
    The Parent for the render object clone. Defaults to the Frame of the VF_Handler. (Used mainly internally, you’ll probably never use it)

Returns:

  • Hum_Handler [Custom Object]
    The handler created for the Character

function VF_Handler:Destroy()

Destroys the VF_Handler and all Obj_Handlers+Hum_Handlers currently being rendered by it

Parameters:

  • nil

Returns:

  • nil

The following APIs are designed to make mass changes easier. They are the equivalent to calling their respective methods on all the current objects being handled.


function VF_Handler:Refresh()

Refreshes all objects in the handler

Parameters:

  • nil

Returns:

  • nil

function VF_Handler:Pause()

Pauses all objects in the handler

Parameters:

  • nil

Returns:

  • nil

function VF_Handler:Resume()

Resumes all objects in the handler

Parameters:

  • nil

Returns:

  • nil

function VF_Handler:Hide()

Hides all objects in the handler

Parameters:

  • nil

Returns:

  • nil

function VF_Handler:Show()

Shows all objects in the handler

Parameters:

  • nil

Returns:

  • nil

Obj_Handler API:

function Obj_Handler:Destroy()

Destroys the Obj_Handler and the object clone being rendered by it

Parameters:

  • nil

Returns:

  • nil

function Obj_Handler:SetFPS(NewFPS)

Updates the Obj_Handler refresh rate

Parameters:

  • NewFPS [Number]
    The Refresh rate for the Obj_Handler. (Clamped between 0 and 9999)

Returns:

  • nil

function Obj_Handler:Pause()

Stops the Obj_Handler refresher

Parameters:

  • nil

Returns:

  • nil

Freezes the object in the Frame. Behaves similar to :SetFPS(0)


function Obj_Handler:Resume()

Resumes the Obj_Handler refresher

Parameters:

  • nil

Returns:

  • nil

The counterpart to :Pause()


function Obj_Handler:Refresh()

Runs the Obj_Handler refresher once

Parameters:

  • nil

Returns:

  • nil

Updates (once) the object in the Frame. Used for updating a :Pause()ed object without having to :Resume() and quickly :Pause() again


function Obj_Handler:Hide()

Hides the object in the Frame

Parameters:

  • nil

Returns:

  • nil

Can be used to stop showing things when not needed without having to delete and recreate Obj_Handlers.


function Obj_Handler:Show()

Shows the object in the Frame

Parameters:

  • nil

Returns:

  • nil

Undoes :Hide(). If not hidden, the function won’t do anything.


Hum_Handler API:

function Hum_Handler:Destroy()

Destroys the Hum_Handler and the char clone being rendered by it

Parameters:

  • nil

Returns:

  • nil

Yeah, that was convoluted.

Let’s get into some usage examples so you’ll see it’s actually really simple.

local ViewportHandler = require(script.ViewportHandler)

local Frame = script.Parent.ViewportFrame

local VF_Handler = ViewportHandler.new(Frame)

local baseplate_Handler = VF_Handler:RenderObject(workspace.Baseplate)
local part_Handler = VF_Handler:RenderObject(workspace.Part,20)
local char_Handler = VF_Handler:RenderHumanoid(game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait())

wait(4)

--Set refresh rate of part to 10 (previously 20)
part_Handler:SetFPS(10)

wait(3)

--Set refresh rate of part to 60 (previously 10)
part_Handler:SetFPS(60)

wait(5)

--Remove baseplate from ViewportFrame
baseplate_Handler:Destroy()

wait(2)

--Remove character from ViewportFrame
char_Handler:Destroy()

wait(3)

--End the entire handler (removes Part and disconnects render loop)
VF_Handler:Destroy()

Module File:

This is the first prototype of this that I actually got working, so please reply below with all forms of feedback, suggestions, and bug reports!


Future Updates:

I do hope to add to this. Here’s what I’ve got planned.

  • Additional features for Hum_Handlers
    • :SetFPS(), :Pause(), that kind of thing.

I also plan to optimize it, but I’m putting that off until the new VM because idk what’s fast anymore.

I’ll also create a few examples (applicable usage, not just basic API testing) and post them in the replies.


Notes:

Objects will refresh visual properties (Color, Material, etc) instantly regardless of FPS rate. For these, it’s faster and better to hook the .Changed event than it is to poll for changes at the set rate.

You can reach into the various custom objects and toy with their properties. You’ll break them, so you’re best just sticking to the given APIs. Regardless, here’s the internals of each one.

Object Internals

VF_Handler:

  • HandlerID
  • Frame
  • ObjectsRenderQueue

Obj_Handler:

  • ObjectID
  • Object
  • ObjectClone
  • WaitTime
  • LastUpdate
  • Enabled
  • Showing
  • Running
  • Handler

Hum_Handler:

  • ObjHandlers



Enjoying my work? I love to create and share with the community, for free.

If you’d like to help fund my work, consider sponsoring me on GitHub/Patreon or donating on BuyMeACoffee/PayPal!

214 Likes

Example:

The most common use for my camera system was camera systems for concerts.

With a few minutes, 20 lines of code, and a free model DJ set, this handler let me set up a simple camera system.

DJ_Example

Code:

local ViewportHandler = require(script.ViewportHandler)

local Frame = script.Parent.ViewportFrame

wait(2)
local c = Instance.new("Camera")
 c.CFrame = workspace.CamPart.CFrame
Frame.CurrentCamera = c

local VF_Handler = ViewportHandler.new(Frame)

local baseplate_Handler = VF_Handler:RenderObject(workspace.Baseplate)
local part_Handler = VF_Handler:RenderObject(workspace.Part,20)
local char_Handler = VF_Handler:RenderHumanoid(game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait())

for i,d in pairs(workspace.DJ:GetDescendants()) do
	if d:IsA("BasePart") then
		VF_Handler:RenderObject(d)
	end
end
69 Likes

Finally got around to recreating my camera system with this module. Anyone using the old camera system should switch!
This demo should help people understand how to implement this module.

16 Likes

This is really nice, and effective when needed.

5 Likes

Excellent job on your ViewportFrame scripts! It’s not that easy to script, so I appreciate you made this. Really nice work.

2 Likes

Made a cool flashbang effect using this.

Does the whole “burns the image to your eyes” thing.

Creates all the object handlers with an FPS of 0, and then calls the ViewportHandler:Refresh() when it wants to burn the image.

44 Likes

Update!

Added the Object APIs into the Handler APIs, allowing you to handle all objects at once without having to store all your Obj_Handlers and loop through each one to call their function.

Could you possibly open-source this? :o

1 Like

Using this module for it is overkill and actually wastes CPU.

I’ll make a Screenshot module or something that’s specifically designed for this, and open source that.
I’ll fix this module to work better in these cases.

1 Like

Made a pretty awesome change to the module.

Although still perhaps overkill, it no longer wastes CPU.


The issue:

The reason it was wasting CPU was very simple.

Every frame we iterate over every part and check if it needs to be updated. There are no parts that were active (aka everything was 0 FPS and being refreshed manually when burning the image), so we always iterated through them all and did nothing but update some numbers in them.


This was my initial change in an attempt to solve the issue. I've removed this, for reasons discussed in the end of this dropdown.

I made a simple(ish) change.

function ViewportHandler:_ValidateRefresher()

Anytime you make a change to the handler or an object within the handler, this gets called.

This function goes through all the objects. If there’s an active object, we make sure we have our binding to renderstep. If there are no active objects (such as our flashbang case), we remove that binding.

This way, when no objects are active, there’s no iteration happening.


Why I removed this:

There’s still a downside: if you have even one object active, it iterates through all the inactive ones too.
My final solution fixed this, and also reduces the cost of the module since it doesn’t need to call :_ValidateRefresher() all the time.

The solution:

Instead of iterating over every part, we only iterate over the active ones!

We now have 2 dictionaries: AllObjects and ObjectsRenderQueue.
When an object is inactive, it’s removed from the render queue, and added back in when made active again.

Our render logic only iterates through the render queue, which is empty in the flashbang case.

This also makes our render logic faster in it of itself, since it no longer has to check if the object is active. If it’s in that queue, we know it is!


This is a huge performance gain.

The previous version would be impacted by static objects. In our flashbang example, everything was static. But it matters in other uses too! If you have a large static map, you’d have rendered them at 0 FPS too! Now that won’t crush your performance!

2 Likes

Small change.

No longer uses BindToRenderStep. Connects to .Heartbeat instead.

Doesn’t affect much, but it allows it to run in parallel to the frame drawing so it shouldn’t drastically affect your game’s FPS.

2 Likes

Is that cheaper to run on the server?

ViewportFrames and frame rendering are client processes.

4 Likes

Is there a way you could provide a coding example of how the flashbang or “burn” effect was made? I tried doing it myself, but I can’t seem to wrap my head around it :^(

1 Like

So from this brief explanation, I’m copying the entire world? Ok

You should only really copy what you need to. You can see the Security Camera example, or open a linked topic if you require more help.

1 Like

What if I would make live interactive concerts with those cameras :thinking::thinking::thinking::thinking:

2 Likes

Sorry to bother you kind sir but I have a couple questions:

  1. Heartbeat vs Stepped or .Changed
  2. if RunService then would it be best to have 1 Heartbeat or Stepped connection managing all the new viewports or 1 per viewport

You’re making history my guy, keep up the great effort! I wouldn’t look for guidance for Viewport Frame’s from anyone else other than you.

3 Likes