CutsceneService - Smooth cutscenes using Bézier curves


Smooth cutscenes using Bézier curves

Repository | Roblox model | Plugin

Hey developers,

TweenService is probably the best choice for tweening between 2 points. However, when you have more than 2 points, you have to do something like this:

This doesn’t look good and is pretty outdated. But there’s a solution: Bézier curves.

CutsceneService is a module which specializes in cutscenes. It is easy to use, fully customizable and has a wide range of features.

Getting started

Note: It is highly recommended to use the plugin as it greatly facilitates the work.

Firstly, get the module here and insert into your game via the Toolbox (preferably ReplicatedStorage).

After that we’ll set up a cutscene. Create a folder in Workspace and name it Cutscene1.
Then insert parts (also called points) into it. Name the parts 1 , 2 , 3 , etc. The cutscene starts at 1 and ends at the last point.
Position the parts and make sure they are looking in the right direction.

The more points in a cutscene, the more calculations have to be done - don’t add unnecessarily many!

Now create a LocalScript in StarterGui and require the module:

local CutsceneService = require(game.ReplicatedStorage.CutsceneService)

Use the :Create(folder, duration) function to create a new cutscene.

local CutsceneService = require(game.ReplicatedStorage.CutsceneService)

local cutscene1 = CutsceneService:Create(workspace.Cutscene1, 5)

The cutscene object has 5 functions:

  • Play
  • Pause
  • Resume
  • Cancel
  • Destroy

After adding :Play(), run the game and see how the cutscene looks. You might want to reposition or rotate some parts.

local CutsceneService = require(game.ReplicatedStorage.CutsceneService)

local cutscene1 = CutsceneService:Create(workspace.Cutscene1, 5)

Easing & Special functions

You mostly don’t want just a plain cutscene. There are easing styles, easing directions and special functions which you can add to your cutscene.

When I told you that the Create function only has 2 parameters, I lied. You can actually add as many arguments as you want.

The order of the arguments (after the first two) doesn’t matter.

For easings, you can either use Enums or the name of a specific easing function (look in the child of the CutsceneService module).

CutsceneService:Create(workspace.Cutscene1, 5, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut)

CutsceneService:Create(workspace.Cutscene1, 5, "InOutSine")

Unlike TweenService, CutsceneService has the EasingDirection OutIn (only callable through strings).

Special functions are called with a string. You can also use CutsceneService.Enum to quickly autocomplete the name. In this example we will use CurrentCameraPoint.

CutsceneService:Create(workspace.Cutscene1, 5, "CurrentCameraPoint")

CutsceneService:Create(workspace.Cutscene1, 5, CutsceneService.Enum.CurrentCameraPoint)

Here is an uncopylocked example place.


Queues let you play multiple cutscenes successively.
After creating some cutscenes, you can create a queue like this:

local queue1 = CutsceneService:CreateQueue(cutscene1, cutscene2, cutscene3)

Queues have the same 5 functions like cutscenes:

  • Play
  • Pause
  • Resume
  • Cancel
  • Destroy

It also has a property called CurrentCutscene, which represents the cutscene it currently plays.

Finally, it is possible to loop a single cutscene like this:

cutscene1.Next = cutscene1

You can loop a queue like this:

local queue = CutsceneService:CreateQueue(cutscene1, cutscene2, cutscene3)
cutscene3.Next = cutscene1

The Next property represents a cutscene which will be automatically played after this one. Please note that this property will be overwritten/removed when the cutscene is in a queue and a function of the queue (like Play) is called.

Happy creating! :grinning_face_with_smiling_eyes:

Advanced tips & tricks

This section requires full understanding of the other guides.

An advantage of CutsceneService is that many useful things, that are normally internal, are available as API here. However I won’t document most of them, that’s why I will present some of them in this section.

PreviousCameraType and PreviousCoreGuis

These are properties that get created when a cutscene or queue is played. They contain the CameraType and CoreGui settings from before the playback, since they need to be restored after the cutscene.

Hence you can for example make it so the camera doesn’t go back to the character immediately:

cutscene1.PreviousCameraType = Enum.CameraType.Scriptable
for k in cutscene1.PreviousCoreGuis do
	cutscene1.PreviousCoreGuis[k] = false
	task.wait(3) --wait 3 seconds, then change the settings back to normal
	workspace.CurrentCamera.CameraType = Enum.CameraType.Custom
	game.StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.All, true)

Changing the points during playback

It is possible to change the points while the cutscene is playing. When you create a cutscene, in some cases you might not know the CFrame of a point that will be in the cutscene. You only know it the moment the cutscene is played, for example a point that is 50 studs above the character.
A possible solution is to create a new cutscene every time and play it immediately, but there’s a better way:

Cutscenes have the property Points, which are simply the points you provided and during playback the property PointsCopy, which is a temporary copy of Points that was modified by special functions (for example a point was added). You can change these properties whenever you want, but it is recommended to change PointsCopy only right after calling Play, otherwise the player might see a jump.

This cutscene can be found in the sample place:

local cutscene4 = CutsceneService:Create(
	{}, 7, "InOutSine", --an empty table is passed as points
	CutsceneService.Enum.CurrentCameraPoint, 1,

--this creates 2 points above the character and inserts them into the cutscene
	local p1 = CFrame.lookAt(char.HumanoidRootPart.Position +, 100, 0), char.HumanoidRootPart.Position)
	local p2 = CFrame.lookAt(char.HumanoidRootPart.Position +, 15, 0), char.HumanoidRootPart.Position)
	table.insert(cutscene4.PointsCopy, 2, p1)
	table.insert(cutscene4.PointsCopy, 3, p2)

To be continued.


The main module, obtained with require().


Enum : {[string]: string}
Serves the purpose to quickly autocomplete special function names.

Settings : {[string]: any}
Contains settings for the CutsceneService module.


YieldPauseArgument : boolean
When using the waitTime argument of the Pause function, this yields the function.


Create(points : Instance | {CFrame}, duration : number, ...): Cutscene
Creates and returns the cutscene object. points can be an instance with the points as it’s children or an array with CFrames. EasingStyles, EasingDirections and special functions can be added after duration.

CreateQueue(...): Queue
Returns a queue of the cutscenes provided.


The cutscene object returned by Create.


PlaybackState : Enum.PlaybackState
Describes the current stage of the cutscene. Possible values

Progress : number
Describes the current progress of the cutscene which starts at 0 and ends at 1. To get a percentage, multiply it by 100.


Starts the cutscene. If a cutscene is already playing on the client, it is not possible to start it.

Pause(waitTime : number?)
Halts the playback of the cutscene. If a waitTime is given, it will automatically resume after it.

Resumes the cutscene. It must first be played and then paused to be able to resume it.

Cancels the playback of the cutscene.


Completed(playbackState : Enum.PlaybackState)
Fires when the cutscene finishes playing. This will happen either when the cutscene naturally finishes playing, or if it is stopped with Cancel. A function connected to this event will be passed the PlaybackState to give indication about why the tween ended (Can be Completed or Cancelled).


The queue object returned by CreateQueue.


PlaybackState : Enum.PlaybackState
refer to Cutscene

CurrentCutscene : Cutscene?
Represents the cutscene which is currently played. When the queue isn’t playing, it is nil.


refer to Cutscene

Pause(waitTime : number?)
refer to Cutscene

refer to Cutscene

refer to Cutscene


Completed(playbackState : Enum.PlaybackState)
refer to Cutscene

Special functions

Functions that enhance your cutscene. They are called at Cutscene:Play or right after the cutscene finished.
Arguments for special functions can be passed right after the string, like this: (..., "DefaultCameraPoint", 3, "DisableControls"). DefaultCameraPoint would be passed the argument 3. Everything except strings and Enums will count as an argument for a special function.


Parameters: customCamera : Camera
Made for ViewportFrames, changes the camera whose CFrame is tweened.


Disables the character controls so you can’t move.


Parameters: stopAnimations : boolean?
Anchors the HumanoidRootPart. The default for stopAnimations is true.


Parameters: position : number?
Sets the current camera CFrame as a point. position is the number where the point gets inserted: 1 would set it as the first point. The default is #points + 1.


Parameters: position : number?, useCurrentZoomDistance : boolean?
Calculates a CFrame which is behind the characters back and sets it as a point. The default for useCurrentZoomDistance is true, as you would have to change the CameraZoomDistance otherwise and there is only a workaround for that.


Version 1.4.1

  • Removed special function YieldAfterCutscene
  • Renamed CutsceneService.Functions to CutsceneService.Enum
  • Removed the resetCamera parameter of Cutscene:Cancel and Queue:Cancel
  • Small optimizations

Please reply with feedback, suggestions, bugs, improvements, etc. I hope it comes in handy! :slightly_smiling_face:


nice module, would you mind posting the source & and show an example cutscene in a video?


Like said before, i would love to use this, but is there anyway to post on a youtube video? Just for a better reference? Not saying your explanation isnt good or anything…


I made an example cutscene here, it’s uncopylocked.


Great resources! Thanks!! I can’t wait to use them!


The place is on private, cant really seem to do anything here.

1 Like

Click on the 3 dots and then “Edit” to open it in Studio.


Ah i see. Thanks for correcting me.

1 Like

Thanks for the innovative asset! :innocent:

1 Like

Added a new function and another parameter!

1 Like

Do you mind explaining to me exactly how it works? I do building and work alone, but i need a cutscene system like yours.

1 Like

I learned how to use Bézier curves in this article.


The module is now fully released! :smile:


I just released a complete overhaul of the module! Getting the new version is definitely worth it, but you have to rewrite parts of your code.


I love the module so much. Thank you for the update!


Thanks for your feedback! :grinning_face_with_smiling_eyes:


Quick Suggestion: If you request to :Play() a cutscene, while another one is playing, instead of erroring, add it to a “Queue” of cutscenes to play. Then, when one cutscene ends, if there’s another one in the queue, play that one.


Also there’s a bug where if you’re triggering a cutscene in first person, the character model isn’t visible during the cutscene

1 Like

Unfortunately I can’t fix this with a single change to the character because the PlayerModule is constantly overriding this.
Thanks for the report though.

1 Like

I’ll consider this for the next update! :+1:

1 Like