I made a cutscene system but I feel like I can do it more efficiently

So I have a cutscene system in my game. Unfortunately, I feel like there is a more efficient way to do this. This is why I am here to ask you people for help. I am kinda new to cutscene scripting by the way.

Here is my cutscene system if you’re wondering.

-- CUTSCENE
local AnimFolder = script.Parent.CutsceneAnimations -- Where I store the cutscene animations
-- NOTE: The Animfolder["3"] means its the 3rd cutscene animations in the episode.
local maintrack = game.Workspace.GameCamera.Humanoid.Animator:LoadAnimation(AnimFolder.Parent.CameraAnimations["3"])
local track2 = script.Parent.npc_1.Humanoid:LoadAnimation(AnimFolder["3"].npc_1)
local track3 = script.Parent.npc_2.Humanoid:LoadAnimation(AnimFolder["3"].npc_2)
local track4 = script.Parent.npc_playermodel.Humanoid:LoadAnimation(AnimFolder["3"].npc_playermodel)

local animationtracks = {maintrack,track2,track3,track4}
game.ReplicatedStorage.GameVariables.CutsceneCameraLock.Value = true -- When the variable is true, the localscript forces the player to set the camera position to a NPC in the workspace.
task.wait(1)
game.ReplicatedStorage.Events.ScreenFade:FireAllClients("FadeOut",2)
for i,v in pairs(animationtracks) do
	v:Play(0,1,1)
end

maintrack:GetMarkerReachedSignal("sceneloop"):Connect(function(num)
	if num == "0" then -- The first portion of animation. It loops.
		for _,v in pairs(animationtracks) do
			v.TimePosition = 0
		end
	elseif num == "1" then -- This case, all animation tracks pause.
		maintrack:AdjustSpeed(0)
		for _,v in pairs(animationtracks) do
			v:AdjustSpeed(0)
			v.TimePosition = 362/60 -- frame divided by frame per second
		end
	end
end)


GameModule.SendMessagetoPlayers("All","NPC 1","Hi")
GameModule.SendMessagetoPlayers("All","NPC 2","Hello")
GameModule.SendMessagetoPlayers("All","NPC 1","How are you doing")
for _,v in pairs(animationtracks) do -- The time position switches to 1 frame after the sceneloop animation event "0"
	v.TimePosition = 301/60
end
task.wait(.2)
GameModule.SendMessagetoPlayers("All","Player","I NEED HELP!")
GameModule.SendMessagetoPlayers("All","Player","WE ARE BEING ATTACKED!")
game.ReplicatedStorage.Events.ScreenFade:FireAllClients("FadeIn",2) -- Where the animation ends
task.wait(2)
for i,v in pairs(animationtracks) do
	v:Stop(0)
end
game.ReplicatedStorage.GameVariables.CutsceneCameraLock.Value = false

So what are the problems?

  1. I feel like I am doing this wrong. Not that it doesn’t work, but I feel there is a more “proper” way of doing something like this. The script and system works perfectly fine.
  2. Sometimes the animations aren’t timed correctly. For example, if I make a cutscene where NPC1 punches NPC2, NPC2 sometimes takes impact BEFORE NPC1 touches NPC2. Which looks very bad.
  3. Should I use a NPC to do the camera animation? Or the Camera instance itself? If the NPC, how do I do the FOV?
2 Likes

What’s up! Your script looks good, but I think you need to use a trigger or marker for the animation so that the NPC won’t take damage before punching. This could be happening because of the different clients, and everyone sees different replication or plays animations at different times.

By the way, maybe you should use delta time instead of a predefined FPS because some devices might interpret it differently. For example, a device with 240 fps and a device with 60 fps. I think the device with 240 fps will see it quicker.

Regarding the camera animation: Use the camera instance because it already has everything you need. Just make sure you’ve set the camera mode to scriptable, and that it happens before any camera manipulation. If you are playing the animation on each client separately and not replicating it from the server, use Workspace.CurrentCamera.

TweenService or other Tween libraries might help you smoothly edit camera values.

Let me know if you need anything else. I’m currently improving my skills through a video course, so I’m trying to refresh my knowledge. I’m not an expert either. :grinning:

3 Likes

Thanks! So I start using the camera instance itself for animation but now I have a new problem. The camera isn’t animating while the NPCs in my cutscene are!

Animations.Scene1.Player.AnimationId = game.KeyframeSequenceProvider:RegisterKeyframeSequence(workspace.Anim.AnimSaves.scene1)
Animations.Scene1.Camera.AnimationId = game.KeyframeSequenceProvider:RegisterKeyframeSequence(workspace.Camera.scene1)

local MainAnimation =  workspace.CurrentCamera.AnimationController.Animator:LoadAnimation(Animations.Scene1.Camera)
local Anim2 = Character.Humanoid:LoadAnimation(Animations.Scene1.Player)
local Anims = {MainAnimation,Anim2}
task.wait(3)
game.ReplicatedStorage.GameVariables.CutsceneCameraLock.Value = true
workspace.CurrentCamera.CameraType = Enum.CameraType.Scriptable
for _,Animation in pairs(Anims) do Animation:Play(0,1,0) end
Events.ScreenFade:FireAllClients("FadeOut",3)
task.wait(4)
GameModule:GameDialogue("Player","Where am I?")
GameModule:GameDialogue("Player","I should explore this place...")

The script is above.
I used moon animator to export the rigs. The camera has animationcontroller and animator inside of it. What is the issue?

And no, the Animation:Play(0,1,0) isn’t the issue. The camera position isn’t the same where I animated it compared to the in game camera

What’s up again!

First, I don’t think you need to use an animation controller on the camera; just animate the values directly. When I was working on my first cutscene, **I watched a YouTube video or asked ChatGPT for help. You can ask ChatGPT about this method of animating the camera with Moon Animator. He might explain why there are functions like Inverse(). By the way, it’s a local script.

local camera = workspace.CurrentCamera

local Player = game:GetService("Players").LocalPlayer

local ReplicatedStoarge = game:GetService("ReplicatedStorage")

local TweenService = game:GetService("TweenService")
local AnimationFolder = ReplicatedStoarge:FindFirstChild("folder name")

local fovFolder = AnimationFolder:FindFirstChild("FOV")
local FramesFolder = AnimationFolder:FindFirstChild("Frames")

local character = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart = character:FindFirstChild("HumanoidRootPart")

local DefaultCFrameOffset = AnimationFolder:FindFirstChild("DefaultCFrameOffset").Value


local function playCameraAnimation(AnimationFolder2)
	if AnimationFolder2 then
		AnimationFolder = AnimationFolder2
	end
	
	local FirstKeyFrameCFrame = FramesFolder:FindFirstChild("1").Value
	local PlayerOffset = HumanoidRootPart.CFrame:Inverse() * FirstKeyFrameCFrame
	
	camera.CameraType = Enum.CameraType.Scriptable
	
	for i, v in pairs(FramesFolder:GetChildren()) do
		local targetCFrame
		
		local success, result = pcall(function()
			local KeyFrameCFrame = FramesFolder:FindFirstChild(i).Value
			
			local relativeCFrame = DefaultCFrameOffset:Inverse() * KeyFrameCFrame
			
			targetCFrame = character:FindFirstChild("HumanoidRootPart").CFrame * relativeCFrame
		end)
		
		
		local tween = TweenService:Create(camera, TweenInfo.new(0.01), {CFrame = targetCFrame})
		tween:Play()
		tween.Completed:Wait()
	end
	print("Kk")
	camera.CameraType = Enum.CameraType.Custom
end

game:GetService("ReplicatedStorage").AwakeningCameraEvent.OnClientEvent:Connect(function(AnimationFolder2)
	playCameraAnimation(AnimationFolder2)
end)

This might not be the best example of an animated camera, but basically, if you’re creating an animation, Moon Animator creates a folder containing the camera position, field of view, etc. , with values in them. These value objects have a number as a name. The number indicates when the camera should reach the values of that value object. It’s like a keyframe. So, just create something similar to this cycle and go through the folder with those value objects using for i, v in ipairs().

If your fade function doesn’t work either, let me know. Maybe I can find a way to fix it sometime.

I hope this works. As I mentioned earlier, I’m not an expert, but I’m trying to improve my skills through a video course. Also, I still have school. :school: