Thanks! Hope the plugin and the runtime can be of help!
MAJOR UPDATE! v2.0.0 - Has breaking changes!
Please read the change log before updating!
Dual-Paradigm
The engine now supports two workflows, Service-Only and a Track-Based allowing for a high-level or low-level control depending on the prefered way of working.
Service-Only
Retains the same high-level API as the previous version. Functions like Play and Stop operate by accepting a GuiObject and a Config.
- New Added Functions:
PauseandResumehave been added directly to the service and allows pausing and resuming from paused state. - Play/Batch returns a proxy signal as previous version that fires the status strings:
Completed,Looped,Cancelled.
Track-Based
Using CreateTrack or CreateBatchedTrack now generates a dedicated AnimTrack or BatchedAnimTrack object.
- Control: The returned object provides direct access to the animation state. Methods such as
:SetSpeed(),:Scrub(), or:Pause()can be called directly on the track instance.
*Instead of firing strings the track objects exposes specific events as: Completed, Looped, Cancelled and MarkerReached.
Removed Functions
- BindToEvent & GetRunner have been removed since they were redundant. Call play inside your own event connections
- SetToStart has been removed since SetToTime can handle all cases.
- SetLoop/Batch loop settings should only be handled with TrackOptions (Previously PlayOptions) by setting Loop=true or Loop = 4 (loop 4 time) or manipulating the track object itself.
New Functions
| Function | Status | Description |
|---|---|---|
SetFrame / Batch |
New | Snaps a UI element to a specific frame number (calculated automatically using the FPS defined in the config). |
Pause / Resume |
New | Added to both the Service (global instance access) and the Track objects (local access). |
ClearInstance / Batch |
Enhanced | Resets cache values and halts active tracks and resets every single property defined in the animation configuration (e.g., Rotation, Size, Color) back to its cached base state. |
Stop / StopBatch |
Updated | Handles ResetToStart logic. |
Suspend |
New | A master switch to disconnect the service from RunService, effectively pausing the entire engine globally - used for Editor. |
Lifecycle Examples
Service-Only
local MyFrame = script.Parent.Frame
local Config = require(config)
-- Play using the service. The service manages the track internally.
AnimationService.Play(MyFrame, Config, { Reset = true })
task.wait(1)
-- New Feature: Pause via the service without needing a track variable
AnimationService.Pause(MyFrame, Config)
task.wait(1)
-- Cleanup: Stops animations and resets ALL properties defined in 'Config'
-- (e.g., Rotation, Size, Transparency) back to their original state.
AnimationService.ClearInstance(MyFrame)
Track-Based
local MyFrame = script.Parent.Frame
local Config = require(config)
-- Create the track manually
local track = AnimationService.CreateTrack(MyFrame, Config)
-- Connect to track events
local con = track.Completed:Connect(function()
print("UI Transition Finished")
end)
con:Disconnect()
track:Play()
task.wait(0.5)
track:SetSpeed(2) -- Speed up the UI animation dynamically
track:Scrub(0) -- Instant rewind to the start
-- Cleanup: The Service automatically detects destruction and performs cleanup
track:Destroy()
I’m really happy with this rewrite and I hope it will help you too!
And As always if you have any feedback or questions, let me know!

Nice update!!
Thank you and thanks for the help testing before release! ![]()
UI Rework Dev Update!
I need a couple of volunteers to test the new UI Rework! Please comment here or write me if you would like to test the update before release!
The only condition for testing is that you own a copy of the plugin!
Here a couple of images of how the new UI looks.
All help is appreciated, thanks!
roui3
also btw I like this project alot, the interface looks great, functionality looks great.
asdjnasdasdljkalkjkjladasdadasdasdsdaq
Next release is really close! The release will have a surprise as well! Probably this week or early next week!
Changelog
2.0.2
2026-02-24
Fixed:
- Import bug were loading animations could fail when having the FPS property.
I promised animation events early this year… And.
Change log v2.1.0
- Features a fully reworked UI with a lot of quality of life keybinds and a design that will make working with the plugin easier. Go through the keybinds to see all the available actions.
- Animation Events - Right now you can use the datatypes - String, number and boolean. You can toggle the events track inside of the Animation Settings popup.
- Themes - you can cycle between - Dark,. Blue, Light and Orange.
- Keybinds - Can be set to new input, can be set with modifiers and are saved between sessions.
A big thanks to @winpol who helped with testing during the test phase!
Enjoy!
Cascade Animation Events — Usage Examples
This guide below is a bit of a repeat from the v2.0.0 post but I figure it might be needed. the guide shows how to use Animation Events (markers) with Cascade’s runtime engine.
Events are fired during playback when the animation reaches a specific frame, letting you
trigger sounds, particles, logic, or any game behaviour in sync with your animations.
Setup
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local AnimationService = require(ReplicatedStorage:WaitForChild("CascadeRuntimeEngine").AnimationService)
local MyAnim = require(ReplicatedStorage:WaitForChild("CascadeAnimations").MyAnimation)
1. Service-Only Approach — AnimationService.Play()
Play an animation on a single instance. Returns a proxy signal — a single
signal that fires for every event type. The first argument is always the event
type string ("Completed", "Looped", "Cancelled", or "MarkerReached"),
followed by any type-specific arguments.
local myFrame = script.Parent.MyFrame
local signal = AnimationService.Play(myFrame, MyAnim)
signal:Connect(function(eventType, ...)
if eventType == "MarkerReached" then
local name, eventData = ...
if name == "PlaySound" then
local soundId = eventData.Data
local sound = Instance.new("Sound")
sound.SoundId = soundId
sound.Parent = myFrame
sound:Play()
sound.Ended:Once(function() sound:Destroy() end)
elseif name == "ToggleVisibility" then
myFrame.Visible = not myFrame.Visible
end
elseif eventType == "Completed" then
print("Animation finished!")
elseif eventType == "Looped" then
print("Animation looped!")
end
end)
With Options
local signal = AnimationService.Play(myFrame, MyAnim, {
Loop = true,
Delay = 0.5,
Reset = true,
})
signal:Connect(function(eventType, ...)
if eventType == "MarkerReached" then
local name, eventData = ...
if name == "LoopPulse" then
print("Pulse!")
end
elseif eventType == "Looped" then
print("Animation looped!")
end
end)
2. Service-Only Approach — AnimationService.PlayBatch()
Play the same animation on multiple instances at once. Same proxy signal pattern
as Play(), but MarkerReached events include a fourth value: the specific
instance that triggered the event.
local buttons = {
script.Parent.Button1,
script.Parent.Button2,
script.Parent.Button3,
}
local signal = AnimationService.PlayBatch(buttons, MyAnim, {
Stagger = 0.1,
})
signal:Connect(function(eventType, ...)
if eventType == "MarkerReached" then
local name, eventData, instance = ...
if name == "Highlight" then
print(instance.Name .. " reached the Highlight marker")
instance.BackgroundColor3 = Color3.fromRGB(255, 200, 0)
end
elseif eventType == "Completed" then
print("All instances finished!")
end
end)
3. Track-Based Approach — AnimationService.CreateTrack()
Create a track for full playback control (play, pause, resume, scrub, speed).
Unlike Play()/PlayBatch(), tracks expose separate signals for each
event type: Completed, Looped, Cancelled, and MarkerReached.
local myFrame = script.Parent.MyFrame
local track = AnimationService.CreateTrack(myFrame, MyAnim, {
Loop = 3,
Reset = true,
})
track.MarkerReached:Connect(function(name, eventData)
if name == "Damage" then
local amount = tonumber(eventData.Data) or 10
print("Deal " .. amount .. " damage!")
elseif name == "ScreenShake" then
shakeCamera(0.2)
end
end)
track.Completed:Connect(function()
print("Track finished all loops")
track:Destroy()
end)
track.Looped:Connect(function()
print("Loop iteration complete")
end)
track:Play()
Track Control
track:Pause()
task.wait(1)
track:Play()
track:SetSpeed(2)
track:SetSpeed(0.5)
track:SetSpeed(-1)
track:Scrub(1.5)
track:Stop()
track:Destroy()
4. Track-Based Batch — AnimationService.CreateBatchedTrack()
Full track control over multiple instances with staggered playback.
MarkerReached receives a third argument: the instance that triggered the event.
local cards = {
script.Parent.Card1,
script.Parent.Card2,
script.Parent.Card3,
script.Parent.Card4,
}
local track = AnimationService.CreateBatchedTrack(cards, MyAnim, {
Stagger = 0.15,
Loop = false,
Reset = true,
})
track.MarkerReached:Connect(function(name, eventData, instance)
if name == "FlipSound" then
playSound("rbxassetid://123456", instance)
elseif name == "Glow" then
applyGlow(instance)
end
end)
track.Completed:Connect(function()
print("All cards finished animating")
track:Destroy()
end)
track:Play()
5. Event Data Types
Events support three data types set in the editor. The eventData table
contains Name, Time, DataType, and Data.
String Events
-- Track-based (separate signal)
track.MarkerReached:Connect(function(name, eventData)
-- eventData.DataType == "String"
-- eventData.Data is a string
if name == "PlaySound" then
local soundId = eventData.Data -- e.g. "rbxassetid://123456"
playSound(soundId)
end
end)
-- Service-only (proxy signal)
signal:Connect(function(eventType, ...)
if eventType == "MarkerReached" then
local name, eventData = ...
if name == "PlaySound" then
playSound(eventData.Data)
end
end
end)
Number Events
track.MarkerReached:Connect(function(name, eventData)
-- eventData.DataType == "Number"
-- eventData.Data is a number
if name == "SetAlpha" then
local alpha = eventData.Data -- e.g. 0.5
myFrame.BackgroundTransparency = alpha
end
end)
Boolean Events
track.MarkerReached:Connect(function(name, eventData)
-- eventData.DataType == "Boolean"
-- eventData.Data is a boolean
if name == "ToggleHUD" then
local show = eventData.Data -- true or false
hudFrame.Visible = show
end
end)
6. Example Use — UI Intro Sequence
A complete example using track-based batch: an intro animation that fades in
cards one by one, plays sounds at key moments, and enables interaction when done.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local AnimationService = require(ReplicatedStorage.CascadeRuntimeEngine.AnimationService)
local IntroAnim = require(ReplicatedStorage.CascadeAnimations.IntroSequence)
local menuFrame = script.Parent.MenuFrame
local cards = menuFrame.CardContainer:GetChildren()
for _, card in cards do
card.Active = false
card.Interactable = false
end
local track = AnimationService.CreateBatchedTrack(cards, IntroAnim, {
Stagger = 0.12,
Reset = false,
})
track.MarkerReached:Connect(function(name, eventData, instance)
if name == "Whoosh" then
local sound = Instance.new("Sound")
sound.SoundId = eventData.Data
sound.PlaybackSpeed = 0.9 + math.random() * 0.2
sound.Parent = instance
sound:Play()
sound.Ended:Once(function() sound:Destroy() end)
elseif name == "EnableInteraction" then
instance.Active = true
instance.Interactable = true
end
end)
track.Completed:Connect(function()
print("Intro complete — menu is ready")
track:Destroy()
end)
track:Play()
Patch for the UI!
Partly because of a bug where keyframes were disappearing, the further down the hierarchy they were and partly the expand/collapse on each track made it impossible to work with larger hierarchies for a cutscene or some larger intro animation.
Changelog
v2.1.1
-
Split hierarchy browser and property tracklist into separate panels
-
Hierarchy viewer is toggleable from the tracklist header
-
Tracklist now only shows animated properties, grouped by object
-
Fixed timeline grid clipping bug where keyframes disappeared on scroll
Let me know if you have any feedback on this!
Changelog
I was a bit trigger happy yesterday, a couple of more fixes!
v2.1.2
-
Fixed animation not playing past a certain frame when FPS < 60 (duration was capped at LengthSeconds instead of actual animation length. Plugin only, no effects on AnimationService)
-
Fixed track ordering so parent objects always appear above their children
-
Fixed box selection not aligning with the new tracklist row layout
-
Clicking an object in the hierarchy viewer now selects it in the Explorer
-
Changing FPS no longer changes TotalFrames — length in seconds adjusts instead
-
Property popup filter auto-focuses on open for faster searching
-
Fixed so Length is updated in the config when changing FPS or TotalFrames
New UI looking pretty good! It’s getting more intuitive.
I’m glad to hear that!
Have you tried the new additions to the API?
Changelog
Re-install the runtime to get the fix for relative tracks!
v2.1.3
-
Fixed a crash when playing animations with relative mode enabled on isolated axis tracks (e.g. animating only
Position.X.Scale). Relative tracks now correctly offset from the instance’s base value without affecting untouched axes. -
Fixed the Open dialog not listing animations stored inside subfolders of
CascadeAnimations. All animations are now shown recursively with their folder path (e.g.UI/FadeIn). -
Save and Save As now support subfolder paths — entering
UI/FadeInas the animation name will create theUIfolder automatically if it doesn’t exist.
Changelog
Re-install the runtime to get the new fixes for animation events!
v2.1.4
- Events now fire correctly on the first and last frame of every animation
- Events work in both forward and reverse play
- Events fire at the correct time per-instance in staggered batch animations
- Looping animations fire events on every loop pass, not just the first
Changelog
Re-install the runtime to get the new fixes type annotations
v2.1.5
- Fixed the type annotations on Play and PlayBatch functions. Now they have the correct class and gives auto-complete.
Also… a teaser from my upcoming tutorials on creating UI Animations…
Changelog
Re-install the runtime to getthe fixes for Axis Isolation!
v2.1.6
- Fixed axis isolation locking non-animated axes to their capture-time values — animating
Position.Xno longer prevents moving the object on Y.
Just posted a tutorial series on animating game UI with the plugin — two episodes up so far covering the basics and building a full main menu. More on the way.
Change log
Re-install the runtime to get Step easing support!
v2.2.0
Visual overhaul
-
Redesigned panel layout with clear borders between hierarchy, tracklist, and timeline
-
New alternating row colors for better readability across all themes
-
Smoother hover and selection transitions on track rows
-
Refined toolbar with matching header color and active button highlights
-
Selected keyframes now have a subtle glow halo
-
Timeline ruler shows more frame numbers at different zoom levels
-
Polished “Select an object” start screen
New features
-
Step easing — new “Step” option in the easing picker. Holds the previous value and snaps instantly at the keyframe, like a hold/constant keyframe. Works in forward play, reverse, and preview scrubbing. (Suggested by @winpol)
-
Track visibility toggle — eye icon on each track row to hide/show individual tracks from preview and playback without deleting them
-
Hierarchy visibility toggle — toggle switch on each object in the hierarchy to collapse all of its tracks from the timeline view
-
Name animation on creation — prompted to name your animation when starting or creating new, shown in the toolbar
-
Unsaved changes indicator — asterisk (*) next to the animation name when there are unsaved edits
-
Version number — fetched from the marketplace, displayed in the License popup and stamped on the installed runtime
-
Smart runtime replacement — installing the runtime now checks the existing version and prompts before replacing
Improvements
-
Saving is now allowed with no tracks (empty/template animations)
-
Removed unnecessary metadata (CreatedAt) from saved animation files
-
Default values (Delay=0, Loop=false) are no longer written to saved files
-
Fixed hierarchy viewer child indentation — leaf and branch children at the same depth now align correctly
As always, let me know if you have any feedback on the update!
Also a reminder that I have released a couple of tutorials for the plugin!



