Decoupled, simple systems.
Send a typed message and any system reacts, with no reference back to the sender. Three message types, about 10 ns per handler, zero GC.
using DxMessaging.Core.Attributes;
using DxMessaging.Core.Extensions;
using DxMessaging.Unity;
[DxUntargetedMessage]
[DxAutoConstructor]
public readonly partial struct WaveStarted
{
public readonly int Index;
}
// Register once; token cleanup follows the owner.
Token.RegisterUntargeted<WaveStarted>(OnWaveStarted);
// Send from any system.
WaveStarted waveStarted = new WaveStarted(3);
waveStarted.EmitUntargeted();
using DxMessaging.Core.Attributes;
using DxMessaging.Core.Extensions;
using DxMessaging.Unity;
[DxTargetedMessage]
[DxAutoConstructor]
public readonly partial struct Heal
{
public readonly int Amount;
}
// Register on the player that can receive heals.
Token.RegisterGameObjectTargeted<Heal>(gameObject, OnHeal);
// Send from any system.
Heal heal = new Heal(50);
heal.EmitGameObjectTargeted(player);
using DxMessaging.Core;
using DxMessaging.Core.Attributes;
using DxMessaging.Core.Extensions;
using DxMessaging.Unity;
using UnityEngine;
[DxBroadcastMessage]
[DxAutoConstructor]
public readonly partial struct TookDamage
{
public readonly int Amount;
}
public sealed class DamageFeed : MessageAwareComponent
{
protected override void RegisterMessageHandlers()
{
base.RegisterMessageHandlers();
// Token belongs to this listener, not the object taking damage.
_ = Token.RegisterBroadcastWithoutSource<TookDamage>(OnAnyDamage);
}
private void OnAnyDamage(InstanceId source, TookDamage message)
{
// React to damage from any source.
}
}
public sealed class EnemyHealth : MonoBehaviour
{
public void ApplyHit(int amount)
{
TookDamage tookDamage = new TookDamage(amount);
tookDamage.EmitGameObjectBroadcast(gameObject);
}
}
The three message types
Announce to all
A global message with no sender and no target. Anything listening hears it. The PA system.
[DxUntargetedMessage] Command one
A message to one specific GameObject. Only that object receives it. The addressed letter.
[DxTargetedMessage] Emit a fact
A fact from a known source. Listeners can filter by who sent it. The radio station.
[DxBroadcastMessage] Start Here¶
- Quick Start - Define, register, and emit your first message.
- Mental Model - Choose between untargeted, targeted, and broadcast messages.
- Inspector Tools - Use diagnostics and base-call warnings inside Unity.
- Message Monitor - Inspect emissions, trace paths, and registration topology.
- Performance - Read the current published benchmark tables.
Install¶
OpenUPM¶
Git URL¶
See the Install Guide for scoped registry, Git URL, and local tarball options.
First Message¶
using DxMessaging.Core.Attributes;
using DxMessaging.Core.Extensions;
using DxMessaging.Unity;
using UnityEngine;
[DxTargetedMessage]
[DxAutoConstructor]
public readonly partial struct DamageRequested
{
public readonly int Amount;
}
public sealed class DamageReceiver : MessageAwareComponent
{
public int Health { get; private set; } = 100;
protected override void RegisterMessageHandlers()
{
base.RegisterMessageHandlers();
_ = Token.RegisterGameObjectTargeted<DamageRequested>(gameObject, OnDamageRequested);
}
private void OnDamageRequested(ref DamageRequested message)
{
Health = Mathf.Max(0, Health - Mathf.Max(0, message.Amount));
}
}
public sealed class Hazard : MonoBehaviour
{
public int Damage = 25;
private void OnTriggerEnter(Collider other)
{
DamageRequested request = new DamageRequested(Damage);
request.EmitGameObjectTargeted(other.gameObject);
}
}
On the hazard GameObject, enable Is Trigger on its collider and add a kinematic Rigidbody with Use Gravity disabled. Put DamageReceiver and the entering collider on the same target GameObject. The hazard targets that exact object without knowing whether it is a player, enemy, crate, or future gameplay type. Neither component holds a reference to the other.
Why Teams Use It¶
Simple primitives
Three message shapes - untargeted, targeted, broadcast - and nothing else to learn. Each contract is an explicit typed struct, and no system holds a reference to any other.
Easy to use
Define a struct, register a handler, emit. Registration tokens follow their owner's lifecycle, so handlers remove themselves - no manual unsubscribe, no leaked listeners.
Small edits, big impact
The same simple primitives decouple entire systems. Wiring a feature in is one registration; removing it is deleting that line. Interceptors, handler priorities, and global observers layer on without touching existing code.
High performance
Struct messages and by-ref handlers keep steady-state dispatch at zero allocation. Type-indexed routing stays O(1), with published results around 10 ns per handler.
Next¶
- New to the package: Getting Started
- Choosing message types: Message Types
- Unity integration patterns: Unity Integration
- Debugging message flow: Diagnostics
- API details: Reference