Skip to content

Tutorial: Reactive Logic with Reactor

Hook scene behaviour onto changes — a value crossing a threshold, an event firing, a signal arriving, a frame cycle beginning. The Reactor component is the host for that wiring: zero scripting, all in the Inspector, all reusing the same operation/command vocabulary you already use elsewhere.

By the end of this tutorial you'll have a Reactor that:

  • Fires a "level up" sound when an XP float crosses 100.
  • Plays an animation while a signal is being broadcast.
  • Stops a process when a bool flips to true.

Prerequisites: Basic familiarity with the Process system and DataStorage.


Mental Model

A Reactor hosts a list of targets. Each target owns one source and a list of (trigger, body) reactions.

Reactor
└─ Targets
   ├─ Float target ─── source: IObservableFloatValue
   │   └─ Reactions
   │       ├─ Threshold (Above 100) → [IProcess body]
   │       └─ Range (0..100)        → [IProcess body]
   └─ Signal target ── source: SignalType
       └─ Reactions → [IProcess body]

Each target is dedicated to one source. Inside the target, triggers decide when to fire and bodies decide what to run.

The Reactor itself is the lifecycle: when it begins, every target subscribes; when it ends, every target unsubscribes.


1. Add the Reactor

Create a GameObject named GameLogic (or use any controller you already have). Add the Reactor component.

Add Component → Jungle → Reactions → Reactor

You'll see a Targets list. Each item is a typed target.


2. Set Up an XP Threshold Reaction

Goal: when the player's XP float value crosses 100, play a level-up sound.

Prepare the source value

Create a DataStorageAsset named PlayerStats with a float entry keyed by an XP Key. (See Data for the basics.)

Add a Float target

Click + on Targets. Pick Float Target (or FloatReactionTarget, whichever the picker shows).

Configure:

  • SourceObservableFloatValueFromKey reading the XP key on PlayerStats.

Add a reaction with a threshold trigger

Inside the target, click + on Reactions. Pick Threshold Float Trigger:

Field Value
Comparison Above
Threshold 100
Hysteresis 5 (prevents flutter near the boundary)

Wire the body

The reaction's Body is a List<IProcess>. The picker is filtered to commands and operations that contain at least one float-reactive op — anything implementing IFloatReactionTarget.

Pick a command (e.g. an AudioCommand) and inside it pick a PlayAudioOperation. The Reactor auto-wires the operation's ReactionInput: SmartFloat slot to the observed XP value the first time the body dispatches — no manual setup needed.

Press Play and bump the XP key past 100. The sound plays. Drop it back below 95 (95 = 100 − hysteresis) and the threshold re-arms.

Triggers vs sources

The source decides what's observed. The trigger decides what condition counts as "fire". A single source can host many triggers in the same target — fire on each change, fire when above 100, fire when between 50 and 75 — each running its own body.


3. Run a Process While a Signal Is Broadcasting

Goal: while a "level-up celebration" signal is being broadcast, play a flashing animation. Stop the animation when the signal stops.

Prepare the signal

Create a SignalType asset named LevelUpSignal.

Add a Signal target

Click + on Targets. Pick Signal Target.

  • Source — drag the LevelUpSignal SignalType in.

Inside, add a reaction. The signal trigger fires when Reactor.Receive(LevelUpSignal) is called externally. The body runs the matching process.

Raise the signal

The signal arrives via someone calling Reactor.Receive(signal). Common ways:

  • A Raise Signal On Transmitter or Raise Signal On Owner Transmitter operation wired to fire from another process. (See LocalSignalTransmitter for the per-instance pub/sub side of signals.)
  • A JointBreakSignalSource on a joint, which pushes a signal into a configured Reactor when the joint breaks.
  • Your own code calling reactor.Receive(LevelUpSignal).

When the signal arrives, the body runs. When the body finishes, the reaction rearms for the next signal.

Overlap policy

If a second signal arrives while the body is still running, the trigger's overlap policy decides:

Strategy Behaviour
Complete and Restart Finish the current body, then start fresh.
Complete Finish without restarting.
Restart Restart immediately.
Toggle Pause Pause / unpause the running body.
Ignore Discard the new signal.

Default is usually Complete and Restart.


4. Bool Target — Run While True

Goal: while a "Door Open" bool is true, play an ambient sound.

Add a Bool target

  • Source — ObservableBoolValueFromKey on a "DoorOpen" key.
  • Add a reaction. Pick the On True trigger (or whatever the bool-trigger options call it). The body begins when the bool flips true and is asked to complete when the bool flips false.

This is exactly how the previous Watcher's ValueChangedEntry worked, but with typed access to the bool value inside the body if you need it.


5. Event Target

Goal: when a IEvent raises — a UI button, a video finished, a duration timer — run something.

  • Source — any IEvent. The picker offers all event types including EventFromAsset and SignalEventFromHub.
  • Body — runs once per raise. The trigger here is simply "raised".

This is the canonical way to wire a designer-configured "X happens → do Y" rule without writing a MonoBehaviour.


6. Frame Cycle Target

Some targets watch a frame cycle — a per-frame begin/end pair, often emitted by another component's lifecycle. Use these when you want a body to run while something is happening (and stop when it stops), rather than only on edges.

A common case: subscribe to an AttachableObject's Attached cycle so a Reactor body runs while the attachable is held.


7. Retrigger and Interrupt

Two policies control how the Reactor handles edges and shutdown:

  • ProcessRetriggerStrategy — what happens when the trigger re-fires while the body is still running (per-reaction).
  • ReactionInterruptPolicy — what happens to in-flight bodies when the Reactor itself ends:
  • ForceComplete — snap each in-flight process to its final state.
  • Cancel — leave each at its current state and stop ticking it.

Pick ForceComplete for state changes that must land (sound off, animation reset). Pick Cancel for fire-and-forget effects that should just stop.


8. Auto-Wiring Body Inputs

Many body operations expose a SmartX ReactionInput slot — e.g. IFloatReactionTarget exposes SmartFloat ReactionInput. On first dispatch the Reactor wires the slot to the observed value. The operation sees the current value when it runs without any manual configuration. Disable this only when you specifically want to feed the operation a different source.


9. Reactor vs LocalSignalTransmitter

Both route signals, but they're different surfaces:

Surface Routing scope When to use
Reactor + Signal target The Reactor's lifecycle. Push via Reactor.Receive(signal). When the signal feeds into typed reactions on a Reactor.
LocalSignalTransmitter Per-GameObject. Subscribe via SignalEventFromHub, raise via RaiseSignalOnTransmitter. When you want pub/sub on a single GameObject without going through a Reactor.

A SignalEventFromHub can also be plugged into a Reactor's Event target, so the two surfaces compose: instance-local pub/sub on the LocalSignalTransmitter side, typed reactions on the Reactor side.


Next Steps