Why not use splines that go through the points? just curious
Perfect, this seems to be fixed. Thank you for the quick response!
The reason I chose Bézier curves is because they are optimal for camera paths: They go through the first and last point and generate a smooth curve between them. Furthermore, they are very easy to compute.
Interpolating curves (curves that pass through all points) can be smooth as well, but not necessarily as much as Bézier curves. However an advantage of interpolating curves is that you have more control over the path.
Additionally, Bézier curves are very easy to implement for Roblox’s CFrames due to the built-in Lerp function. This can be complicated for other curves.
The fact that Bézier curves are the default doesn’t mean you can’t use your own. You can simply replace the getCF
function in the source code.
Another thing I’ve come across with looping is that the cutscene will jump from the end point to the start point. Is there a special function for preventing this, so it will smoothly go between the points? I’m currently just putting the start point at the end.
Nope, unfortunately there is not a special function for this. You have to make the first and last point the same.
If you want, you can add this function in the source code which will solve your problem:
function cutscene:PlayLoop()
if module.Playing then error("A cutscene is already playing") end
local points = self.Points
if points[1] ~= points[#points] then
table.insert(points, points[1])
end
self:Play()
self.Next = self
end
Hi! Im trying to cancel a looping queue to play another cutscene but im met with this error, how would I solve it?
ReplicatedStorage.CutsceneService:519: invalid argument #1 to ‘next’ (table expected, got nil)
Here is my code:
local CutsceneService = require(game.ReplicatedStorage.CutsceneService)
local BattleInfo = Arguments[1]
local IdleScene1 = CutsceneService:Create(BattleInfo.Parent:FindFirstChild("IdleScene1"), 5, "InOutQuart")
local IdleScene2 = CutsceneService:Create(BattleInfo.Parent:FindFirstChild("IdleScene2"), 5, "InOutQuart")
local IdleScene3 = CutsceneService:Create(BattleInfo.Parent:FindFirstChild("IdleScene3"), 5, "InOutQuart")
local IdleScene4 = CutsceneService:Create(BattleInfo.Parent:FindFirstChild("IdleScene4"), 5, "InOutQuart")
local IdleScene5 = CutsceneService:Create(BattleInfo.Parent:FindFirstChild("IdleScene5"), 5, "InOutQuart")
local ActionScene1 = CutsceneService:Create(BattleInfo.Parent:FindFirstChild("ActionScene1"), 1, "InOutQuart")
local IdleQueue = CutsceneService:CreateQueue(IdleScene1, IdleScene2, IdleScene3, IdleScene4, IdleScene5)
IdleQueue:Play()
IdleScene5.Next = IdleScene1
State:GetChangedSignal("State"):Connect(function(NewValue)
if NewValue == "Swapping" or NewValue == "Action" then
IdleQueue:Cancel()
ActionScene1:Play()
elseif NewValue == "Idle" then
IdleQueue:Play()
IdleScene5.Next = IdleScene1
end
end)
Everything works great and smooth, but if you have Streaming enabled and the parts for the cutscene are out of the radius it just errors, is there any way of fixing it?
The problem is that the client can’t access the CFrames of the parts. The best solution is probably to put them in ReplicatedStorage so they don’t disappear.
I just published version 1.4.3 which fixes your bug. Sorry for the inconvenience!
Thank you for the quick response
Is it still possible to do things like shaking the camera while the player is in a cutscene?
Yes, shaking the camera during a cutscene worked perfectly for me. CutsceneService uses Enum.RenderPriority.Camera.Value + 1
as render priority, you probably need to use a higher one for the shake then (like Enum.RenderPriority.Camera.Value + 2
).
Do you have an example on how I would implement that?
I used sleitnick’s camera shaker for this:
local CameraShaker = require(game.ReplicatedStorage.CameraShaker)
local explosion = CameraShaker.new(Enum.RenderPriority.Camera.Value + 2, function(cf)
camera.CFrame *= cf
end)
explosion:Start()
cutscene:Play()
task.wait(1)
explosion:Shake(CameraShaker.Presets.Explosion)
Hi,
Great module works well, how would you get the camera to always look at a part while moving?
Thanks
That is not possible with the current API, but you can add your own special function to implement this.
Add this in specialFunctions.Start
:
{"FocusOnPart", function(_, part:BasePart)
assert(part, "FocusOnPart Argument 1 missing or nil")
--change the algorithm to get a point on the curve
getCF = function(points, t)
local copy = {unpack(points)}
local n = #copy
for j = 1, n - 1 do
for k = 1, n - j do
copy[k] = copy[k]:Lerp(copy[k + 1], t)
end
end
return CFrame.lookAt(copy[1].Position, part.Position)
end
end}
Add this in specialFunctions.End
:
{"FocusOnPart", function()
--change function back
getCF = function(points, t)
local copy = {unpack(points)}
local n = #copy
for j = 1, n - 1 do
for k = 1, n - j do
copy[k] = copy[k]:Lerp(copy[k + 1], t)
end
end
return copy[1]
end
end}
Then you can just use it in your script:
local cutscene1 = CutsceneService:Create(
workspace.Cutscene1, 7, "InOutQuart",
"FocusOnPart", workspace.SpawnLocation
)
Posting this here because I was asked about it:
While working with Bézier curves, a common problem people encounter is that the camera doesn’t move at constant speed throughout the cutscene.
This is usually solved with arc length parameterization. I recently wrote about this topic when I published a Bézier curves module: Introduction | Bezier
I will show you how to solve it with the module, we have to edit the source code a bit:
Firstly, insert the Bézier module into the game and add this line in the CutsceneService module to require it:
local Bezier = require(game.ReplicatedStorage.Bezier)
Now find this at line ~288 in the Play
function:
local duration = self.Duration
and move it under this at line ~305:
assert(#self.PointsCopy > 1, "More than one point is required")
Now we are going to add a special function called “EqualSpeed”:
Add this in specialFunctions.Start
as the last entry:
{"EqualSpeed", function(self, speed:number?)
local vector3Points = {}
for _, v in self.PointsCopy do
table.insert(vector3Points, v.Position)
end
local curve = Bezier.new(vector3Points)
curve:UpdateLUT()
getCF = function(points, t)
t = curve:ConvertT(t)
local copy = {unpack(points)}
local n = #copy
for j = 1, n - 1 do
for k = 1, n - j do
copy[k] = copy[k]:Lerp(copy[k + 1], t)
end
end
return copy[1]
end
if speed then
self.Duration = curve.Length / speed
end
end}
Add this in specialFunctions.End
:
{"EqualSpeed", function(self)
getCF = function(points, t)
local copy = {unpack(points)}
local n = #copy
for j = 1, n - 1 do
for k = 1, n - j do
copy[k] = copy[k]:Lerp(copy[k + 1], t)
end
end
return copy[1]
end
end}
Now you can have cutscenes that play with constant speed and optionally you can specify this speed.
Not sure what’s the difference between fixed version but i don’t see any problem with it currently.
Normally, the camera doesn’t move at a constant speed with a linear easing style.
These articles explain it:
“DefaultCameraPoint” goes to the wrong CFrame when the player respawns, this normally works fine but If I change teams and spawn somewhere else and call this function it tweens to the old CFrame.