Playing Sequences
Sequences always evaluate on the client. There are two ways to start one:
- Local playback, started by a client for itself with
Flow:Play. Nobody else sees it. Use it for UI transitions, a local camera move, or anything the server doesn't need to know about. - Synced playback, started by the server with
Flow:PlayForAllorFlow:PlayFor. Every client involved evaluates it from the same shared start time, including players who join while it is running.
Playing locally
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Flow = require(ReplicatedStorage.Flow)
local playback = Flow:Play("DoorOpen", {
Bindings = { Door = door }, -- fills the sequence's "Door" binding
Parameters = { OpenAngle = 90 }, -- overrides a sequence parameter for this play
Rate = 1.5, -- plays at one and a half times speed
})
playback.Completed:Wait()
Play takes a sequence name, the sequence's Configuration, or a sequence loaded with
Flow:GetSequence. It returns a Playback, or nil with a warning if
the sequence doesn't exist. Calling it on the server warns and does nothing; use PlayForAll there.
Options
| Option | Default | Meaning |
|---|---|---|
Bindings | none | Instances for the sequence's public bindings, by name. See Bindings and retargeting |
Parameters | none | Values for the sequence's parameters, by name |
Rate | 1 | Speed multiplier |
StartTime | 0 | Sequence time, in seconds, to start from |
Root | the sequence's root | A different root instance for this play, which retargets the whole sequence |
Loop | false | Restart from the beginning instead of completing |
Controlling a playback
playback:Pause()
playback:Seek(2.5) -- jumps to 2.5 seconds
playback:SetRate(0.5)
playback:Resume()
playback:Stop() -- ends early and applies each section's restore policy
| Member | Meaning |
|---|---|
Time | Current sequence time in seconds |
Length | The sequence's length |
Rate | Current speed multiplier |
IsPlaying | False while paused, and once it ends |
IsActive | False once it has completed or stopped |
Completed | Fires when it reaches the end on its own |
Stopped | Fires when Stop ends it early |
A seek that jumps over a section skips it entirely, and one that lands inside a section starts it at that point, so jumping to the middle of a sequence puts everything where it would be at that moment.
When it starts
Play returns right away, but the first frame can wait a moment: a sequence that uses a
subject preset builds its subjects first, which can need a
web request. The clock starts once they are ready, so nothing is dropped into a sequence already underway.
Playing for everyone
From a server Script:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Flow = require(ReplicatedStorage.Flow)
local track = Flow:PlayForAll("Intro", { Bindings = { Door = workspace.Castle.FrontDoor } })
track.Completed:Wait()
PlayFor(player, name, options) does the same for one player. Both return a
SequenceTrack, which has the same Pause, Resume, Seek, SetRate, and Stop
methods as a playback, plus GetTime().
Remember that each client must also require the executor, and that a client
evaluates the sequence against its own copy of the world. Any instance you pass in Bindings has to be one
that has replicated to the clients.
How sync works
The server never animates anything. It sends each client a small record: which sequence, the server time it
started at, its rate, whether it is paused, and its bindings and parameters. Each client works out the current
time from workspace:GetServerTimeNow() and evaluates the sequence there. That is why every player sees the
same moment, and why a late joiner lands at the right point instead of at the start.
Control lives with the server. Every call on the SequenceTrack updates the record and sends it out again.
The playbacks clients create for a synced sequence are read-only; their control methods warn and do nothing,
since a client that paused itself would drift away from everyone else.
Root and Loop apply to local playback only; synced playback uses the sequence's own root and plays once.
Held state for late joiners
A synced sequence with Hold sections leaves something behind, such as a door left open. The server remembers every synced sequence that ended with held sections, in order, and a player who joins later has each one applied at its final state, so they see the same world as everyone who watched it play.
That covers what is drawn on screen. Game state that matters, such as whether the door's collision is off, still belongs to your scripts.
Managing playbacks
| Function | Where | Returns |
|---|---|---|
Flow:GetActivePlaybacks() | Client | Every running playback on this client, local and synced |
Flow:GetActiveTracks() | Server | Every synced playback this server started |
Flow:StopAll() | Either | Stops every local playback and every server track |
Flow:GetSequenceNames() | Either | The names of every sequence in the place |
Running sequences side by side
Any number of sequences can play at once. When two write the same property, the one that started later takes it, and the output warns that both are targeting the same instance. When they end the property goes back to its original value, not to whatever the other sequence was showing at the time. Sequences don't need to know about each other for that to work.