State API

SetActiveState(LoadoutState) is the primary runtime API for controlling which reticle category is displayed. You call it from your game's input system whenever the player's combat posture changes. RCE handles all transition animations, shape blending, and GPU upload automatically.

Enum ValueConstantTypical Trigger
HUD LoadoutState.HUD Always-on elements — set once at startup, rarely changed at runtime
Situational Awareness LoadoutState.SituationalAwareness Default idle state — no weapon input active
Hip Fire LoadoutState.HipFire Fire button pressed without aiming
Aim Down Sight LoadoutState.AimDownSight ADS / right-click / left trigger held
Performance

Only call SetActiveState() when the state actually changes. Calling it every frame with the same value is safe but wasteful — RCE performs an equality check internally, but the call overhead is unnecessary. Guard with a simple if (newState != currentState). See the Performance page for more guidance.

Unity Input System (Gamepad)

GamepadStateDriver.cs
using UnityEngine;
using UnityEngine.InputSystem;
using RCE.Runtime.Data;
using RCE.Runtime.Bootstrap;

public class GamepadStateDriver : MonoBehaviour
{
    private ILoadoutManager _loadout;
    private LoadoutState _current = LoadoutState.SituationalAwareness;

    void Start()
    {
        _loadout = RCEServices.Get<ILoadoutManager>();
    }

    void Update()
    {
        var gp = Gamepad.current;
        if (gp == null) return;

        LoadoutState next;

        if (gp.leftTrigger.IsPressed())
            next = LoadoutState.AimDownSight;
        else if (gp.rightTrigger.IsPressed())
            next = LoadoutState.HipFire;
        else
            next = LoadoutState.SituationalAwareness;

        if (next != _current)
        {
            _loadout.SetActiveState(next);
            _current = next;
        }
    }
}

Classic Input Manager (Keyboard + Mouse)

KeyboardStateDriver.cs
using UnityEngine;
using RCE.Runtime.Data;
using RCE.Runtime.Bootstrap;

public class KeyboardStateDriver : MonoBehaviour
{
    private ILoadoutManager _loadout;
    private LoadoutState _current = LoadoutState.SituationalAwareness;

    void Start()
    {
        _loadout = RCEServices.Get<ILoadoutManager>();
    }

    void Update()
    {
        LoadoutState next;

        if (Input.GetMouseButton(1))       // Right-click = ADS
            next = LoadoutState.AimDownSight;
        else if (Input.GetMouseButton(0))  // Left-click = Hip Fire
            next = LoadoutState.HipFire;
        else
            next = LoadoutState.SituationalAwareness;

        if (next != _current)
        {
            _loadout.SetActiveState(next);
            _current = next;
        }
    }
}

ILoadoutManager

ILoadoutManager is the primary interface for controlling the reticle system at runtime. It manages the active combat state, provides access to the current loadout data, and bridges your game logic to the rendering pipeline.

MemberDescription
SetActiveState(LoadoutState) Switches the active combat category. Triggers transition animation if configured for this state pair.
SetEditState(LoadoutState) Sets the category being edited in the visual editor. Does not trigger transitions — used by editor UI only.
GetCurrentState() Returns the currently active LoadoutState.
LoadoutData ActiveLoadout The currently loaded loadout data. Read-only at runtime; modifications go through the editor or serialization layer.
LoadoutManagerUsage.cs
using RCE.Runtime.Data;
using RCE.Runtime.Bootstrap;

public class WeaponController : MonoBehaviour
{
    private ILoadoutManager _loadout;

    void Awake()
    {
        _loadout = RCEServices.Get<ILoadoutManager>();
    }

    public void OnAimStart()
    {
        _loadout.SetActiveState(LoadoutState.AimDownSight);
    }

    public void OnAimEnd()
    {
        _loadout.SetActiveState(LoadoutState.SituationalAwareness);
    }

    public void OnFireStart()
    {
        if (_loadout.GetCurrentState() != LoadoutState.AimDownSight)
            _loadout.SetActiveState(LoadoutState.HipFire);
    }

    public void OnFireEnd()
    {
        if (_loadout.GetCurrentState() == LoadoutState.HipFire)
            _loadout.SetActiveState(LoadoutState.SituationalAwareness);
    }
}

Custom Storage Paths (IDataPathProvider)

IDataPathProvider is the interface RCE uses to resolve file system paths for all persistent data — loadouts, tracers, and color palettes. The default implementation uses Application.persistentDataPath, but you can provide your own to redirect storage anywhere: a custom user directory, a cloud-sync folder, or a game-specific save location.

MemberDescription
string UserDataPath Root directory for all RCE user data.
string LoadoutsPath Directory where loadout JSON files are stored.
string TracersPath Directory for tracer configuration files.
string PalettesPath Directory for color palette files.
EnsureDirectoriesExist() Creates all required directories if they don't already exist. Called automatically on startup.

Default Implementation

DefaultDataPathProvider.cs
using UnityEngine;
using System.IO;

public class DefaultDataPathProvider : IDataPathProvider
{
    private readonly string _root;

    public DefaultDataPathProvider()
    {
        _root = Path.Combine(Application.persistentDataPath, "RCE");
    }

    public string UserDataPath  => _root;
    public string LoadoutsPath => Path.Combine(_root, "Loadouts");
    public string TracersPath  => Path.Combine(_root, "Tracers");
    public string PalettesPath => Path.Combine(_root, "Palettes");

    public void EnsureDirectoriesExist()
    {
        Directory.CreateDirectory(LoadoutsPath);
        Directory.CreateDirectory(TracersPath);
        Directory.CreateDirectory(PalettesPath);
    }
}

Custom Implementation

CustomDataPathProvider.cs
using System.IO;

public class CustomDataPathProvider : IDataPathProvider
{
    private readonly string _root;

    public CustomDataPathProvider(string basePath)
    {
        _root = Path.Combine(basePath, "RCE");
    }

    public string UserDataPath  => _root;
    public string LoadoutsPath => Path.Combine(_root, "Loadouts");
    public string TracersPath  => Path.Combine(_root, "Tracers");
    public string PalettesPath => Path.Combine(_root, "Palettes");

    public void EnsureDirectoriesExist()
    {
        Directory.CreateDirectory(LoadoutsPath);
        Directory.CreateDirectory(TracersPath);
        Directory.CreateDirectory(PalettesPath);
    }
}
Tip

Register your custom IDataPathProvider before RCE initializes. If you're using the built-in RCEInstaller, override its path provider binding. If you have your own DI container, bind the interface there instead.


Service Locator (RCEServices)

RCEServices is a lightweight static service locator that provides access to all RCE engine interfaces at runtime. Services are registered automatically by RCEInstaller during startup — no manual configuration needed.

MemberDescription
T Get<T>() Resolves and returns the registered implementation of interface T. Throws if not registered.
bool Has<T>() Returns true if an implementation of T is registered. Use to check optional services before resolving.
GlobalFXBridge GlobalFX Convenience property for quick access to the GlobalFX system. Equivalent to Get<GlobalFXBridge>().
ServiceLocatorExamples.cs
using RCE.Runtime.Bootstrap;
using RCE.Runtime.Data;

// Resolve the loadout manager
var loadout = RCEServices.Get<ILoadoutManager>();
loadout.SetActiveState(LoadoutState.HipFire);

// Access GlobalFX for hit markers
var globalFX = RCEServices.GlobalFX;
globalFX?.PlayLayerByName("HitMarker", LayerPlayMode.FireAndForget);

// Check if a service is available before using it
if (RCEServices.Has<IColorPaletteStorage>())
{
    var palettes = RCEServices.Get<IColorPaletteStorage>();
}

DI / Installer (RCEInstaller)

RCEInstaller is the bootstrap component that registers all RCE engine services on startup. It runs automatically when the RCE prefab is loaded — you don't need to call anything manually.

Services Registered by RCEInstaller

InterfacePurpose
ILoadoutManagerCombat state management, active loadout access
ILoadoutStorageLoadout file persistence (JSON read/write)
ITracerStorageTracer configuration persistence
IColorPaletteStorageColor palette persistence
IThemeProviderEditor UI theme resolution
IDataPathProviderFile path configuration (overridable)

Using Your Own DI Container

If your project already uses a dependency injection framework like Zenject or VContainer, you can bypass RCEInstaller and bind RCE's interfaces directly in your container. This gives you full control over lifetimes, scoping, and constructor injection.

ZenjectExample.cs — Zenject Installer
using Zenject;
using RCE.Runtime.Bootstrap;
using RCE.Runtime.Loadout;
using RCE.Runtime.Palettes;

public class GameInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        // Override path provider with your own
        Container.Bind<IDataPathProvider>()
            .To<CustomDataPathProvider>()
            .AsSingle();

        // Bind RCE engine services
        Container.Bind<ILoadoutManager>()
            .To<LoadoutManager>()
            .AsSingle();

        Container.Bind<ILoadoutStorage>()
            .To<LoadoutStorage>()
            .AsSingle();

        Container.Bind<IColorPaletteStorage>()
            .To<ColorPaletteStorage>()
            .AsSingle();
    }
}
VContainerExample.cs — VContainer Lifetime Scope
using VContainer;
using VContainer.Unity;
using RCE.Runtime.Bootstrap;
using RCE.Runtime.Loadout;

public class GameLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        builder.Register<IDataPathProvider, CustomDataPathProvider>(Lifetime.Singleton);
        builder.Register<ILoadoutManager, LoadoutManager>(Lifetime.Singleton);
        builder.Register<ILoadoutStorage, LoadoutStorage>(Lifetime.Singleton);
    }
}

Loadout Serialization

Loadouts are stored as plain JSON files with a versioned schema (currently v8). The LoadoutSerializer handles reading, writing, and automatic migration between schema versions — older loadout files are transparently upgraded when loaded.

Persistence Interface

ILoadoutStorage abstracts the file I/O layer. The default implementation writes JSON to the path provided by IDataPathProvider, but you can replace it to persist loadouts anywhere — a database, a REST API, or a cloud storage backend.

LoadoutPersistence.cs
var storage = RCEServices.Get<ILoadoutStorage>();

// List all saved loadouts
var names = storage.GetLoadoutNames();

// Load a specific loadout
LoadoutData loadout = storage.Load("MyReticle");

// Save the current loadout
storage.Save(loadout);

Schema Versioning

VersionChanges
v1Initial schema — basic shapes, colors, transforms
v2Added transition settings (TransitionSettingsMap)
v5Custom shape points (customPoints, customPathClosed)
v6Boolean groups, palettes, full loadout serialization
v8Animation sequence system, unique entry IDs
Modding Friendly

Because loadouts are plain JSON, they're inherently modding-friendly. Players and community tools can create, share, and import loadout files directly. RCE's automatic version migration ensures that loadouts created with older versions of the engine always load correctly in newer releases — no manual conversion needed.


Loadout Format Overview

Understanding the loadout data structure helps when building tools, writing importers, or debugging serialization issues. Here's the high-level hierarchy:

LoadoutData Structure
LoadoutData
├── name: string                          // Display name
├── version: int                           // Schema version (currently 8)
├── categories: CategoryData[5]            // HUD, SA, HF, ADS, Global
│   └── CategoryData
│       └── layers: List<LayerData>
│           └── LayerData
│               ├── id: string              // Unique layer GUID
│               ├── name: string            // Display name
│               ├── shapeType: ShapeType    // Circle, Ring, Polygon, etc.
│               ├── position: Vector2      // UV-space position
│               ├── rotation: float        // Degrees
│               ├── scale: Vector2         // X/Y scale
│               ├── color: Color           // Fill color + alpha
│               ├── outlineColor: Color    // Outline color + alpha
│               ├── glowIntensity: float
│               ├── glowSize: float
│               ├── feather: float
│               ├── animations: List<ShapeAnimationEntry>
│               │   └── ShapeAnimationEntry
│               │       ├── id: string      // Unique entry GUID
│               │       ├── property: AnimatableProperty
│               │       ├── mode: AnimationMode
│               │       └── ... speed, amplitude, frequency, etc.
│               ├── customPoints: List<CustomControlPoint>
│               └── customPathClosed: bool
└── transitions: TransitionSettingsMap
    ├── hfToSa: TransitionPairSettings
    ├── saToHf: TransitionPairSettings
    ├── saToAds: TransitionPairSettings
    ├── adsToSa: TransitionPairSettings
    ├── hfToAds: TransitionPairSettings
    └── adsToHf: TransitionPairSettings
        ├── transitionType: TransitionType
        ├── duration: float
        ├── easingType: EasingType
        ├── enabled: bool
        ├── slideDirection: SlideDirection
        └── flashColor: Color

Each LayerData maps to one ShapeData struct (144 bytes) that gets uploaded to the GPU structured buffer. The C# struct and HLSL struct must match exactly — see the Architecture page for details on the binary layout.


What's Next