Continuous per-layer property animations — spin, oscillate, pulse, and more. All evaluated CPU-side as additive deltas on the base shape data.
Shape property animations are per-layer, additive modifications applied by the ShapePropertyAnimator (SPA) at runtime. Rather than baking motion into the shader, RCE evaluates animation waveforms on the CPU each frame and writes the resulting deltas directly into the ShapeData buffer before it reaches the GPU. This keeps the shader simple and allows any combination of animated properties per layer.
The animation system sits in the middle of the rendering pipeline:
Animated values are additive — the animator computes a delta and adds it to the base shape data each frame. The one exception is scale, which uses multiplicative blending: scale *= 1 + delta. This means a delta of 0 leaves the shape unchanged, while a delta of 1.0 doubles the scale.
All animation timers reset when RebuildAnimationMapping() fires, which happens on every category switch. This ensures ramp-in envelopes and tween entries always play from the beginning when the player transitions between combat states.
Animations are off by default in edit mode. Use the Preview toggle in the Animations Panel to enable them while designing. In test mode and at runtime, animations are always active.
Every animation entry specifies one of seven modes. The first six are continuous waveforms; the seventh (Tween) is a one-shot interpolation. Each mode's formula determines how the delta evolves over time.
| # | Mode | Formula | Typical Use |
|---|---|---|---|
| 0 | Spin | speed × Σ(dt × envelope) |
Continuous rotation using envelope-weighted accumulation — ramp in/out modulates rotation rate (angular velocity) instead of scaling the accumulated angle, preventing visual reversal |
| 1 | Oscillate | amplitude × sin(2π × freq × t) |
Smooth back-and-forth motion, breathing effects |
| 2 | Pulse | amplitude × pow(abs(sin(π × f × t)), sharpness) |
Heartbeat, flash effects — sharpness controls the spike width |
| 3 | PingPong | amplitude × asin(sin(…)) / π / 2 |
Triangle wave — linear back-and-forth with sharp reversals |
| 4 | Sawtooth | amplitude × frac(freq × t) |
Ramp-and-snap — linear ramp up then instant reset |
| 5 | Noise | amplitude × PerlinNoise(seed, t × freq) |
Organic jitter, screen shake, battlefield vibration |
| 6 | Tween | fromDelta × (1 − eased(t / dur)) |
One-shot intro/exit — interpolate from an offset to the base value |
Waveform modes (0–5) run continuously and are shaped by speed, amplitude, frequency, phase, and sharpness parameters. Tween mode (6) has its own dedicated parameter set — see the Tween Mode section below.
Each animation entry targets exactly one property from the AnimatableProperty enum. Multiple entries can target the same property on the same layer — their deltas are summed.
| Property | Delta Type | Description |
|---|---|---|
| Rotation | Additive (degrees) | Rotates the shape around its center |
| PositionX | Additive (UV units) | Horizontal offset from base position |
| PositionY | Additive (UV units) | Vertical offset from base position |
| PositionXY | Additive (UV, both axes) | Single entry animates X and Y; uses amplitudeY, speedY, fromDeltaY, phaseY for the Y axis. Set phaseY = 90° for circular motion. |
| ScaleX | Multiplicative | Horizontal scale factor (scale.x *= 1 + delta) |
| ScaleY | Multiplicative | Vertical scale factor (scale.y *= 1 + delta) |
| ScaleUniform | Multiplicative | Uniform scale (scale *= 1 + delta) |
| ColorAlpha | Additive (0–1) | Shape fill opacity |
| GlowIntensity | Additive | Glow brightness multiplier |
| GlowSize | Additive | Glow spread radius |
| Feather | Additive | Edge anti-aliasing softness |
| GapRotation | Additive (degrees) | Rotation of the gap pattern |
| GapThickness | Additive | Gap line or radial thickness |
| OutlineAlpha | Additive (0–1) | True outline opacity |
| TaperAmount | Additive (0–1) | LineBurst taper end sharpness |
| Color | Lerp (0→1) | Full RGBA lerp between colorA and colorB; the animation delta drives the lerp factor. Overrides the shape's base color. Typically used with Tween mode. |
When the target property is PositionXY, a single entry animates both axes. The primary parameters (amplitude, speed, fromDelta, phase) control the X component; the secondary parameters amplitudeY, speedY, fromDeltaY, and phaseY control the Y component. Set phaseY = 90° with matching amplitude/frequency for X and Y to get circular or elliptical motion.
The Color property animates the shape's fill color by lerping between two RGBA colors (colorA and colorB). The waveform or tween output is interpreted as a lerp factor: 0 = full colorA, 1 = full colorB. Color entries default to Tween mode with colorA initialized from the layer's current color. The editor provides From/To color swatches and pickers when Color is selected.
Several parameters are shared across all or most animation modes.
Every animation entry — waveform or tween — supports a delay (in seconds) before the animation begins. During the delay period, tween entries hold at their starting offset (for "In" direction) or at zero (for "Out" direction). Waveform modes output zero delta during the delay.
Waveform modes (Spin, Oscillate, Pulse, PingPong, Sawtooth, Noise) support envelope shaping via ramp in and ramp out durations. The ramp acts as a multiplier on the waveform output, fading it from 0 → 1 at the start or 1 → 0 at the end. The ramp easing parameter controls the curve shape (any EasingType value). Tween mode does not use ramp — it has its own easing via tweenEasing.
| Mode | Behavior |
|---|---|
| Loop | Repeat continuously. Spin always loops regardless of this setting. |
| Once | Play one full cycle then hold at the end value. |
| PingPong | Play forward then reverse, smoothly alternating. For Tween: offset → base → offset. For waveforms: behaves like Loop. |
Each ShapeAnimationEntry is assigned a GUID id field at creation time. This ID is used for stable timer state keying (so entries retain their elapsed time across rebuilds within the same session) and for UI tracking in the Animations Panel. Duplicate properties are allowed — you can have two Rotation entries on the same layer, each with different modes or phases, and their deltas will sum.
Entries can randomize numeric parameters within a min/max range so each run (or each cycle) varies. Toggles: randomizeSpeed, randomizeAmplitude, randomizeFrequency, randomizeFromDelta. When enabled, the corresponding max field defines the upper bound; the main field is the lower bound (values are clamped so min/max order doesn't matter). Randomized values are resolved once when the entry becomes active (at RebuildAnimationMapping). When rerollEachCycle is enabled, values are re-resolved at each loop or cycle boundary (e.g. each Spin period, each Tween duration, or each waveform period). PositionXY entries can randomize both axes via the same toggles (amplitudeY/fromDeltaY/speedY use the same randomization as the primary parameter).
Tween is a one-shot interpolation from a saved starting offset back to the base value (or the reverse). It is the primary tool for intro and exit animations — shapes that scale up from nothing, fade in from transparent, or slide into position when a combat state activates.
| Parameter | Description |
|---|---|
fromDelta | The starting offset from the base value. For scale: fromDelta = 1.0 means start at 2× the base scale. |
duration | Total interpolation time in seconds. |
tweenEasing | Easing curve for the interpolation (EasingType). Separate from the waveform rampEasing. |
reverseDirection | false = "In" (offset → base). true = "Out" (base → offset). |
The editor adapts the "From" field label and value display based on the target property:
2.0×). Stored internally as fromDelta = display - 1.0.0).Helper methods DisplayValueToFromDelta(), FromDeltaToDisplayValue(), and GetTweenFromLabel() handle conversions between the UI-facing values and the stored internal representation.
To make a shape grow from zero to its base size when a category activates, add a Tween entry targeting ScaleUniform with direction In, fromDelta display of 0.0× (stored as -1.0), duration 0.3s, and easing EaseOutBack for a satisfying overshoot.
By default, all animation entries on a layer run simultaneously and independently. The Animation Sequence system provides optional sequential execution — entries play one after another in the order they appear in the list.
Toggle Sequence Mode on the layer to switch from independent to sequenced playback. When enabled, you mark a contiguous range of entries with Start and End markers using the AnimationSequenceRole enum. Entries between the markers are automatically part of the sequence; entries outside the markers continue to run independently.
These settings live on LayerData and apply to the layer's entire sequence:
| Setting | Description |
|---|---|
animationSequenceEnabled | Master toggle for sequence mode on this layer. |
sequenceLoop | Whether the sequence repeats after the last entry completes. |
sequenceLoopCount | Number of loops. -1 = infinite. |
sequencePingPong | When looping, alternate direction (forward → reverse → forward). |
When a waveform entry runs inside a sequence, the duration field controls how long it plays before the sequence advances to the next entry. A value of 0 uses the natural duration for "Once" loop mode. A value of -1 holds indefinitely for "Loop" or "Spin" entries (useful for a sustained state before an external trigger advances the sequence).
When cumulative is enabled on an entry, its delta stacks on top of the accumulated delta from all prior entries in the sequence that target the same property. This enables seamless chaining — for example, a Tween that rotates right 45° followed by a Tween that rotates left 30° from the current position, rather than snapping back to base before each entry.
accum - fromDelta to prevent discontinuity at the entry boundary.When an entry inside a sequence has Hold on Complete (holdOnComplete) enabled, its final delta is kept and applied every frame for the rest of the sequence instead of resetting when the next entry starts. This is useful for building up state step-by-step (e.g. rotate 30°, then hold that rotation while the next entry animates position). The toggle appears in the Animations Panel for entries within a sequence range.
Entries can be reordered via drag handle in the Animations Panel. Start/End markers are validated after every reorder — if a Start marker ends up after an End marker, both markers are automatically cleared to prevent invalid state.
The OnSequenceEvent callback fires AnimationSequenceEventArgs at key points in the sequence lifecycle:
| Event | When It Fires |
|---|---|
| SequenceStarted | The sequence begins playback (or restarts after a loop). |
| EntryStarted | An individual entry within the sequence becomes active. |
| EntryCompleted | An individual entry finishes its duration. |
| SequenceLooped | The sequence completes one full pass and loops. |
| SequenceEnded | The sequence finishes all loops (or hits the end in non-looping mode). |
Each animation entry has a blend mode (AnimEntryBlendMode) that controls how its delta interacts with other entries targeting the same property on the same shape.
| Mode | Behavior |
|---|---|
| Additive (default) | Delta stacks with other entries' deltas. Multiple Additive entries on the same property are summed. |
| Override | When active, temporarily suppresses all Additive entries for the same property on this shape. Uses rampIn for smooth takeover crossfade and rampOut for smooth release back to the base state. Additive entry timers keep advancing while suppressed, so they resume at the correct phase when the override releases. |
An Override entry goes through a well-defined lifecycle:
0 → 1 over rampIn seconds. During this phase, Additive entries are scaled down by (1 - overrideWeight) and the override's own output is scaled up by overrideWeight. Smoothstep interpolation eliminates jerk at ramp boundaries.weight = 1.0). Additive entries are fully suppressed (except Spin, which uses rate-based suppression).duration), the final delta is frozen.1 → 0 over rampOut seconds, smoothly returning control to Additive entries.awaitTrigger entries, it automatically re-arms for the next trigger.Spin (continuous rotation) overrides use rate-based crossfading rather than position-based. The override modulates the rotation speed instead of scaling the accumulated angle. This avoids visual reversal (the shape briefly unwinding). During late release, the override automatically snaps to the nearest 360° boundary so the handoff to Additive entries is imperceptible.
When an Override entry uses Tween mode and has a non-zero rampIn, the tween holds at its starting value during the ramp-in phase. This prevents a non-monotonic peak where the decreasing tween delta fights the increasing blend weight. The tween interpolation begins only after the ramp-in completes.
A common pattern is a continuous Additive Spin on rotation (base animation) paired with an Override Oscillate on the same property, set to awaitTrigger. When the player fires, the trigger starts a brief wobble that overrides the spin, then smoothly releases back to the continuous rotation.
The awaitTrigger flag (ShapeAnimationEntry.awaitTrigger) makes an animation entry stay dormant until explicitly triggered at runtime. While dormant, the entry's timer is frozen and it produces no output.
ShapePropertyAnimator.TriggerAnimation(layerId, entryId) or through the GlobalFX API via TriggerLayer(layerId) / TriggerLayerByName(layerName)When a GlobalFX layer has autoPlay enabled, calling TriggerLayerByName() on an idle layer will automatically start it using the layer's autoPlayMode before firing the trigger. This means you can design a Global layer with a continuous base animation (e.g. idle pulse) and trigger-reactive overlays (e.g. fire wobble) — calling TriggerLayerByName() handles the entire lifecycle.
Weapon-fire recoil wobble, hit confirm flashes, ability activation bursts — any brief reactive effect that should overlay a continuous base animation and automatically return to normal afterward.
When a shape is the base layer of a boolean group, its transform animation entries (Rotation, PositionX, PositionY, PositionXY, ScaleX, ScaleY, ScaleUniform) gain an Apply to Operands toggle (applyToOperands). When enabled, the animated delta computed for the base shape is also written to every consecutive operand shape in the GPU array at runtime.
This lets the entire boolean group move, rotate, or scale as a unit without requiring duplicate animation entries on each operand. The toggle appears per-entry in the Animations Panel and is only visible for transform properties on base layers of groups.
Apply to Operands is a runtime GPU-level operation — it writes deltas to the operand slots in the shape buffer. It is independent of the Link Operands setting on the layer, which controls editor-time parent/child transform propagation.
When a layer has Use Pivot enabled (layer.usePivot), rotation animations orbit the shape around the layer's pivot point in UV space instead of rotating around the shape's own center. This is useful for elements that should spin around a fixed point (e.g. a hand on a clock). For boolean groups, when a rotation entry has Apply to Operands enabled, operand shapes orbit around the base shape's position (or pivot), so the whole group rotates as a rigid body around that point.
The animation system is designed to cooperate cleanly with Category Transitions. During an active transition, ShapePropertyAnimator pauses — it skips rebuild while the CategoryTransitionAnimator (CTA) is interpolating between states.
When a transition finishes (postTransition = true), tween-in entries for certain "pop-prone" properties are force-completed — their elapsed time is set past their duration so they resolve immediately to their end value. This prevents a visual pop where a shape would snap from its base position back to a tween start offset. The force-completed properties are:
Non-pop properties like Rotation and PositionX/Y play their tween-in animation normally after the transition ends, providing a natural entrance effect.
The CategoryTransitionAnimator calls BuildTransitionAnimations to construct intermediate frames during a transition. It excludes all non-passthrough animations (not just tweens) from these transition frames, so that category animations start fresh in the ShapePropertyAnimator once the transition completes. The _wasInTransition flag in SPA tracks transition state for postTransition detection.
Multiple ShapePropertyAnimator instances can coexist in the same scene. The main reticle pipeline has one SPA, the Global FX pipeline has a second (configured via ConfigureAsGlobalFX), and the RenderTexture output pipeline creates a third (configured via ConfigureForRT). Each SPA independently tracks its own entry timers, sequence state, and override blend weights.
GlobalFXBridge routes TriggerLayer() calls to the correct SPA using HasLayerInShapes(layerId) — if a layer's shapes are being rendered by the RT manager's SPA, triggers are routed there; otherwise, they go to the main GlobalFX SPA.
Paused layers remain visible but their timers do not advance; stopped layers return to base state. See GlobalFX API for PauseLayer, StopLayer, TriggerLayer, and play modes.
The Animations Panel is a dedicated floating popup opened via the "Edit Animations" button in the layer properties panel. Each layer gets its own panel instance.
Entries are displayed in an accordion view — each animation is a collapsible card tracked by its unique entry ID. The panel shows:
[START] and [END] markers displayed inlinecolorA, colorB)awaitTrigger entries on the layer (auto-enables Preview if not active)At the top of the panel, a collapsible section provides: Sequence Mode toggle, Loop toggle, Loop Count field, and Ping-Pong toggle. These control the layer-level sequence configuration.
The panel is draggable via its title bar. Position persists in PlayerPrefs between sessions. The panel blocks scene interaction while visible via the IsOverVisiblePanel() bounds check, preventing accidental shape manipulation while editing animations.
In the layer list, layers with active animations display a ~ badge. Additional indicators are appended to the animation summary text:
| Badge | Meaning |
|---|---|
~ | Layer has one or more animation entries |
+ | At least one entry has cumulative mode enabled |
[H] | Entry has Hold on Complete enabled (sequence) |
[S] | Entry is marked as sequence Start |
[E] | Entry is marked as sequence End |
[OVR] | Entry uses Override blend mode |
[T] | Entry has awaitTrigger enabled |