Skip to content
Tuxedo CatSTUDIO
Menu

SignalBus Architecture

Review draft. This source page has not completed website editorial and release-specific verification.

The publisher-subscriber model is a communication pattern where one system announces that something happened and any interested systems may react.

Publisher -> Message Bus -> Subscribers

The publisher does not need to know:

  • How many subscribers exist
  • Which objects are listening
  • Where those objects are located
  • What each subscriber does with the message

Subscribers register interest in a message contract and receive it when a publisher sends that message through the bus.

SignalBus follows this model by routing messages through an owned World or Game Instance subsystem. Publishers and subscribers remain independent, and delivery is selected by:

Scope
Message ID
Exact payload struct type

Example:

Damage system
-> publishes Combat.DamageApplied
-> quest system reacts
-> scoring system reacts
-> audio system reacts
-> UI system reacts

The damage system does not store references to those systems or call them directly. Adding or removing a subscriber does not require changing the publisher.

The publisher-subscriber model provides:

  • Lower coupling: publishers and subscribers do not directly depend on each other.
  • Easier extension: new listeners can be added without modifying existing publishers.
  • Cleaner ownership: each system manages its own behavior and subscription lifetime.
  • One-to-many delivery: one event can notify several independent systems.
  • Better testability: publishers and subscribers can be tested separately.
  • Clearer debugging: SignalBus diagnostics and the Broadcast Graph show observed message flow.
  • Scope isolation: World and Game Instance buses prevent unrelated contexts from sharing messages.
  • C++ and Blueprint interoperability: both use the same runtime routing path.

SignalBus is a good fit when several systems need to react to the same event but should not hold direct references to one another.

Typical uses include:

  • Gameplay events such as damage, death, spawning, objectives, and interactions
  • UI reactions to gameplay or session state
  • Audio, camera, analytics, tutorial, and quest reactions
  • Communication between Actors, Components, and Subsystems
  • Cross-level local session notifications through the Game Instance bus
  • Deferred FIFO event delivery through queued messages

Example:

Door opens
-> World.DoorOpened
-> quest system updates
-> audio system plays a sound
-> map system removes a marker

SignalBus should not replace communication that has a clear direct owner or requires a different Unreal feature.

Use a direct function call when:

  • One object owns another object
  • The caller expects a return value
  • The interaction is a clear one-to-one command
  • The dependency is intentional and stable

Use an Unreal Event Dispatcher when:

  • A specific object exposes an event
  • Listeners already hold a reference to that object
  • The relationship is local and object-centered

Use a Blueprint Interface when:

  • The caller knows the target object
  • Several target classes should implement the same command or query
  • A direct request-response relationship is intended

Use Gameplay Tags or the Gameplay Ability System when:

  • You need ability activation, effects, attribute changes, prediction, or tag-driven gameplay rules

Use replication and RPCs when:

  • Information must move between server and clients

SignalBus is most valuable when the publisher should announce an event without discovering or controlling every listener.

Benefit Compared with Direct UE Communication

Section titled “Benefit Compared with Direct UE Communication”

A direct-reference design can grow into this:

Damage system
-> HUD
-> quest manager
-> audio manager
-> scoring manager
-> tutorial manager

The publisher must then know every receiver and be updated whenever another reaction is added.

SignalBus changes that relationship to:

Damage system
-> Combat.DamageApplied

Each receiving system independently subscribes:

HUD -> subscribes
Quest manager -> subscribes
Audio manager -> subscribes
Scoring manager -> subscribes
Tutorial manager -> subscribes

This reduces reference chains and makes systems easier to add, remove, replace, or test. It also provides one consistent place to inspect subscriptions, dispatches, queued messages, payloads, warnings, and runtime topology.


SignalBus is a local, context-owned publisher-subscriber system for Unreal Engine.

Publisher -> SignalBus subsystem -> Subscriber

Publishers never store, search for, or directly call subscribers. Subscribers never store, search for, or directly call publishers.

Routing is determined by:

  • SignalBus scope
  • Message ID
  • Exact reflected payload type (UScriptStruct*)

A message is delivered only to subscriptions registered in the same SignalBus subsystem with the same message ID and exact payload type.


SignalBus separates message producers from message consumers.

Example:

Damage system
-> publishes Combat.DamageApplied
-> quest system reacts
-> scoring system reacts
-> AI director reacts
-> analytics system reacts

The damage system does not need references to any of those listeners. New listeners can be added without modifying the publisher.

This makes SignalBus useful for:

  • Gameplay event coordination
  • UI notifications
  • System-to-system communication
  • Decoupling actors, components, and subsystems
  • Runtime diagnostics and message-flow inspection

SignalBus is not a replacement for:

  • Unreal replication
  • Server or client RPCs
  • Authority checks
  • Save-game serialization
  • Long-term state storage
  • Cross-process or cross-machine messaging

SignalBus provides two separate routing scopes:

World
Game Instance

The selected subsystem defines the routing boundary.

The two scopes never share:

  • Subscriptions
  • Queues
  • Diagnostics
  • Warnings
  • Broadcast Graph state
  • Message delivery

Using the same message ID and payload type in both scopes does not connect them.


World messages route only through the owning:

USignalBusWorldSubsystem

Use the World bus for events that belong to one specific gameplay world, map, or match.

Typical examples:

Combat.DamageApplied
Actor.Destroyed
Enemy.Spawned
Door.Opened
Objective.ProgressChanged
Projectile.Impact
Match.ScoreChanged
AI.AlertChanged

Conceptually:

UWorld
└── USignalBusWorldSubsystem
├── subscriptions
├── queued messages
├── diagnostics
└── warnings

When the World is destroyed:

  • World subscriptions are removed
  • Pending World messages are discarded
  • World diagnostics and warnings are destroyed
  • A later World receives a new World SignalBus instance

Use World scope when the event only makes sense inside the current gameplay environment.

Enemy takes damage
-> server or client gameplay code updates local state
-> publishes Combat.DamageApplied on that World bus
-> local quest, UI, audio, or AI systems react

The message remains inside that specific World.


Game Instance messages route only through the owning:

USignalBusGameInstanceSubsystem

Use the Game Instance bus for local session-level events that may outlive a World or remain relevant across map transitions.

Typical examples:

Profile.Loaded
Save.Completed
Login.StatusChanged
Matchmaking.SearchStarted
Matchmaking.SearchStopped
Travel.Started
Travel.Completed
Frontend.ScreenChanged
PersistentInventory.Refreshed
Session.Error

Conceptually:

UGameInstance
└── USignalBusGameInstanceSubsystem
├── subscriptions
├── queued messages
├── diagnostics
└── warnings

The Game Instance normally survives ordinary map travel, so its SignalBus can also survive while one World is destroyed and another is created.

Current map begins travel
-> publishes Travel.Started on the Game Instance bus
-> loading screen listens
-> frontend flow listens
-> save coordinator listens
New map finishes loading
-> publishes Travel.Completed on the same Game Instance bus

Do not place a message on the old World queue and expect it to arrive after travel. Use Game Instance scope for travel coordination.


Use this rule:

Does the message belong only to the current level, match, or gameplay world?
Yes -> World bus
Should it survive travel or represent the local game session?
Yes -> Game Instance bus

Another useful distinction:

World bus
= gameplay-space communication
Game Instance bus
= local session-level communication

Examples:

Use World Use Game Instance
Actor damaged Profile loaded
Enemy spawned Matchmaking state changed
Door opened Save completed
Objective updated Travel started
Projectile impacted Login status changed
Match score changed Frontend screen changed

SignalBus supports two delivery modes.


Immediate dispatch is synchronous.

PublishImmediate
-> validates the message and payload
-> invokes matching subscribers
-> returns after callbacks complete

A subscriber callback runs before PublishImmediate returns.

Example:

Before Publish
Subscriber Callback
After Publish

Immediate dispatch is appropriate when the publisher expects all local reactions to complete during the current call stack.

Be aware that subscribers may:

  • Unsubscribe themselves
  • Unsubscribe another subscriber
  • Add a new subscription
  • Publish another immediate message
  • Queue another message

SignalBus preserves deterministic insertion ordering and safe mutation behavior during dispatch.


Queued dispatch copies the payload at enqueue time and returns without invoking subscribers.

PublishQueued
-> validates the message and payload
-> copies the payload
-> adds it to the local subsystem queue
-> returns
Later queue-processing pass
-> finds current matching subscribers
-> delivers in FIFO order

Expected order:

Before Queue
After Queue
Subscriber Callback

Queued dispatch is appropriate when work should occur later rather than inside the publisher’s current call stack.

Important behavior:

  • Payload data is copied at enqueue time
  • Subscribers are matched at delivery time
  • A subscriber added before delivery can receive the message
  • A subscriber removed before delivery does not receive it
  • A destroyed publisher does not cancel an already queued message
  • Messages queued during queue processing wait for a later pass
  • Queues are isolated per World or Game Instance subsystem

Server, Client, and Multiplayer Architecture

Section titled “Server, Client, and Multiplayer Architecture”

SignalBus is local to the Unreal context that owns it.

It is not a network transport.

A typical multiplayer layout looks like this:

Server process
├── Server UWorld
│ └── Server World SignalBus
└── Server UGameInstance
└── Server Game Instance SignalBus
Client 1 process
├── Client 1 UWorld
│ └── Client 1 World SignalBus
└── Client 1 UGameInstance
└── Client 1 Game Instance SignalBus
Client 2 process
├── Client 2 UWorld
│ └── Client 2 World SignalBus
└── Client 2 UGameInstance
└── Client 2 Game Instance SignalBus

Each bus is separate.

A publish on the server’s World bus reaches only subscribers registered in that server World bus.

It does not automatically reach:

  • Client 1
  • Client 2
  • Client World buses
  • Client Game Instance buses

Likewise, a client publish remains local to that client unless Unreal networking separately sends information to the server or another client.


SignalBus is safe for multiplayer when used as a local communication system.

The correct division is:

SignalBus
= local coordination inside one World or Game Instance
Unreal networking
= communication between server and clients

SignalBus does not bypass:

  • Server authority
  • Replication rules
  • RPC ownership rules
  • Relevancy
  • Dormancy
  • Network serialization
  • Validation

A client cannot create authoritative server state merely by publishing a local SignalBus message.


Use Unreal networking to cross the network, then use SignalBus locally on each machine.

Example:

Client input
-> Server RPC
-> server validates the request
-> server changes authoritative state
-> server publishes a local World SignalBus message
-> server-side systems react
-> replicated property or RPC reaches clients
-> client OnRep or Client RPC publishes a local client SignalBus message
-> client UI, audio, camera, and effects react

This keeps responsibilities clear:

RPC or replication
= moves commands or state between machines
SignalBus
= fans that event out to local systems

Authoritative damage processing:

Client requests attack
-> Server RPC
-> server validates attack
-> server applies damage
-> server publishes Combat.DamageApplied on the server World bus
-> server quest system reacts
-> server scoring system reacts
-> server AI director reacts
-> health value replicates to clients

The SignalBus publish itself does not travel to clients.


Reacting to replicated health:

Replicated Health changes on client
-> OnRep_Health executes
-> client publishes Combat.HealthChanged on the client World bus
-> HUD updates
-> hit audio plays
-> camera effect reacts
-> local accessibility feedback reacts

The client-side SignalBus message is a local reaction to networked state.


SignalBus does not replace server-to-client or client-to-server RPC architecture.

An RPC has network semantics that SignalBus does not provide.

RPCs can:

  • Cross process and machine boundaries
  • Follow Unreal ownership rules
  • Target the server, owning client, or multiple clients
  • Serialize supported parameters
  • Participate in Unreal’s network channel and connection model

SignalBus messages cannot do those things by themselves.


This will not reach clients:

Server
-> Publish Combat.DamageApplied on server World bus
-> Client subscriber

The client subscriber is registered in a different subsystem on another machine or network context.

This also does not send a command to the server:

Client
-> Publish Inventory.AddItem on client World bus
-> Server inventory system

The server inventory system cannot receive that local client publish.

Use a Server RPC or another Unreal networking mechanism.


RPC Delivery and SignalBus Delivery Are Different

Section titled “RPC Delivery and SignalBus Delivery Are Different”

RPC delivery is directed by Unreal networking.

SignalBus delivery is selected by:

Local subsystem
+ Message ID
+ Exact payload type

An RPC usually targets a specific network endpoint or group of endpoints.

SignalBus broadcasts locally to every matching subscriber in the selected subsystem.

Example:

Server RPC
-> one authoritative server function
Server SignalBus message
-> every matching local server subscriber

These mechanisms complement each other.


A thin networking layer can translate between RPCs and local SignalBus messages.

Example:

Server gameplay system
-> changes authoritative objective state
-> replicated objective data reaches client
Client objective component
-> OnRep_ObjectiveState
-> publishes Objective.StateChanged on client World bus
Local listeners
-> HUD
-> map marker system
-> announcer audio
-> tutorial prompts

Another example:

Client UI
-> requests matchmaking cancel
-> Server RPC or online-session API
-> authoritative response returns
-> local Game Instance bus publishes Matchmaking.Cancelled
-> frontend widgets and status systems react

Do not create an automatic generic “replicate every SignalBus message” bridge without a deliberate authority, security, bandwidth, and serialization design.


SignalBus messages do not replicate.

A server publish does not automatically produce a client publish.

SignalBus does not know whether a publisher has server authority.

The caller must use Unreal authority checks where required.

Example:

if (HasAuthority())
{
// Apply authoritative state and publish a server-local message.
}

SignalBus does not validate client requests.

Server RPC handlers must still validate:

  • Permissions
  • Ownership
  • Input ranges
  • Gameplay rules
  • Anti-cheat concerns

SignalBus payloads use reflected structs for local routing, but they are not automatically network-serialized.

A payload being valid for SignalBus does not guarantee it is valid as an RPC parameter or replicated property.

SignalBus has no concept of:

  • Packet loss
  • Network reliability
  • Connections
  • Relevancy
  • Bandwidth
  • Replication frequency
  • Dormancy

Those remain Unreal networking concerns.

A server and client generally own separate Game Instances.

A Game Instance SignalBus message remains local to its own Game Instance.

Game Instance scope means “across local Worlds owned by this Game Instance,” not “across every machine in the multiplayer session.”


In Multi-PIE, several Worlds and Game Instances may exist in one executable.

SignalBus still isolates them by subsystem instance.

Example:

World - PIE 0
Game Instance - PIE 0
World - PIE 1
Game Instance - PIE 1

Even though they share a process:

  • World messages do not cross PIE Worlds
  • Game Instance messages do not cross PIE Game Instances
  • Queues remain separate
  • Diagnostics remain separate
  • Warnings remain separate
  • Broadcast Graph state remains separate

This is why the SignalBus Runtime Inspector shows multiple selectable contexts.


  • Server-authoritative match events
  • Client-local reactions to replicated gameplay state
  • Actor and component events
  • Combat events
  • Objective events
  • Spawn and destruction notifications
  • Map-local UI
  • Local match systems
  • Login and account flow
  • Matchmaking
  • Frontend state
  • Session errors
  • Save or profile completion
  • Travel coordination
  • Persistent local UI state
  • Local services that survive map transitions

Do not use Game Instance scope merely because an event should reach every client. It still remains local to one Game Instance.


Pattern 1: Server authority, local server reactions

Section titled “Pattern 1: Server authority, local server reactions”
Server changes authoritative state
-> server World SignalBus message
-> local server systems react
-> state replicates normally
Replicated property changes
-> OnRep
-> client World SignalBus message
-> client presentation systems react
Client local input
-> Server RPC
-> server validates
-> server changes state
-> optional server-local SignalBus publish
Server sends Client RPC
-> receiving client publishes local SignalBus message
-> multiple local client systems react
Server executes NetMulticast RPC
-> RPC runs on relevant machines
-> each machine publishes its own local SignalBus message
-> each machine's local listeners react

Use NetMulticast carefully according to Unreal’s authority, relevancy, and reliability rules.


Do not treat this as authoritative:

Client publishes Player.CurrencyAdded locally
-> client updates authoritative currency

Do not expect this to cross the network:

Server publishes Match.Ended
-> all clients automatically receive it

Do not create a generic payload-forwarding RPC that accepts arbitrary client SignalBus messages without validation.

That can introduce:

  • Authority bypass
  • Security problems
  • Excess bandwidth
  • Unbounded message types
  • Unsafe payload handling
  • Difficult debugging
  • Accidental client control over server systems

Network-facing messages should use explicit, reviewed RPCs or replicated state.


Actors and Actor Components may subscribe to either scope.

Typical guidance:

Gameplay Actor or Component
-> usually World scope
Persistent manager or session coordinator
-> usually Game Instance scope

A World Actor may subscribe to the Game Instance bus, but Game Instance scope does not make the Actor persistent.

When the Actor or listener component is destroyed:

  • Its subscription is removed or pruned
  • It no longer receives messages
  • The Game Instance subsystem may continue to exist

The lifetime owner remains separate from the routing scope.


Blueprint APIs call the same runtime subsystems used by C++.

There is no separate Blueprint-only bus.

Blueprint listener callbacks receive an owned payload wrapper copy before the Blueprint event is invoked. This keeps Blueprint delivery safe without changing native C++ immediate callback semantics.

Immediate Blueprint order:

Before Publish
Receive Callback
After Publish

Queued Blueprint order:

Before Queue
After Queue
Receive Callback

Blueprint and C++ subscribers can communicate through the same SignalBus subsystem when they use the same:

  • Scope
  • Message ID
  • Exact payload struct type

Diagnostics, warnings, the SignalBus Runtime Inspector, the Broadcast Graph, and Unreal Trace observe runtime behavior.

They do not:

  • Control routing
  • Mutate subscriptions
  • Repair warnings
  • Edit payloads
  • Replay messages
  • Change delivery order
  • Replicate messages

The Inspector can show separate World and Game Instance contexts for server and client PIE instances.

The Broadcast Graph represents observed local topology:

Publisher -> Message -> Subscriber

Publisher nodes are based on retained observed history. Publishers are not permanently registered.


SignalBus is game-thread-only.

Unsupported operations include:

  • Cross-thread publishing
  • Cross-thread subscription
  • Worker-thread callbacks
  • Lock-free producer queues

Callers must return to the game thread before using SignalBus.

SignalBus does not add locks or partial cross-thread behavior.


World SignalBus
- One bus per UWorld
- Current map, match, and gameplay
- Destroyed with the World
- Isolated per server/client World
Game Instance SignalBus
- One bus per UGameInstance
- Local session and cross-level coordination
- Survives ordinary World travel
- Isolated per server/client Game Instance
Unreal Networking
- Moves commands and state between server and clients
- Enforces authority, ownership, and replication
- Separate from SignalBus

The safest multiplayer architecture is:

Use Unreal networking to cross machines.
Use SignalBus to coordinate local systems after the network event arrives.