Skip to content

The bridge

A Loom bridge is a C# class tagged with [Bridge]. It defines the contract between your game code and your UI. Three things cross the wire:

DirectionMechanismWhen
C# → UI[BridgeState] Observable propertiesWhenever .Value changes
UI → C#[BridgeAction] methodsWhenever the UI calls bridge.actions.foo(...)
C# → UI[BridgeEvent] Event propertiesWhenever C# calls .Fire(payload)

Each member of your bridge class falls into one of these three buckets.

State

State is reactive. You declare it once with an initial value; the UI sees a typed Solid signal-like accessor.

C#
[BridgeState]
public Observable<float> Health { get; } = new(100f);
// later, on damage:
Health.Value = Mathf.Max(0f, Health.Value - 10f);
TS
const bridge = useBridge()
// reactive — re-renders when Health changes
bridge.state.health // number

The source generator names match by convention: C# Health becomes TS health. Acronyms (e.g. HUD, UI) follow C# casing — HudVisible becomes hudVisible.

Container types

Beyond scalar observables, Loom provides:

  • ObservableList — reactive list. UI sees array-like access with fine-grained reactivity (single-item changes don’t re-render the whole list).
  • Nested DTO classes. Properties of a [Bridge] class can be POCOs with their own Observable properties. The generated TS types nest the same way.

See the Observable and Event reference for the exact runtime semantics.

Actions

Actions are methods. The UI calls them; C# executes them on the main thread during the next pump.

C#
[BridgeAction]
public void TakeDamage(float amount) {
Health.Value = Mathf.Max(0f, Health.Value - amount);
}
TS
bridge.actions.takeDamage(10)

Actions are fire-and-forget; they don’t return values to the UI. If your UI needs the result of an action, the action should mutate state — which the UI sees reactively.

Events

Events are one-shot fan-outs. C# fires them; the UI listens.

C#
[BridgeEvent]
public Event<DamageEvent> Damaged { get; } = new();
// later:
Damaged.Fire(new DamageEvent { Amount = 10, IsCritical = true });
TS
import { onEvent } from '@loomgui/bridge'
onEvent('damaged', (e) => {
// e: { amount: number; isCritical: boolean }
flashRed()
})

Events carry typed payloads. The DTO class (DamageEvent above) is a plain C# class with [Serializable]-style fields — the source generator picks up its shape and types the TS callback parameter.

See the DTO conventions reference for the rules.

Example: the sample HUD

The in-repo Unity sample’s unity/sample/Assets/Scripts/SampleBridge.cs puts all three together in ~30 lines. Its unity/sample/UI/src/screens/Hud.tsx shows the matching Solid component.