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:
- A GOAP action uses
SendSignalStrategy<T>as its strategy. - The strategy gets
ISignalProvider<T>from the sender agent's capabilities. - It resolves the target (self or a blackboard-stored GameObject).
- It calls
provider.ProduceSignal()to build the signal payload. - It dispatches via
SignalDispatcher.Send(target, signal). - The target's
ISignalListener<T>receives and processes the signal.
Step 1: Define a Signal¶
Signals are plain C# classes that extend Signal:
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
TargettoBlackboardGameObject - Set
TargetKeyIdto the key holding the target enemy reference - Set
OnUnhandledtoFail(the action should fail if the target can't receive damage)
Step 6: Wire the Components¶
On the sender (your GOAP agent):
GoapAgentwith the brain containing the signal actionDamageSignalProvidercomponent
On the receiver (the target):
Healthcomponent (withISignalListener<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:
- A
Signalsubclass - An
ISignalProvider<T>on the sender - An
ISignalListener<T>on the receiver - 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.