Skip to content

Inter-Agent Communication

The signal system enables typed, decoupled messaging between GameObjects via GOAP actions. An agent sends a signal; listeners on the target handle it. This tutorial walks through building a complete signal flow.


Architecture

Agent (sender)                          Target (receiver)
┌─────────────────────┐                ┌────────────────────┐
│ ISignalProvider<T>  │ ──produces─►  │ ISignalListener<T> │
│ (MonoBehaviour)     │    TSignal     │ (MonoBehaviour)    │
└─────────────────────┘                └────────────────────┘
         ▲                                      │
         │                                      ▼
  SendSignalStrategy<T>              SignalDispatcher.Send()
  (GOAP Action)                      → MessageResult

The flow:

  1. A GOAP action uses SendSignalStrategy<T> as its strategy.
  2. The strategy gets ISignalProvider<T> from the sender agent's capabilities.
  3. It resolves the target (self or a blackboard-stored GameObject).
  4. It calls provider.ProduceSignal() to build the signal payload.
  5. It dispatches via SignalDispatcher.Send(target, signal).
  6. The target's ISignalListener<T> receives and processes the signal.

Step 1: Define a Signal

Signals are plain C# classes that extend Signal:

public class DamageSignal : Signal
{
    public float Damage;
    public Vector3 HitPoint;
}

Keep signals lightweight — they're data containers, not logic.


Step 2: Create a Provider (Sender Side)

Implement ISignalProvider<T> on a MonoBehaviour attached to the sender agent:

public class DamageSignalProvider : MonoBehaviour, ISignalProvider<DamageSignal>
{
    public float baseDamage = 10f;

    public DamageSignal ProduceSignal() => new DamageSignal
    {
        Damage = baseDamage,
        HitPoint = transform.position
    };
}

The strategy retrieves this via context.GetCapability<ISignalProvider<DamageSignal>>().


Step 3: Create a Listener (Receiver Side)

Implement ISignalListener<T> on a MonoBehaviour attached to the target GameObject:

public class Health : MonoBehaviour, ISignalListener<DamageSignal>
{
    public float hp = 100f;

    public MessageResult OnSignal(DamageSignal signal)
    {
        hp -= signal.Damage;
        if (hp <= 0f)
            Destroy(gameObject);
        return MessageResult.Handled;
    }
}

MessageResult

Result Meaning
Handled The signal was consumed successfully
NotHandled The listener exists but chose not to process the signal
Inactive The listener is not active (disabled component, etc.)

Step 4: Create the Strategy Asset

This is a one-liner — create a concrete strategy class:

[CreateAssetMenu(menuName = "RGS/GOAP/Strategies/Send Damage Signal")]
public class SendDamageSignalStrategy : SendSignalStrategy<DamageSignal> { }

Then create the asset via Create > RGS > GOAP > Strategies > Send Damage Signal.


Step 5: Configure the Action

In the GOAP Hub, create an action and assign your SendDamageSignalStrategy. Configure the settings:

SendSignalSettings

Field Type Description
Target MessageTarget Self or BlackboardGameObject
TargetKeyId SerializableGuid Blackboard key holding the target reference (when Target = BlackboardGameObject)
OnUnhandled UnhandledMessageBehavior Succeed or Fail — what happens if no listener handles the signal
ResultKeyId SerializableGuid Optional: bool key set to true when the signal is handled

For a damage action targeting an enemy:

  • Set Target to BlackboardGameObject
  • Set TargetKeyId to the key holding the target enemy reference
  • Set OnUnhandled to Fail (the action should fail if the target can't receive damage)

Step 6: Wire the Components

On the sender (your GOAP agent):

  • GoapAgent with the brain containing the signal action
  • DamageSignalProvider component

On the receiver (the target):

  • Health component (with ISignalListener<DamageSignal>)

That's it. When the planner selects the attack action, the strategy produces the signal, dispatches it to the target, and the Health component processes the damage.


Use Cases

The signal system isn't limited to damage. Any typed data can be sent:

Signal Use Case
DamageSignal Deal damage to a target
HealSignal Heal an ally
AlertSignal Warn nearby allies about a threat
TradeSignal Exchange items between agents
CommandSignal Issue orders from a commander to subordinates

For each signal type, create:

  1. A Signal subclass
  2. An ISignalProvider<T> on the sender
  3. An ISignalListener<T> on the receiver
  4. A one-line SendSignalStrategy<T> subclass

Handling Unhandled Signals

The OnUnhandled setting on SendSignalSettings controls what happens when no listener processes the signal:

  • Succeed — The action completes successfully even if the signal wasn't handled. Use this for optional communication (e.g., alerting nearby allies that might not exist).
  • Fail — The action fails, which fails the entire plan. Use this for required interactions (e.g., attacking a target that must have a health system).

What's Next

  • Guard Post Demo — See the signal system in action in a complete demo.
  • Action Strategies — How strategies execute, including signal strategies.
  • Blackboard — How target references are stored for signal dispatch.