Animated transitions between combat states. 10 transition types with per-pair configuration, smart shape matching, and interrupt support.
When the active combat state changes — for example, switching from Hip Fire to ADS — the CategoryTransitionAnimator interpolates between the two shape arrays to produce a smooth visual handoff. All transition logic runs on the CPU; no shader modifications are required.
RCE's four combat categories (HUD, Hip Fire, Sighted/SA, and ADS) form six directional pairs:
Each pair can be configured independently with its own transition type, duration, easing curve, and type-specific parameters. A master toggle enables or disables the entire transition system, and each pair has its own enabled toggle for granular control. Transition settings are stored per-loadout in LoadoutData.transitions and serialized with the loadout JSON.
When the editor is in edit mode, transitions are bypassed entirely — state changes apply instantly so you can iterate on designs without waiting for animations to play.
Transitions sit between state selection and the animation system: LoadoutManager → CategoryTransitionAnimator → ShapePropertyAnimator → LoadoutRendererBridge → ReticleRenderer. The animator emits interpolated shape frames that flow downstream exactly like static shape data.
RCE ships with 10 CPU-side transition types. Each type defines how shapes from the outgoing state transform into the incoming state. Two types accept additional parameters; the rest are self-contained.
| # | Type | Description | Extra Params |
|---|---|---|---|
| 0 | Morph | Matched shapes lerp all properties (position, scale, color, glow, etc.); unmatched shapes crossfade in/out. The only type that uses smart shape matching. | — |
| 1 | CrossFade | Simultaneous fade-out of old shapes and fade-in of new shapes. No shape matching — both sets render simultaneously with opposing alpha ramps. | — |
| 2 | ScaleOutIn | Two-phase transition: old shapes shrink to zero scale, then new shapes grow from zero. Clean and mechanical. | — |
| 3 | Pop | New shapes appear with a bouncy scale overshoot using an EaseOutBack curve. Feels punchy and responsive. | — |
| 4 | Slide | Old shapes slide off-screen in a configurable direction, then new shapes slide in from the opposite edge. | slideDirection |
| 5 | RadialWipe | A radial reveal from the center outward. Old shapes are masked away as the wipe expands, revealing new shapes behind them. | — |
| 6 | StaggerFade | Shapes fade out one-by-one in sequence with stagger timing, then new shapes fade in one-by-one. Creates a cascade effect. | — |
| 7 | Spiral | Shapes rotate and scale simultaneously in a vortex pattern as they exit, and spiral back in for the new state. | — |
| 8 | ExplodeImplode | Old shapes scatter outward from center (explode), then new shapes gather inward (implode). Dramatic and attention-grabbing. | — |
| 9 | ColorFlash | A three-phase flash: shapes fade to a configurable flash color, hold briefly, then resolve to the new state. Useful for hit confirmations or state alerts. | flashColor |
Dissolve (10) and Blur (11) are planned as Part 2 additions. These will require shader-side support and are not yet available.
Each of the six directional pairs is configured with a TransitionPairSettings object containing:
| Field | Type | Description |
|---|---|---|
transitionType | TransitionType | Which of the 10 transition types to use for this pair. |
duration | float | Total transition time in seconds. |
easingType | EasingType | The easing curve applied to the transition's progress value. |
enabled | bool | Whether this specific pair animates. When disabled, the state change is instant. |
slideDirection | SlideDirection | Left, Right, Up, or Down. Only used by the Slide transition type. |
flashColor | Color | The intermediate flash color. Only used by the ColorFlash transition type. |
The TransitionSettingsMap holds all six pairs as named fields — hfToSa, saToHf, saToAds, adsToSa, hfToAds, adsToHf — and provides a Get(from, to) method that resolves the correct settings for any state change. This means the transition from Hip Fire to ADS can be completely different from ADS back to Hip Fire: different types, different durations, different easing.
All transition settings live in LoadoutData.transitions and are serialized as part of the loadout JSON. The master toggle is stored separately in PlayerPrefs (ADS_TransitionEnabled) so it persists across loadouts. Loadouts created before the transition system (v1) are automatically migrated with default settings when loaded.
The Morph transition type needs to decide which shape in the outgoing state corresponds to which shape in the incoming state so it can smoothly interpolate between them. The ShapeTransitionMatcher handles this with a multi-factor scoring algorithm.
| Factor | Weight | Description |
|---|---|---|
| Shape type match | +0.50 | Same shapeType between the two shapes. This is the strongest signal — a Circle will strongly prefer matching to another Circle. |
| Position proximity | +0.00 to +0.25 | How close the two shapes are in UV space. Shapes near the same screen position score higher. |
| Scale / area ratio | +0.00 to +0.25 | How similar the two shapes are in size (scale.x * scale.y). Similarly-sized shapes score higher. |
The maximum possible score is 1.0 (same type, same position, same size). Any candidate pair scoring below the 0.3 minimum threshold is rejected — the shapes are too dissimilar to morph convincingly and will instead crossfade independently.
After scoring all possible from/to pairs, the matcher sorts them by score in descending order and assigns greedily: the highest-scoring pair is assigned first, then both shapes are removed from the candidate pool. This continues until no more valid pairs remain. Any unmatched shapes crossfade in or out independently.
Shapes that are operands in a boolean group (groupShapeIndex > 0) are excluded from the matching process entirely. During any transition type, operands are forced invisible — only standalone shapes and group base layers participate. Group metadata is zeroed during the transition so all shapes render as standalone; the original group structure is restored when the transition completes.
The match result is calculated at the start of each transition and reused for every interpolated frame. This keeps per-frame cost minimal regardless of shape count.
Each transition pair has a configurable easing curve that shapes the interpolation progress over time. The easing applies to the normalized t value (0 → 1) before it's fed into the transition type's interpolation logic.
Available easing types include:
The full set of easing functions is defined in Easing.cs. The easing type configured on a pair is separate from any easing used internally by a specific transition type — for example, Pop always uses EaseOutBack for its scale overshoot regardless of the pair's easing setting, but the pair's easing still controls the overall transition progress.
State changes can arrive mid-transition. If the player switches from Hip Fire to SA and then immediately to ADS before the first transition finishes, the system handles this seamlessly.
When an interrupt occurs, the CategoryTransitionAnimator captures the current _interpolatedShapes — the partially-blended shape array at the exact moment of interruption — and uses it as the new "from" state. A fresh transition then begins from this intermediate snapshot to the newly-requested target state.
This means:
The animator tracks state via _previousState, _pendingFromState, and _pendingToState to correctly resolve the transition pair even when interrupts chain rapidly.
The Transitions Panel is a floating, non-blocking editor panel (TransitionsPanel.uxml) that provides visual configuration for all six transition pairs.
The panel displays an accordion of six collapsible sections, one for each directional pair. Each section provides:
| Control | Description |
|---|---|
| Enabled toggle | Enable or disable this specific pair. Disabled pairs show an "OFF" badge. |
| Type dropdown | Select from the 10 available transition types. |
| Duration slider | Set the transition duration in seconds. The current value is shown as a badge on the collapsed header. |
| Easing dropdown | Choose the easing curve for this pair's interpolation. |
| SlideDirection | Appears only when the Slide type is selected. Choose Left, Right, Up, or Down. |
| FlashColor | Appears only when the ColorFlash type is selected. Preset dropdown and color swatch. |
The "Copy First → All" button copies the first pair's complete settings (type, duration, easing, and any type-specific params) to all six pairs. This is a fast way to set a uniform transition style and then customize individual pairs as needed.
The Shape Property Animator and Category Transition Animator are designed to work together without conflict, but their interaction requires careful coordination.
While a category transition is active, shape property animations pause. The ShapePropertyAnimator skips its rebuild cycle when IsTransitioning is true, ensuring animated deltas don't fight with the transition interpolation. Animation timers continue to advance so delay and ramp calculations remain correct when animations resume.
When a transition ends, there is a risk of visual "pop" — a frame where shapes snap from their animated starting offset to their base value before the tween-in can begin. To prevent this, the system force-completes tween-in entries for certain properties immediately after the transition finishes:
Force-completed entries have their elapsed time set past their duration so they resolve to their final value on the first post-transition frame. This keeps alpha and scale stable while allowing rotational and positional animations to play their full intro.
The CategoryTransitionAnimator also excludes all non-passthrough animation entries from its internal BuildTransitionAnimations call, ensuring category-specific animations start fresh in the ShapePropertyAnimator after the transition rather than mid-way through.
SetActiveState()