--!optimize 2 --!strict local RunService = game:GetService("RunService") type TweenSequenceItem = number | Tween | { Tween } | () -> () export type TweenSequenceType = { TweenSequenceItem } type CurrentState = { thread: thread?, index: number, elapsed: number, heartbeat: RBXScriptConnection?, activeTweenIndices: { number }, } type TweenSequenceImpl = { __index: TweenSequenceImpl, __tostring: (self: TweenSequence) -> string, new: (sequence: TweenSequenceType) -> TweenSequence, _connectHeartbeat: (self: TweenSequence) -> (), _disconnectHeartbeat: (self: TweenSequence) -> (), _runItem: (self: TweenSequence, item: TweenSequenceItem) -> (), _resumeItem: (self: TweenSequence, item: TweenSequenceItem) -> (), Play: (self: TweenSequence) -> (), Pause: (self: TweenSequence) -> (), Resume: (self: TweenSequence) -> (), } export type TweenSequence = typeof(setmetatable( {} :: { _sequence: TweenSequenceType, _current: CurrentState, Playing: boolean, }, {} :: TweenSequenceImpl )) local TweenSequence = {} :: TweenSequenceImpl TweenSequence.__index = TweenSequence -- Creates a new TweenSequence from a sequence of items. -- Items can be number (wait), Tween, {Tween} (parallel), or () -> () (callback). function TweenSequence.new(sequence: TweenSequenceType): TweenSequence local self = setmetatable({}, TweenSequence) self._sequence = sequence self.Playing = false self._current = { thread = nil, index = 0, elapsed = 0, heartbeat = nil, activeTweenIndices = {}, } return self end function TweenSequence:_connectHeartbeat() self._current.heartbeat = RunService.Heartbeat:Connect(function(dt: number) self._current.elapsed += dt end) end function TweenSequence:_disconnectHeartbeat() if self._current.heartbeat then self._current.heartbeat:Disconnect() self._current.heartbeat = nil end end function TweenSequence:_runItem(item: TweenSequenceItem) if typeof(item) == "number" then task.wait(item) elseif typeof(item) == "table" then self._current.activeTweenIndices = {} for tweenIndex, tween in item do tween:Play() table.insert(self._current.activeTweenIndices, tweenIndex) tween.Completed:Connect(function() local idx = table.find(self._current.activeTweenIndices, tweenIndex) if idx then table.remove(self._current.activeTweenIndices, idx) end end) end local longest: Tween = item[1] for _, tween in item do if tween.TweenInfo.Time > longest.TweenInfo.Time then longest = tween end end longest.Completed:Wait() elseif typeof(item) == "function" then item() else item:Play() item.Completed:Wait() end end function TweenSequence:_resumeItem(item: TweenSequenceItem) if typeof(item) == "number" then local remaining = item - self._current.elapsed if remaining > 0 then task.wait(remaining) end elseif typeof(item) == "table" then for _, tweenIndex in self._current.activeTweenIndices do item[tweenIndex]:Play() end if #self._current.activeTweenIndices > 0 then local longest: Tween = item[self._current.activeTweenIndices[1]] for _, tweenIndex in self._current.activeTweenIndices do local tween: Tween = item[tweenIndex] if tween.TweenInfo.Time > longest.TweenInfo.Time then longest = tween end end longest.Completed:Wait() end else local tween = item :: Tween tween:Play() tween.Completed:Wait() end end -- Plays the sequence from the beginning. -- Sets Playing to true for the duration of playback. function TweenSequence:Play() self._current.thread = task.spawn(function() self.Playing = true for index, item in self._sequence do self._current.index = index self._current.elapsed = 0 self._current.activeTweenIndices = {} self:_connectHeartbeat() self:_runItem(item) self:_disconnectHeartbeat() end self.Playing = false end) end -- Pauses the sequence at the current step. -- Pauses any actively playing Tween instances at that step. function TweenSequence:Pause() self:_disconnectHeartbeat() if self._current.thread then task.cancel(self._current.thread) self._current.thread = nil end local item = self._sequence[self._current.index] if item then if typeof(item) == "table" then for _, tween in item do if tween.PlaybackState == Enum.PlaybackState.Playing then tween:Pause() end end elseif typeof(item) ~= "number" and typeof(item) ~= "function" then item:Pause() end end self.Playing = false end -- Resumes the sequence from where it was paused. -- Completes the remaining duration of the current step before advancing. function TweenSequence:Resume() if self._current.index == 0 or self.Playing then return end self._current.thread = task.spawn(function() self.Playing = true local startIndex = self._current.index self:_connectHeartbeat() self:_resumeItem(self._sequence[startIndex]) self:_disconnectHeartbeat() for index = startIndex + 1, #self._sequence do self._current.index = index self._current.elapsed = 0 self._current.activeTweenIndices = {} self:_connectHeartbeat() self:_runItem(self._sequence[index]) self:_disconnectHeartbeat() end self.Playing = false end) end function TweenSequence:__tostring(): string return ("TweenSequence(Playing=%s, step=%d/%d)"):format( tostring(self.Playing), self._current.index, #self._sequence ) end return { Create = TweenSequence.new, }