Control hit markers, damage flashes, and persistent HUD widgets at runtime with a clean, event-driven API.
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:
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.
The IGlobalFXService is registered automatically when the GlobalFXBridge component initializes in the scene. Access it through the service locator:
| Accessor | Returns | Description |
|---|---|---|
RCEServices.GlobalFX | IGlobalFXService | Convenience property. Returns the service or null. |
RCEServices.Get<IGlobalFXService>() | IGlobalFXService | Generic accessor. Returns the service or null. |
RCEServices.Has<IGlobalFXService>() | bool | Check if the service is registered before accessing. |
The recommended pattern is to cache the reference once in Start() and null-check before use:
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); } }
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.
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.
| Method | Returns | Description |
|---|---|---|
GetGlobalLayerNames() | string[] | Get all layer names in the Global category. Returns empty array if none exist. |
GetLayerIdByName(layerName) | string | Get the internal GUID for a layer name. Returns null if not found. |
ShowLayerByName(layerName) | bool | Make a layer visible without starting its animations. |
PlayLayerByName(layerName, mode) | bool | Start animations and auto-show if hidden. mode controls post-sequence behavior. |
PauseLayerByName(layerName) | bool | Freeze animations at their current state. Layer stays visible. |
StopLayerByName(layerName, hide) | bool | Stop and reset animations. Pass hide: true to also hide the layer. |
HideLayerByName(layerName) | bool | Hide the layer and stop its animations. |
IsLayerVisibleByName(layerName) | bool | Query whether the layer is currently visible. |
IsLayerPlayingByName(layerName) | bool | Query whether the layer's animations are actively advancing. |
IsLayerPausedByName(layerName) | bool | Query whether the layer is visible but paused. |
TriggerLayerByName(layerName) | bool | Fire 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:
string[] layerNames = RCEServices.GlobalFX?.GetGlobalLayerNames() ?? Array.Empty<string>(); foreach (var name in layerNames) Debug.Log($"Global layer: {name}");
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:
| Method | Description |
|---|---|
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. |
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.
The LayerPlayMode enum controls what happens when a layer's animation sequence completes naturally. Pass it as the second argument to PlayLayerByName() or PlayLayer().
| Value | Behavior | Use 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 |
// 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);
GlobalFX exposes several events for tracking layer lifecycle and two read-only properties for inspecting current state:
| Event | Signature | Description |
|---|---|---|
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. |
| Property | Type | Description |
|---|---|---|
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:
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"); }
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.
Auto-play is evaluated once per edit-to-test or edit-to-runtime transition. The autoPlay and autoPlayMode fields on LayerData control this behavior:
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.
The most common GlobalFX pattern. Trigger a transient effect on damage — the layer shows, plays its animation sequence, and hides automatically.
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); } }
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.
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); } }
Persistent effects can be paused and resumed — useful for freeze-frame moments, pause menus, or slow-motion sequences.
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); } }
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.
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"); } }
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.
Use the OnLayerComplete event to trigger follow-up effects when a FireAndForget layer finishes — creating sequenced multi-stage visual feedback without coroutines.
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); } }
Understanding how GlobalFX fits into the rendering pipeline helps you reason about timing, ordering, and performance.
LoadoutManager.EmitGlobalShapes() ↓ GlobalFXBridge // filters to visible layers only ↓ ShapePropertyAnimator // applies per-layer animations ↓ ReticleRenderer #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.
GlobalFXBridge never modifies LayerData.visible — the editor's saved visibility state stays intact. Runtime visibility is controlled entirely through internal HashSet collections:
| File | Purpose |
|---|---|
Assets/RCE/Runtime/Rendering/IGlobalFXService.cs | Public interface definition |
Assets/RCE/Runtime/Rendering/GlobalFXBridge.cs | Implementation of IGlobalFXService |
Assets/RCE/Runtime/Bootstrap/RCEServices.cs | Service locator with GlobalFX property |
Assets/RCE/Runtime/Data/LayerPlayMode.cs | Persistent / FireAndForget enum |
Assets/RCE/Runtime/Loadout/LayerData.cs | autoPlay and autoPlayMode fields |
Assets/RCE/Runtime/Rendering/ShapePropertyAnimator.cs | Animation evaluation |
Assets/RCE/Runtime/Rendering/ReticleRenderer.cs | GPU shape rendering |