I have a similar system. I used a module for each cutscene and put them into folders in replicatedstorage. Each module essentially returns a function. When a player loads in, the data containing the user’s current cutscene is fetched, and then the function is called.
An example of one of my cutscenes:
local TS = game:GetService("TweenService")
local movement = require(game.ReplicatedStorage.modules.movement)
local dialogue_handler = require(game.ReplicatedStorage.modules.dialogue)
dialogue_handler = dialogue_handler.new()
local spr = require(game.ReplicatedStorage.modules.spr)
local lighting = game:GetService("Lighting")
return function()
game:GetService("UserInputService").MouseIconEnabled = false
lighting.TimeOfDay = "19:30:00"
local atmosphere = Instance.new("Atmosphere", lighting)
local bloom = Instance.new("BloomEffect", lighting)
local blur = Instance.new("BlurEffect", lighting)
blur.Size = 4
bloom.Intensity = 5
bloom.Size = 50
bloom.Threshold = 1
atmosphere.Density = 0.5
atmosphere.Offset = 0
movement:ToggleMovement(true)
local char = game.Players.LocalPlayer.Character
local scene_characters = game.ReplicatedStorage.cutscene_resources.characters
char:PivotTo(workspace.cutscene.OpeningScene.Hideout.spawnParts.player.CFrame * CFrame.new(0, 1, 0))
local commander = scene_characters.Commander:Clone()
commander.Parent = workspace.cutscene
commander:PivotTo(workspace.cutscene.OpeningScene.Hideout.spawnParts.commander.CFrame)
local animation = commander['M4A1'].animations.idle
animation = commander:FindFirstChild("Humanoid"):WaitForChild("Animator"):LoadAnimation(animation)
animation:Play(0)
local cam = workspace.CurrentCamera
cam.CameraType = Enum.CameraType.Scriptable
cam.CFrame = workspace.cutscene.OpeningScene.Hideout.cameraParts.camPart1.CFrame
dialogue_handler:set({speaker = "Commander", text = char.Name..", over here!"})
dialogue_handler:write()
local callback = function()
coroutine.wrap(function()
task.wait(.3)
spr.target(char.PrimaryPart, 1, 1, {CFrame = CFrame.lookAt(char.PrimaryPart.Position, commander.PrimaryPart.Position)})
spr.completed(char, function() wait() end)
end)()
dialogue_handler:clear()
task.wait(1.5)
dialogue_handler.gui.main.Visible = false
end
dialogue_handler:completed(callback)
spr.target(cam, 1, 1.5, {CFrame = workspace.cutscene.OpeningScene.Hideout.cameraParts.camPart2.CFrame})
spr.completed(cam, function() wait() end)
dialogue_handler:set({speaker = "Commander", text = "The Bacons have taken over a city with a group of noob refuges, They're searching the entire city, and our people will be found"})
dialogue_handler:write()
dialogue_handler:completed(function() dialogue_handler:clear() task.wait(.75) dialogue_handler.gui.main.Visible = false end)
dialogue_handler:set({speaker = "Commander", text = "Those refuges contain research on a new weapon that could potentially be used to win the war over the Bacons."})
dialogue_handler:write()
dialogue_handler:completed(function() dialogue_handler:clear() task.wait(.75) dialogue_handler.gui.main.Visible = false end)
dialogue_handler:set({speaker = "Commander", text = "We are going to deploy you as the leader of a small task force with some other recruits, but before recapturing the city we need to do something else."})
dialogue_handler:write()
dialogue_handler:completed(function() dialogue_handler:clear() task.wait(1) dialogue_handler.gui.main.Visible = false end)
spr.target(cam, 1, 1.5, {CFrame = workspace.cutscene.OpeningScene.Hideout.cameraParts.camPart3.CFrame})
print("Move to started!") -- had to do some debugging
char.Humanoid:MoveTo(workspace.cutscene.OpeningScene.Hideout.movementParts.playerOne.Position, workspace.cutscene.OpeningScene.Hideout.movementParts.playerOne)
char.Humanoid.MoveToFinished:Wait()
--[[spr.target(char.PrimaryPart, 1, 1, {CFrame = CFrame.lookAt(char.PrimaryPart.Position, commander.PrimaryPart.Position)})
spr.completed(char.PrimaryPart, function() task.wait(1) end)]]
dialogue_handler:set({speaker = char.Name, text = "What's the mission?"})
dialogue_handler:write()
dialogue_handler:completed(function() dialogue_handler:clear() task.wait(1) dialogue_handler.gui.main.Visible = false end)
spr.target(cam, 1, 1, {CFrame = workspace.cutscene.OpeningScene.Hideout.cameraParts.camPart4.CFrame})
spr.completed(cam, function() task.wait(.5) end)
dialogue_handler:set({speaker = "Commander", text = "It's simple, but the area will be heavily fortified."})
dialogue_handler:write()
dialogue_handler:completed(function() dialogue_handler:clear() task.wait(.75) dialogue_handler.gui.main.Visible = false end)
dialogue_handler:set({speaker = "Commander", text = "There's a Bacon base around 3000 studs from our location. Clear the base, and you'll find some blueprints."})
dialogue_handler:write()
dialogue_handler:completed(function() dialogue_handler:clear() task.wait(.75) dialogue_handler.gui.main.Visible = false end)
dialogue_handler:set({speaker = "Commander", text = "Those are the city's blueprints. They are going to be the key to retreving the city again."})
dialogue_handler:write()
dialogue_handler:completed(function() dialogue_handler:clear() task.wait(.75) dialogue_handler.gui.main.Visible = false end)
dialogue_handler:set({speaker = "Commander", text = "Go retrieve them and come back here and report to me, I'll give you the next steps"})
dialogue_handler:write()
dialogue_handler:completed(function() dialogue_handler:clear() task.wait(.75) dialogue_handler.gui.main.Visible = false end)
coroutine.wrap(function()
spr.target(cam, 1, 1, {CFrame = workspace.cutscene.OpeningScene.Hideout.cameraParts.camPart5.CFrame})
end)()
dialogue_handler:set({speaker = "Commander", text = "There are some weapons over at that table. Suit up and get ready, a helicopter will be here to pick your team up in 10 minutes. Good luck."})
dialogue_handler:write()
dialogue_handler:completed(function() dialogue_handler:clear() task.wait(1) dialogue_handler.gui.main.Visible = false end)
spr.target(cam, 1, 1, {CFrame = char.PrimaryPart.CFrame * CFrame.new(char.Humanoid.CameraOffset)})
spr.completed(cam, function() task.wait(1.5) end)
cam.CameraType = Enum.CameraType.Custom
task.wait(1)
movement:ToggleMovement(false)
return true -- if true isn't returned, the cutscene cut out somewhere or broke
end
Some of the characters and maps stored for the cutscenes:

This is all run through a cutscene controller, which is just a tiny module script (probably not necessary but it does come in handy)
local handler = {}
function handler:Play(name)
local map = game.ReplicatedStorage.cutscene_resources.maps.OpeningScene
local cutscene = game.ReplicatedStorage.cutscenes:FindFirstChild(name)
map = map:Clone()
map.Parent = workspace.cutscene
local finished = require(cutscene)()
if not finished then return end
return true
end
function handler.CleanUp()
for i, v in pairs(workspace.cutscene:GetChildren()) do
v:Destroy()
end
end
return handler
This is just how I handled it, and this is all run locally, as there is no point in running any of this on the server. You could just replicate the same cutscene on all clients if you need to play the cutscene for everybody in a group, whether it be through a loop or whatever
Oh yeah, the dialogue handler is just a module of mine to create a typewriter effect on the gui, nothing important here