53 preset animations across 7 categories — fades, slides, bounces, pops, modal entrances, notification slides, page pushes — ready to play on any UI Toolkit element. Author them as reusable ScriptableObjects, or chain them in code with a fluent builder. Under the hood it's all native UI Toolkit CSS transitions, so playback stays on the engine's fast path.
One line, no assets, no setup:
// Fade in, then bounce-pop — chain code presets and per-property steps freely
new TransitionSequencePlayer(
new TransitionSequence().FadeIn(0.2f).Bounce().Build()
).Play(myButton);.FadeIn(), .FadeOut(), .Bounce(), .SlideIn(), .SlideOut(), .Pulse() presets plus full per-property steps (opacity, scale, translate, rotate, colors, width/height)Repeat, and Once / Loop / PingPong / LoopCount play modesClearInlineStyles cleanup, OnComplete callbacks, and UI particles mid-sequence via PlayOverlayThe simplest way to add animations is through the TransitionAnimator component:
TransitionAnimator to a GameObjectUIViewComponent or a PanelRenderer (leave the PanelRenderer empty to auto-search this GameObject and its parents)// Play animation manually
transitionAnimator.Play();
// Or enable "Play On Enable" in the InspectorLuna includes 53 preset animation assets. They import with the Essentials sample under Assets/Samples/LunaUI/<version>/Essentials/ScriptableObjects/TransitionPresets/. Reference one in the Inspector and play it:
[SerializeField] private TransitionSequenceAsset _fadeIn;
// Play the referenced preset
var player = _fadeIn.CreatePlayer();
player.Play(myElement);Build animations programmatically with the TransitionSequence builder:
var sequence = new TransitionSequence()
.Opacity(0f, 0.3f, EasingMode.EaseOut) // Fade out
.Scale(0.8f, 0.3f, EasingMode.EaseOut) // Scale down
.Build();
var player = new TransitionSequencePlayer(sequence);
player.OnComplete += () => Debug.Log("Animation complete!");
player.Play(myElement);MonoBehaviour that plays transition animations on VisualElements.
| Property | Description |
|---|---|
| Nodes | Ordered list of animation nodes (inline step groups or sub-sequence asset references) |
| Play Mode | Once, Loop, PingPong, or LoopCount |
| Loop Count | Number of loops when Play Mode is LoopCount |
| UI View Component | Optional UIViewComponent reference for target resolution |
| Panel Renderer | PanelRenderer reference (used when no UIViewComponent is set; if empty, searches this GameObject and its parents) |
| Element Name | Name of the target VisualElement (leave empty for root) |
| Fade Trigger | Which UIView fade event triggers this animation |
| Play On Enable | Automatically play when the component is enabled |
⚠️ Async tree delivery.
PanelRendererdelivers its visual tree asynchronously — there is no synchronousrootVisualElement. The animator registers a reload callback on the resolved PanelRenderer the first timePlay()resolves its target (the callback fires immediately when the panel is already mounted) and keeps the cached root current across rebuilds. If the panel hasn't mounted yet,Play()logs a warning and exits — call it again once the panel is ready, e.g. fromUIViewComponent.WhenUILoaded(...).
// Play the animation on the configured element
public void Play()
// Play on a specific element
public void Play(VisualElement target)
// Stop playback
public void Stop()
// Pause/Resume
public void Pause()
public void Resume()
// Create a sequence or player from this animator's nodes
public TransitionSequence CreateSequence()
public TransitionSequencePlayer CreatePlayer()These are UnityEvents with no public C# accessor — wire listeners in the Inspector:
For code subscriptions, use TransitionSequencePlayer.OnComplete / OnStopped via CreatePlayer().
ScriptableObject for defining reusable animation sequences. Create via: Create > CupkekGames > Luna UI > Transition Sequence
Supports:
Runtime player for executing transition sequences on a VisualElement.
// Create from a built sequence
var player = new TransitionSequencePlayer(sequence);
// Or from an asset
var player = myAsset.CreatePlayer();
// Enable debug logging
player.Debug = true;
// Play on target
player.Play(myElement);
// Check state
bool isPlaying = player.IsPlaying;
bool isPaused = player.IsPaused;
int currentNode = player.CurrentNodeIndex;
int currentIteration = player.CurrentIteration;When Play() is called on an element that already has an active TransitionSequencePlayer, the previous player is automatically stopped before the new one starts. This prevents conflicting animations from overlapping on the same element:
var player1 = new TransitionSequencePlayer(bounceSequence);
var player2 = new TransitionSequencePlayer(fadeSequence);
player1.Play(myElement); // starts playing
player2.Play(myElement); // automatically stops player1, then starts player2event Action OnComplete; // Sequence fully complete
event Action<int> OnIterationComplete; // After each loop pass
event Action OnStopped; // Playback stopped via Stop()Every sequence has a RestoreMode:
RestoreOnEnd (default) — the player cleans up when the sequence ends for any reason (completion, Stop(), preemption by another player). Cleanup happens in one of two ways, chosen per property:
translate/scale/rotate, opacity, and background-position have their inline values cleared so the element falls back to whatever USS computes. These properties are treated as animation scratch space: an inline value on them is presumed to belong to some animation, never to the component. Crucially this includes opacity — if a previous fade-out left inline opacity: 0 behind, an entry animation that ends does not faithfully restore that stale 0 (which would make the element vanish); it clears the inline value and USS wins.Play() was called: all colors (background-color, color, border colors, tint) and all length properties (width/height, min/max, margins, paddings, offsets, border widths/radii, font-size, flex-basis). These can be legitimate component state (ProgressBar owns its fills' inline colors), so a flash on an inline-colored element must return to the exact pre-flash color, not to USS.Together these make transient effects interrupt-safe: stopping an opacity pulse loop mid-dim returns to the USS opacity, a size-pulse loop cannot leave a card stuck mid-grow, and a color flash lands back on the exact pre-flash color. The property table lives in one internal registry (InlineStyleRestore); an animated property with no registry entry logs a warning instead of silently leaking.
Persist — the final values stay. Use for exit animations that must remain hidden/moved (FadeOut and SlideOut set this automatically).
Instant setter steps (SetOpacity, SetScale, SetTranslate, SetRotate, SetBackgroundPosition) count as animating their property, so RestoreOnEnd cleans up after them too.
One active player per element. Playing a sequence on an element stops whichever player was animating it (its restore runs first). With several juice systems in play — hover juice, attractor arrivals, bar effects — two effects aimed at the same element preempt each other by design; aim them at different elements (a wrapper vs. the content) when they must run simultaneously.
TransitionEndEvent through an internal one-shot awaiter that auto-unsubscribes and won't hang if the element detaches mid-animation — the sequence advances instead.UsageHints.DynamicTransform. When a sequence animates transform properties (translate / scale / rotate), the player ORs the required UsageHints onto the target element before playing, keeping per-frame transform changes off the mesh-regeneration path.The TransitionSequence class provides a fluent API for building animations:
var sequence = new TransitionSequence()
// Opacity
.Opacity(1f, 0.3f)
.Opacity(0f, 0.3f, EasingMode.EaseOut)
// Scale (uniform or separate x/y)
.Scale(1.2f, 0.2f)
.Scale(new Vector2(1.5f, 1f), new TransitionTimingConfig(0.3f, EasingMode.EaseInOut))
// Translate (pixels or percent)
.Translate(0f, -20f, 0.3f)
.TranslatePercent(0f, -50f, 0.3f)
// Rotate (degrees)
.Rotate(360f, 0.5f)
// Colors
.BackgroundColor(Color.red, 0.2f)
// Border colors are per-side: BorderTopColor / BorderRightColor / BorderBottomColor / BorderLeftColor
.Color(Color.blue, StyleColorProperty.BorderTopColor, new TransitionTimingConfig(0.2f))
// Length properties
.Width(new Length(200, LengthUnit.Pixel), new TransitionTimingConfig(0.3f))
.Height(new Length(50, LengthUnit.Percent), new TransitionTimingConfig(0.3f))
// Background position (scrolling textures, marching stripes)
.BackgroundPosition(64f, 0f, 0.8f, EasingMode.Linear)
.Build();BackgroundPosition animates background-position-x/y — combined with a repeating background-image this gives marching stripes, energy flows, and conveyor effects. SetBackgroundPosition is its instant counterpart; a Loop sequence of jump to 0 → animate to one tile width makes a seamless sawtooth scroll:
var stripeScroll = new TransitionSequence()
.SetBackgroundPosition(Vector2.zero) // instant jump
.BackgroundPosition(64f, 0f, 0.8f, EasingMode.Linear) // one tile width
.WithPlayMode(TransitionPlayMode.Loop)
.Build();Both are also available as serialized step types in TransitionSequenceAsset (BackgroundPosition / SetBackgroundPosition). The shipped ProgressBarStripeScroll preset is exactly this sequence.
Run multiple animations simultaneously:
var sequence = new TransitionSequence()
.Together(sub => sub
.Opacity(1f, 0.3f)
.Scale(1f, 0.3f)
.Translate(0f, 0f, 0.3f)
)
.Build();var sequence = new TransitionSequence()
.Opacity(0f, 0.3f)
.Delay(0.5f) // Wait 500ms
.Callback(() => Debug.Log("Halfway done!"))
.Opacity(1f, 0.3f)
.Build();Repeat a group of steps a fixed number of times:
var sequence = new TransitionSequence()
.Repeat(3, r => r
.Scale(1.1f, 0.15f, EasingMode.EaseInOut)
.Scale(1f, 0.15f, EasingMode.EaseInOut)
)
.Build();If count is 1, the nodes are inlined directly without a repeat wrapper.
Fire a particle effect or VFX overlay on a UI element at a specific point in your animation sequence — no custom code or Callback needed. This integrates directly with the UIRender system.
Fluent API:
var sequence = new TransitionSequence()
.Scale(1.2f, 0.2f, EasingMode.EaseOut)
.PlayOverlay(sparksPrefab) // fire-and-forget
.Scale(1f, 0.2f, EasingMode.EaseIn)
.Build();With settings:
var overlaySettings = new UIOverlaySettings.Builder()
.WithAnchor(OverlayAnchor.Center)
.WithOverflowVisible(true)
.Build();
var sequence = new TransitionSequence()
.PlayOverlay(sparksPrefab, renderSettings, overlaySettings)
.Build();Blocking mode — wait for particles to finish before continuing:
var sequence = new TransitionSequence()
.PlayOverlay(sparksPrefab, waitForDrain: true)
.FadeOut() // only starts after particles fully drain
.Build();PlayOverlayInstance — borrow an existing scene GameObject instead of instantiating a copy:
var sequence = new TransitionSequence()
.PlayOverlayInstance(existingParticleSystem)
.Build();Target a child element instead of the sequence root:
var sequence = new TransitionSequence()
.PlayOverlay(sparksPrefab, targetElementName: "icon-slot")
.Build();Inspector (ScriptableObject): In a TransitionSequenceAsset, add a node and set its type to Overlay. Configure:
Handle cleanup: Overlay handles are automatically tracked by
TransitionSequencePlayerand released when the player is stopped, reset, or loops. You don't need to manage handle lifetimes manually.
Register a completion callback directly on the builder. It fires after all other nodes:
var sequence = new TransitionSequence()
.FadeIn()
.Bounce()
.OnComplete(() => Debug.Log("All done!"))
.Build();Note:
.OnComplete()on the builder is separate fromTransitionSequencePlayer.OnCompleteevent. The builder version is appended as a finalCallbacknode duringBuild(), while the player event fires externally after sequence completion.
After animating, you may want to revert inline style overrides back to USS-defined or default values. Use ClearInlineStyles with the StyleProperty enum for type-safe cleanup:
var sequence = new TransitionSequence()
.Scale(1.2f, 0.2f)
.Scale(1f, 0.2f)
.ClearInlineStyles(StyleProperty.Scale)
.Build();
// Clear multiple properties at once
var sequence2 = new TransitionSequence()
.Together(sub => sub
.Scale(1.2f, 0.3f)
.Opacity(0.5f, 0.3f)
)
.Together(sub => sub
.Scale(1f, 0.3f)
.Opacity(1f, 0.3f)
)
.ClearInlineStyles(StyleProperty.Scale, StyleProperty.Opacity)
.Build();| Value | CSS Property |
|---|---|
Translate | translate |
Scale | scale |
Opacity | opacity |
Rotate | rotate |
BackgroundColor | background-color |
Width | width |
Height | height |
Color | color |
A string-based overload is also available for properties not covered by the enum:
.ClearInlineStyles("border-width", "margin-left")var sequence = new TransitionSequence()
.Scale(1.1f, 0.2f)
.Scale(1f, 0.2f)
.WithPlayMode(TransitionPlayMode.Loop) // Loop forever
.Build();
// Or loop a specific number of times
var sequence = new TransitionSequence()
.Rotate(360f, 1f)
.WithPlayMode(TransitionPlayMode.LoopCount)
.WithLoopCount(3) // Rotate 3 times
.Build();Animate multiple properties with shared timing:
var timing = new TransitionTimingConfig(0.3f, EasingMode.EaseOutBack);
var sequence = new TransitionSequence()
.Multi(timing,
new OpacitySetter(1f),
new ScaleSetter(new Vector2(1f, 1f)),
new TranslateSetter(Translate.None())
)
.Build();The TransitionSequence builder includes built-in preset methods for the most common animations. These are chainable and handle setup/cleanup internally (e.g. clearing inline styles).
Scales up past target, settles, dips below, then returns to 1. Each phase has its own duration for fine-tuned timing. Clears inline scale automatically.
var sequence = new TransitionSequence()
.Bounce(peakScale: 1.2f, settleScale: 1.15f, dipScale: 0.95f,
peakTime: 0.12f, settleTime: 0.1f,
holdTime: 0.2f,
dipTime: 0.08f, returnTime: 0.1f)
.Build();| Parameter | Type | Default | Description |
|---|---|---|---|
peakScale | float | 1.2 | Scale peak |
settleScale | float | 1.15 | Scale after peak, before dip. Set equal to peakScale to skip |
dipScale | float | 0.95 | Scale dip below 1 |
peakTime | float | 0.12 | Duration of the initial scale-up in seconds |
settleTime | float | 0.1 | Duration of the settle step in seconds |
holdTime | float | 0 | Optional pause after settle before scale-down begins. 0 = no pause |
dipTime | float | 0.08 | Duration of the dip step in seconds |
returnTime | float | 0.1 | Duration of the return-to-normal step in seconds |
easing | EasingMode | EaseOut | Easing for all scale steps |
// Instantly sets opacity to 0, then animates to 1
new TransitionSequence().FadeIn(duration: 0.3f).Build();
// Animates opacity to 0
new TransitionSequence().FadeOut(duration: 0.3f).Build();Animates translation from/to an offset. SlideIn clears inline translate at the end.
// Slide in from the left by 50px
new TransitionSequence()
.SlideIn(distancePx: 50f, direction: SlideDirection.Left, duration: 0.3f)
.Build();
// Slide out to the right by 80px
new TransitionSequence()
.SlideOut(distancePx: 80f, direction: SlideDirection.Right, duration: 0.3f)
.Build();SlideDirection values: Left, Right, Top, Bottom.
Scales up then back to 1, repeated count times. Clears inline scale at the end.
new TransitionSequence()
.Pulse(scale: 1.1f, duration: 0.15f, count: 2)
.Build();Presets are regular builder methods — chain them with any other step:
var sequence = new TransitionSequence()
.FadeIn(duration: 0.3f)
.SlideIn(distancePx: 30f, direction: SlideDirection.Bottom)
.Bounce()
.OnComplete(() => Debug.Log("Entrance complete!"))
.Build();Luna includes 53 ready-to-use animation presets organized into categories:
| Preset | Description |
|---|---|
| FadeIn / FadeOut | Opacity transitions |
| ScaleIn / ScaleOut | Scale from/to zero |
| SlideInUp/Down/Left/Right | Slide from off-screen |
| SlideOutUp/Down/Left/Right | Slide to off-screen |
| FlipIn / FlipOut | 3D flip effect via scaleX |
| RotateIn / RotateOut | Rotation with opacity |
| BounceIn | Bouncy scale entrance |
| Preset | Description |
|---|---|
| Shake / ShakeVertical | Horizontal/vertical shaking |
| Wobble | Rotation wobble |
| Jello | Elastic jiggle effect |
| Rubber | Rubber band stretch |
| Flash | Rapid opacity flashes |
| Heartbeat | Pulsing scale |
| ColorPulse | Background color pulse |
| Preset | Description |
|---|---|
| Pop | Quick scale bump |
| PopBounceIn / PopBounceOut | Bouncy pop entrance/exit |
| Press | Button press effect |
| Lift / LiftReset | Hover lift effect |
| Squish | Squash and stretch |
| Preset | Description |
|---|---|
| Spin | Continuous rotation |
| Bounce | Vertical bounce |
| Blink | Opacity blink |
| Breathe | Gentle scale pulse |
| Preset | Description |
|---|---|
| ZoomIn / ZoomOut | Scale with opacity |
| RiseIn / RiseOut | Rise from bottom |
| DropIn | Drop from top with bounce |
| SwingIn / SwingOut | Rotation swing |
| FoldY / UnfoldY | Vertical fold effect |
| Preset | Description |
|---|---|
| SlideInTop / SlideInBottom | Notification slide in |
| SlideOutTop / SlideOutBottom | Notification slide out |
| Preset | Description |
|---|---|
| PushInLeft / PushInRight | Page push entrance |
| PushOutLeft / PushOutRight | Page push exit |
UIViews can automatically play transition animations on fade events. See UIView Transitions for details.
// Add transition to UIView. An ElementSelector with no filters targets
// the UIView's parent element — set Name / ClassName to scope to a
// child or to multiple matches.
myUIView.AddTransition(new UIViewTransitionEntry
{
Animation = popBounceInAsset,
Trigger = FadeEventTrigger.FadeInStart,
Target = new ElementSelector { Name = "modal-content" }
});Encapsulates timing configuration for transitions:
var timing = new TransitionTimingConfig(
duration: 0.3f, // Duration in seconds
easing: EasingMode.EaseOut, // Easing function
delay: 0.1f // Delay before start
);All Unity UI Toolkit easing modes are supported:
LinearEase, EaseIn, EaseOut, EaseInOutEaseInSine, EaseOutSine, EaseInOutSineEaseInCubic, EaseOutCubic, EaseInOutCubicEaseInCirc, EaseOutCirc, EaseInOutCircEaseInElastic, EaseOutElastic, EaseInOutElasticEaseInBack, EaseOutBack, EaseInOutBackEaseInBounce, EaseOutBounce, EaseInOutBouncePanelRenderer delivers the visual tree asynchronously, so element queries belong inside its reload callback — never in Start. The callback fires immediately on registration when the panel is already mounted, and again after every rebuild (rebuilds produce a fresh tree, so re-query and re-register there):
public class ButtonHoverEffect : MonoBehaviour
{
[SerializeField] private PanelRenderer _panelRenderer;
[SerializeField] private TransitionSequenceAsset _hoverIn;
[SerializeField] private TransitionSequenceAsset _hoverOut;
private TransitionSequencePlayer _playerIn;
private TransitionSequencePlayer _playerOut;
private VisualElement _button;
void Awake()
{
if (_panelRenderer == null) _panelRenderer = GetComponent<PanelRenderer>();
_playerIn = _hoverIn.CreatePlayer();
_playerOut = _hoverOut.CreatePlayer();
_panelRenderer.RegisterUIReloadCallback(OnUIReload);
}
void OnDestroy()
{
if (_panelRenderer != null)
{
_panelRenderer.UnregisterUIReloadCallback(OnUIReload);
}
}
private void OnUIReload(PanelRenderer renderer, VisualElement root, int version)
{
_button = root.Q<Button>("my-button");
if (_button == null) return;
_button.RegisterCallback<PointerEnterEvent>(e =>
{
_playerOut.Stop();
_playerIn.Play(_button);
});
_button.RegisterCallback<PointerLeaveEvent>(e =>
{
_playerIn.Stop();
_playerOut.Play(_button);
});
}
}On a view backed by a UIViewComponent you can skip the callback plumbing: override OnUILoaded(VisualElement root) (or use WhenUILoaded(() => ...)) and wire the button there.
// Create a continuous spinning animation
var spinSequence = new TransitionSequence()
.Rotate(360f, 1f, EasingMode.Linear)
.WithPlayMode(TransitionPlayMode.Loop)
.Build();
var spinner = new TransitionSequencePlayer(spinSequence);
spinner.Play(loadingIcon);
// Stop when loading completes
spinner.Stop();The Showcase sample includes one demo scene:
Import the Showcase sample to explore it. For wiring sequences to screen fade events, see UIView Transitions.
PlayOverlay's particles-on-UI.All 53 presets ship in the box — get Luna on the Asset Store.
Settings
Theme
Light
Contrast
Material
Dark
Dim
Material Dark
System
Sidebar(Light & Contrast only)
Font Family
DM Sans
Wix
Inclusive Sans
AR One Sans
Direction