VPF Replay Module

ViewportFrame Replay Module

by boatbomber


I have to start with a disclaimer: this is memory and CPU intensive. By its very nature, it’s bound to use a solid amount of memory and CPU, but I’ll admit I didn’t even try to compress or optimize that heavily. I got kinda burned out on this project, so I’ve gotten it to a functional state and I’m releasing it so I can move on. Coronavirus quarantine has taken my motivation away, sorry.

This module is used to record and replay moments of your game inside a ViewportFrame. You can tell it what objects to record, such as the players and ball, and what objects to display statically, such as the map and props. It’s a pretty powerful module that extends a lot of functionality to the user, so you can create entire viewing systems with all the features of a standard player of an MP4.

The module stores frame deltas and creates interpolated frames, so even if your game lag or frame drops during recording, it’ll be played back perfectly smooth and timed correctly. This also means that slow motion is still buttery smooth.
You can record at 10 FPS but it’ll always playback 60 FPS (or whatever you’re getting) and just create the assumed frames in between.

This module has not been tested that extensively, so let me know if you find any issues.


(This demo uses the “Follow” camera type)

Here’s the place uncopylocked. A lot of the demo is messy and bad practice, but I wanted to put something together quickly so you can see what it’s capable of. It’s not a real soccer game, so don’t try to take that aspect of it!

API Documentation:

[function] Replay.new(Settings)
	Returns a new ReplayObject		
    @param [table] Settings
		[number] Settings.FPS
			The FPS to record at
			defaults to 20
		[string] Settings.CameraType
			The type of camera for the Replay object playback
			("Recorded", "Track", "Follow", or "Custom")
			defaults to "Recorded"
		[Vector3] Settings.CameraPosition
			Where the camera should sit during playback
			Only relevant when set to "Track"
		[Instance] Settings.CameraSubject
			Not relevant to "Recorded"

[Instance] ReplayObject.VPF
	The ViewportFrame for you to parent to your desired GUI

[Instance] ReplayObject.Camera
	The Camera (use only if CameraType is set to "Custom")
[function] ReplayObject:Register(Object, IgnoreDescendants)
	Adds Object	to be recorded for playback	
    @param [Instance] Object
		The object to register for recording
	@param [Boolean] IgnoreDescendants
		Whether or not it should recursively register the entire object (useful for models and folders)
		defaults to false
[function] ReplayObject:RegisterStatic(Object, IgnoreDescendants)
	Adds Object	to be displayed during playback without moving or recording (Useful for map and props)	
	@param [Instance] Object
		The object to register for static display
	@param [Boolean] IgnoreDescendants
		Whether or not it should recursively register the entire object (useful for models and folders)
		defaults to false
[function] ReplayObject:StartRecording(MaxRecordingTime)
	Begins recording all registered objects	
	@param [number] MaxRecordingTime
		How long the recording can be before automatically stopping
		defaults to 30
[function] ReplayObject:StopRecording()
	Stops the in-progress recording	, and prepares the playback functions
[function] ReplayObject:ClearRecording()
	Clears the stored recording to allow another to be recorded without needing a new ReplayObject and registering everything again
[function] ReplayObject:Destroy()
	Empties the ReplayObject and associated objects for GC
[function] ReplayObject:Play(PlaySpeed,StartTime,Override)
	Plays the stored recording in the ViewportFrame
	@param [number] PlaySpeed
		The time multiplier (useful for slow motion replays or sped up recaps)
		defaults to 1
	@param [number] StartTime
		The time at which playback should begin
		defaults to 0
	@param [Boolean] Override
		Wether or not this playback should override any in-progress playback
		defaults to false
[function] ReplayObject:Stop()
	Halts any in-progress playback
[function] ReplayObject:GoToTime(Time)
	Displays the state in the stored recording in the ViewportFrame at the given Time
	@param [number] Time
		What time in the recording to render
[function] ReplayObject:GoToPercent(Percent)
	Displays the state in the stored recording in the ViewportFrame at the given Percent
	@param [number] Percent
		What percent of the way through the recording to render
[function] ReplayObject:GoToFrame(Frame)
	Displays the state in the stored recording in the ViewportFrame at the given Frame
	@param [number] Percent
		What frame of the recording to render

[event] ReplayObject.RecordingStarted
	Fires when a recording starts
[event] ReplayObject.RecordingStopped
	Fires when a recording stops
[event] ReplayObject.FrameChanged
	Fires when the rendered frame of the recording changes
	@arg [number] Frame
		What frame is now being rendered
	@arg [number] Time
		What time is now being rendered
	@arg [number] Percent
		What percent is now being rendered
[property] ReplayObject.Playing
	Read only boolean of the playing state

[property] ReplayObject.Recording
	Read only boolean of the recording state

[property] ReplayObject.Recorded
	Read only boolean whether there is a stored recording
[property] ReplayObject.FrameCount
	Read only number of how many frames are in the stored recording
[property] ReplayObject.RecordingTime
	Read only number of how long the stored recording is (in seconds)


Final notes

It doesn’t support humanoid clothing wrapping because that’s a pain in the neck to deal with.
Update 1.1 has added humanoid support. Read here: VPF Replay Module

I’m considering a ReplayObject:Serialize() and ReplayModule.fromSerial(SerializedReplay) so that replays can be saved, but that’s a lot of work and compression so maybe later.

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

I make loads of free and open source scripts and modules for you all to use in your projects!
You can find a lot of them listed for you in my portfolio. Enjoy!

If you’d like to help fund my work, check out my Patreon or PayPal!


Thank you for this! I can now finally make a “final kill cam” system!


This is super cool! Now soccer games, FPS games, and many more games can show replays of point sscored!

Thank you so much for this!


Possibly with this module, someone could turn this into a live spectate feature that’s shared across all servers. Very good work of yours.


Ok, only two thing: * Sigh… * and * Hurray! *. I sigh as i was working on this, but hurray as this was to hard for me. I tried with a different method, i think, but now i can learn from my errors! Reall Thanks @boatbomber for this, i only not understand one thing: What is a delta?


The Greek uppercase letter delta (Δ) is the standard mathematical symbol to represent change in some quantity. In this case, it’s the change in time during each stored frame.


This is amazing. 2-3 years ago I never would have thought this is where Roblox would be at.


Very good work! Is it possible to add replay playback into Workspace instead of Viewport Frame?


You could modify it to do that easily enough, if you know what you’re doing.


I’m getting Call of Duty killcam vibes here.

Edit: I’m going to try and make a COD killcam system and I’ll share the end result here!

1 Like



  • Character auto-detection & handling with clothing support
  • Asynchronous registering
  • Update log and version number in the module’s top comment section

As requested by @TAYFUN7, the module now automatically detects and handles Characters (any model with a Humanoid inside) so that all clothing (Shirts, Pants, and Tees) are all rendered in the VPF.

It detects Characters even as children of the original register object, so doing :Register(workspace) will find and render all characters properly. (Although that’s awful practice and you should hook .CharacterAdded to the Register function instead)

The humanoid in the VPF is stateless, so it shouldn’t have any significant impact on performance. It only exists as a way to wrap the clothing textures to the character.

Additionally, the :Register() and :RegisterStatic() functions are now run asynchronously using coroutines, because the character handling can yield.

This update should hopefully make this module far more useful to many people who felt that clothing was vital.


I been watching you for a while now and all your work is AMAZING. Really, really good, I don’t know how you learned to do this things, but thanks god you give us something like this on open-source! Really, really good.

Keep the hard work, totally I’ll take it a look for it to learn something! :smiley:


I made a fork of this that replays in the workspace for people who don’t want the barebones lighting inside ViewportFrames. You’ll have to handle moving the players away and doing the camera work.


Note: This version doesn’t have a :RegisterStatic() function, because those static objects are still in the workspace so why bother adding them into the replay.


How about an option to save the replay (in a datastore or such) so you can pull it anytime you want , example as for anti cheats we could use it to record the users and make it save to a database so we can look back at the replay at anytime to check what happened.

1 Like

I’ve already thought about it, but sadly it’s not possible.

DataStores cannot save Instances, so it would have to be serialized. MeshParts cannot be written to at runtime, so it cannot be deserialized.


@VitroxVox this isn’t entirely impossible but very difficult to achieve with current data store limits.

  • You can only have 250k characters of strings
  • Serialize into valid JSON format

it’s possible but very complex and difficult to achieve with current limitations.

the problem with MeshParts are that they have to be in game with the exact same name.


Serializing isn’t the part that makes it impossible, it’s the fact that you cannot turn that data back into parts when you want to load the replay since you cannot set MeshParts. Serializing is relatively simple, but the deserializing stage is made impossible by Roblox permission levels.


@boatbomber, I am aware. That is what makes things very difficult and complex.

I’d like to argue that it is indeed possible to achieve this by using SpecialMesh instead.

However it will not have near perfect accuracy but still possible with minor compromises.

I’m sure that with some creativity we can do almost anything with Studio.

Here’s a real case:

This game Has achieved this!


The game doesn’t seem to work anymore, nothing replays anymore. Is this a bug issue or am I missing something?


I haven’t touched it in ages, but I guess something broke in a Roblox update? I’ll look at this when I have a chance