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?
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.
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.
Should I use a NPC to do the camera animation? Or the Camera instance itself? If the NPC, how do I do the FOV?
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.
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
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.