Overview

The IGlobalFXService interface — implemented by GlobalFXBridge — is the public API for controlling Global category layers at runtime. Global layers are always-on effects like hit markers, damage flashes, barrel flashes, and persistent UI widgets that exist outside the main reticle state system (HUD / HipFire / SA / ADS).

Global layers render on a dedicated ReticleRenderer instance, completely isolated from the main reticle pipeline. This isolation guarantees three properties:

Design Principle

GlobalFX treats every layer as an independent effect. You show it, play it, hide it — and the engine handles animation timing, visibility, and cleanup. No manual timer management, no coroutine bookkeeping.


Accessing the Service

The IGlobalFXService is registered automatically when the GlobalFXBridge component initializes in the scene. Access it through the service locator:

AccessorReturnsDescription
RCEServices.GlobalFXIGlobalFXServiceConvenience property. Returns the service or null.
RCEServices.Get<IGlobalFXService>()IGlobalFXServiceGeneric accessor. Returns the service or null.
RCEServices.Has<IGlobalFXService>()boolCheck if the service is registered before accessing.

The recommended pattern is to cache the reference once in Start() and null-check before use:

HitMarkerController.cs
using RCE.Runtime.Bootstrap;
using RCE.Runtime.Rendering;
using RCE.Runtime.Data;

public class HitMarkerController : MonoBehaviour
{
    private IGlobalFXService _globalFX;

    void Start()
    {
        _globalFX = RCEServices.GlobalFX;
    }

    public void OnHit()
    {
        _globalFX?.PlayLayerByName("HitMarker", LayerPlayMode.FireAndForget);
    }
}
Early Access

If you're accessing GlobalFX very early (before scene components initialize), check for null or use RCEServices.Has<IGlobalFXService>() to avoid null-reference exceptions during bootstrap.


API Reference — By Name

These methods use the human-readable layer name you assign in the editor. Layer names are case-sensitive. All action methods return true if the layer was found and the operation succeeded, false if the name was not found in the Global category.

MethodReturnsDescription
GetGlobalLayerNames()string[]Get all layer names in the Global category. Returns empty array if none exist.
GetLayerIdByName(layerName)stringGet the internal GUID for a layer name. Returns null if not found.
ShowLayerByName(layerName)boolMake a layer visible without starting its animations.
PlayLayerByName(layerName, mode)boolStart animations and auto-show if hidden. mode controls post-sequence behavior.
PauseLayerByName(layerName)boolFreeze animations at their current state. Layer stays visible.
StopLayerByName(layerName, hide)boolStop and reset animations. Pass hide: true to also hide the layer.
HideLayerByName(layerName)boolHide the layer and stop its animations.
IsLayerVisibleByName(layerName)boolQuery whether the layer is currently visible.
IsLayerPlayingByName(layerName)boolQuery whether the layer's animations are actively advancing.
IsLayerPausedByName(layerName)boolQuery whether the layer is visible but paused.
TriggerLayerByName(layerName)boolFire all awaitTrigger animation entries on the layer. Each triggered entry resets its timer and plays once, then automatically re-arms. If the layer isn't playing but has autoPlay enabled, it is started automatically.

Use GetGlobalLayerNames() to discover available layers at runtime — useful for populating dropdowns or validating names in configuration files:

LayerDiscovery.cs
string[] layerNames = RCEServices.GlobalFX?.GetGlobalLayerNames()
    ?? Array.Empty<string>();

foreach (var name in layerNames)
    Debug.Log($"Global layer: {name}");

API Reference — By ID

These methods use the internal GUID string. They're useful when you've cached the ID from GetLayerIdByName() or received it from an event callback. The signatures mirror the by-name API:

MethodDescription
ShowLayer(layerId)Make a layer visible without starting animations.
PlayLayer(layerId, mode)Start animations and auto-show if hidden.
PauseLayer(layerId)Freeze animations at current state. Layer stays visible.
StopLayer(layerId, hide)Stop and reset animations. Optionally hide the layer.
HideLayer(layerId)Hide the layer and stop animations.
IsLayerVisible(layerId)Query if the layer is currently visible.
IsLayerPlaying(layerId)Query if animations are actively advancing.
IsLayerPaused(layerId)Query if visible but paused.
TriggerLayer(layerId)Fire all awaitTrigger animation entries on the layer. Auto-starts the layer if it has autoPlay enabled.
By Name vs. By ID

Prefer the by-name API for readability and ease of use. The by-ID API exists for performance-sensitive paths where you've pre-resolved the ID, or when handling event callbacks that provide an ID directly.


LayerPlayMode

The LayerPlayMode enum controls what happens when a layer's animation sequence completes naturally. Pass it as the second argument to PlayLayerByName() or PlayLayer().

ValueBehaviorUse Case
Persistent Layer stays visible after the sequence completes. Must be explicitly hidden via HideLayer() or StopLayer(hide: true). Compass, health indicators, status widgets
FireAndForget Layer automatically hides when its animation sequence ends naturally. No cleanup code needed. Hit markers, damage flashes, kill confirms
PlayModeExample.cs
// Transient effect — disappears automatically when the animation ends
_globalFX.PlayLayerByName("HitMarker", LayerPlayMode.FireAndForget);

// Persistent widget — stays visible until you explicitly hide it
_globalFX.PlayLayerByName("Compass", LayerPlayMode.Persistent);

Events & Read-Only State

GlobalFX exposes several events for tracking layer lifecycle and two read-only properties for inspecting current state:

Events

EventSignatureDescription
OnLayerComplete Action<string, bool> Fires when a layer completes its animation sequence or is manually stopped. Parameters: layerId (string) and wasAutoHidden (bool — true for FireAndForget natural end, false for manual stop).
OnLayerStopped Action<string> Fires when a layer is stopped via StopLayer() or StopLayerByName(). Subscribers should reset animation timers for the given layer ID.
OnGlobalFXStateChanged Action Fires whenever the visibility or play state of any Global layer changes (Show, Hide, Play, Pause, Stop, or structure rebuild). Useful for keeping external systems (e.g., RenderTexture outputs) in sync.

Read-Only Properties

PropertyTypeDescription
VisibleLayerIds IReadOnlyCollection<string> The set of Global layer IDs currently considered visible. null when the service is not yet initialized.
PlayingLayerIds IReadOnlyCollection<string> The set of Global layer IDs whose animations are currently advancing. null when the service is not yet initialized.

Always unsubscribe in OnDestroy() to avoid memory leaks from stale references:

EventSubscription.cs
private IGlobalFXService _globalFX;

void Start()
{
    _globalFX = RCEServices.GlobalFX;
    if (_globalFX != null)
        _globalFX.OnLayerComplete += HandleLayerComplete;
}

void OnDestroy()
{
    if (_globalFX != null)
        _globalFX.OnLayerComplete -= HandleLayerComplete;
}

void HandleLayerComplete(string layerId, bool wasAutoHidden)
{
    if (wasAutoHidden)
        Debug.Log($"Layer {layerId} finished and auto-hid");
    else
        Debug.Log($"Layer {layerId} was manually stopped");
}

Auto-Play System

Layers can be configured to play automatically when the game starts — no code required. This is useful for always-on widgets like a compass overlay or persistent status indicator.

Editor Setup

  1. Select a layer in the GBL (Global) category tab
  2. In the LAYER section of the properties panel, enable Auto-Play
  3. Choose the Play Mode (Persistent or FireAndForget)

Runtime Behavior

Auto-play is evaluated once per edit-to-test or edit-to-runtime transition. The autoPlay and autoPlayMode fields on LayerData control this behavior:

No Double-Play

Auto-play fires once at startup. If you also call PlayLayerByName() in your own Start(), the layer simply restarts — there's no conflict or duplication.


Usage Patterns

Hit Marker (FireAndForget)

The most common GlobalFX pattern. Trigger a transient effect on damage — the layer shows, plays its animation sequence, and hides automatically.

DamageController.cs
public class DamageController : MonoBehaviour
{
    private IGlobalFXService _globalFX;

    void Start() => _globalFX = RCEServices.GlobalFX;

    public void OnDamageDealt(float damage, bool isCritical)
    {
        if (isCritical)
            _globalFX?.PlayLayerByName("CritHitMarker", LayerPlayMode.FireAndForget);
        else
            _globalFX?.PlayLayerByName("HitMarker", LayerPlayMode.FireAndForget);
    }
}

Persistent Widget (Show / Hide / Toggle)

For widgets that stay on-screen until the player dismisses them — like a compass, ammo counter, or status indicator — use Persistent mode and manage visibility manually.

CompassController.cs
public class CompassController : MonoBehaviour
{
    private IGlobalFXService _globalFX;

    void Start() => _globalFX = RCEServices.GlobalFX;

    public void ShowCompass()
    {
        _globalFX?.PlayLayerByName("Compass", LayerPlayMode.Persistent);
    }

    public void HideCompass()
    {
        _globalFX?.HideLayerByName("Compass");
    }

    public void ToggleCompass()
    {
        if (_globalFX != null && _globalFX.IsLayerVisibleByName("Compass"))
            _globalFX.HideLayerByName("Compass");
        else
            _globalFX?.PlayLayerByName("Compass", LayerPlayMode.Persistent);
    }
}

Damage Flash with Pause

Persistent effects can be paused and resumed — useful for freeze-frame moments, pause menus, or slow-motion sequences.

HealthUI.cs
public class HealthUI : MonoBehaviour
{
    private IGlobalFXService _globalFX;

    void Start() => _globalFX = RCEServices.GlobalFX;

    public void OnLowHealth()
    {
        // Start pulsing damage indicator
        _globalFX?.PlayLayerByName("LowHealthPulse", LayerPlayMode.Persistent);
    }

    public void OnPauseMenu()
    {
        // Freeze animation while game is paused
        _globalFX?.PauseLayerByName("LowHealthPulse");
    }

    public void OnResumeGame()
    {
        // Resume from where it left off
        _globalFX?.PlayLayerByName("LowHealthPulse", LayerPlayMode.Persistent);
    }

    public void OnHealthRestored()
    {
        // Stop and hide the indicator
        _globalFX?.StopLayerByName("LowHealthPulse", hide: true);
    }
}

Trigger-Based Reactive Effects

The most efficient pattern for rapid-fire reactive effects. Design a Global layer with a persistent base animation and awaitTrigger Override entries. The trigger fires a brief reactive animation that overrides the base, then smoothly releases back.

WeaponFireController.cs
public class WeaponFireController : MonoBehaviour
{
    private IGlobalFXService _globalFX;

    void Start() => _globalFX = RCEServices.GlobalFX;

    public void OnWeaponFired()
    {
        // Fires all awaitTrigger entries on the layer.
        // Auto-starts the layer if it has autoPlay enabled.
        _globalFX?.TriggerLayerByName("WeaponRecoil");
    }
}
No Cleanup Needed

Triggered Override entries automatically re-arm after completing their ramp-out phase. You can call TriggerLayerByName() as rapidly as needed — each call resets the entry timer and starts a fresh override cycle.

Chaining Effects via OnLayerComplete

Use the OnLayerComplete event to trigger follow-up effects when a FireAndForget layer finishes — creating sequenced multi-stage visual feedback without coroutines.

EffectChainer.cs
public class EffectChainer : MonoBehaviour
{
    private IGlobalFXService _globalFX;

    void Start()
    {
        _globalFX = RCEServices.GlobalFX;
        if (_globalFX != null)
            _globalFX.OnLayerComplete += OnEffectComplete;
    }

    void OnDestroy()
    {
        if (_globalFX != null)
            _globalFX.OnLayerComplete -= OnEffectComplete;
    }

    public void TriggerExplosion()
    {
        _globalFX?.PlayLayerByName("ExplosionFlash", LayerPlayMode.FireAndForget);
    }

    void OnEffectComplete(string layerId, bool wasAutoHidden)
    {
        // Chain: flash → smoke → debris
        string flashId = _globalFX.GetLayerIdByName("ExplosionFlash");
        string smokeId = _globalFX.GetLayerIdByName("ExplosionSmoke");

        if (layerId == flashId)
            _globalFX.PlayLayerByName("ExplosionSmoke", LayerPlayMode.FireAndForget);
        else if (layerId == smokeId)
            _globalFX.PlayLayerByName("ExplosionDebris", LayerPlayMode.FireAndForget);
    }
}

Architecture

Understanding how GlobalFX fits into the rendering pipeline helps you reason about timing, ordering, and performance.

Rendering Pipeline

GlobalFX Pipeline
LoadoutManager.EmitGlobalShapes()
       ↓
GlobalFXBridge // filters to visible layers onlyShapePropertyAnimator // applies per-layer animationsReticleRenderer #2 // dedicated Global renderer instance

The main reticle pipeline (HUD / HF / SA / ADS) uses a separate ReticleRenderer instance, ensuring Global effects never interfere with combat state rendering. Each pipeline gets its own StructuredBuffer, its own draw call, and its own animation state.

Visibility Management

GlobalFXBridge never modifies LayerData.visible — the editor's saved visibility state stays intact. Runtime visibility is controlled entirely through internal HashSet collections:

Key Files

FilePurpose
Assets/RCE/Runtime/Rendering/IGlobalFXService.csPublic interface definition
Assets/RCE/Runtime/Rendering/GlobalFXBridge.csImplementation of IGlobalFXService
Assets/RCE/Runtime/Bootstrap/RCEServices.csService locator with GlobalFX property
Assets/RCE/Runtime/Data/LayerPlayMode.csPersistent / FireAndForget enum
Assets/RCE/Runtime/Loadout/LayerData.csautoPlay and autoPlayMode fields
Assets/RCE/Runtime/Rendering/ShapePropertyAnimator.csAnimation evaluation
Assets/RCE/Runtime/Rendering/ReticleRenderer.csGPU shape rendering

What's Next