A health change, a hit, and an item pickup are gameplay facts. A HUD update, sound, screen effect, or controller response is presentation. When one system directly drives the other, a small rule change can force unrelated UI work and make feedback bugs difficult to trace.
This guide is for indie teams building readable roguelike combat feedback in Unreal or Godot. It proposes a small event contract between gameplay rules and presentation consumers. The pattern is deliberately narrow: it helps a team assign responsibility and test a feedback path; it does not predict performance, networking behavior, or a universal engine architecture.
The Problem
Direct calls from combat code into widgets and effects often begin as a convenience. Over time, the same damage function may decide health, choose a color, play a sound, start a camera effect, and update a status panel. The resulting coupling creates two recurring costs:
- A rule change can break a visual path that was never part of the intended change.
- A feedback change can accidentally become the source of truth for gameplay state.
The useful boundary is not an abstract message bus with every possible detail. It is a compact contract that states what happened in gameplay terms and lets presentation decide how to communicate it.
Implementation
1. Define events in player-meaningful language
Start with events that represent a decision-relevant change, not a rendering command. HealthChanged, StatusApplied, PickupCollected, and RunEnded are useful examples. FlashRed and MoveWidget are not: they name a particular presentation choice.
For each event, document the minimum payload a consumer needs. A damage-related event might carry the affected actor, the previous and new value, a source identifier when available, and a severity category chosen by gameplay rules. Keep the payload stable and avoid passing a widget, animation, or engine-specific UI object through the contract.
HealthChanged
targetId
previousValue
currentValue
causeCategory
isLethalA small payload makes ownership visible in code review. If a presentation consumer needs a new field, the team can decide whether it is truly gameplay information or a local visual preference.
2. Give rules one owner and feedback separate consumers
The gameplay layer validates a hit, changes state, and emits the completed event. A HUD listener can update a health bar. A feedback listener can choose an icon, sound, or motion treatment. An accessibility listener can provide text or an alternate cue. Each consumer observes the same fact without becoming responsible for the rule.
In Unreal, Blueprints can extend systems implemented in C++, which makes the contract a useful seam between stable gameplay code and content-facing composition. In Godot, signals provide a direct way for nodes to react to a state change, while a CanvasLayer can host UI and overlay elements separately from the world. The engine mechanisms differ, but the responsibility split remains the same.
3. Keep the contract engine-neutral
Write the contract before deciding how it is delivered. In one project, an Unreal gameplay component may publish to Blueprint-facing listeners. In another, a Godot gameplay node may emit a signal that UI nodes subscribe to. Do not make the event name, payload, or ownership depend on a specific widget class or scene path.
This lets the team change a health-bar layout or replace an effect without rewriting damage rules. It also keeps engine-specific work where it belongs: listener registration, lifecycle handling, and visual composition.
4. Set delivery rules explicitly
An event contract needs a few operational choices, even in a small project:
| Decision | Working default | Review question |
|---|---|---|
| When to emit | After gameplay state changes | Can a listener observe a state that has not actually been applied? |
| Who may subscribe | UI, audio, VFX, accessibility, telemetry consumers | Is this consumer trying to alter the rule? |
| Payload size | Only data required to explain the change | Is this field gameplay truth or a local presentation preference? |
| Ordering | Document events that must be seen in sequence | What happens when damage and death occur in the same update? |
| Lifetime | Disconnect listeners with their UI or scene lifecycle | Can a destroyed screen still receive an update? |
For a first version, prefer synchronous local delivery inside a clearly defined gameplay update. Add queues, replay, persistence, or network transport only when a measured project need requires them.
Tradeoffs
A small contract adds design work up front. The team must name events, choose payloads, and maintain listener lifecycles rather than making a direct UI call. For a prototype with one screen and one attack, that can feel heavier than necessary.
The benefit appears when several features share a state transition. A single StatusApplied event can support a HUD icon, a world cue, a sound option, and a reduced-motion alternative without placing those choices in status-effect logic. The tradeoff is discipline: a contract that carries every possible presentation detail becomes another tightly coupled interface.
Use this pattern for recurring, player-visible state changes. Do not introduce it merely to wrap one private function call that has no second consumer or expected change boundary.
Failure modes
Event names that hide the rule
An event such as UpdateCombatUI forces consumers to infer what changed. Prefer a completed gameplay fact such as ArmorBroken or ResourceChanged, with a documented payload. Clear names reduce the chance that a listener assumes a state change it did not receive.
Presentation becoming authoritative
A common mistake is ending an ability, granting an item, or clearing a status only after an animation finishes. Visual work may be disabled, delayed, or replaced. Resolve gameplay state in its owner, then let presentation react to the resulting event.
Leaking engine objects through the boundary
Passing an Unreal widget reference or a Godot scene node in a general gameplay event ties the publisher to one implementation. Pass stable identifiers and semantic values instead. The listener can resolve its own engine-local objects.
Unmanaged subscriptions
A screen that remains subscribed after it is removed can receive stale updates or retain an object unexpectedly. Add listener registration and removal to the same lifecycle checklist as screen creation and teardown. Test scene changes and run restarts, not only the first combat room.
Treating all feedback as equal
Damage ticks, loot pickups, lethal danger, and a run-ending event should not compete with identical visual weight. Let gameplay publish the category and urgency; let presentation apply a documented priority policy that can merge or defer low-priority feedback.
Testing
Test the contract as a path from a state change to a player-readable result. The goal is to prove that the expected consumers receive the right semantic event and that presentation never owns the rule.
| Scenario | What to verify |
|---|---|
| Normal damage | Health changes once and the HUD receives the matching values. |
| Lethal hit | Health and death state are resolved in gameplay order; the death view observes the final result. |
| Two simultaneous effects | Priority rules keep critical feedback readable and do not drop the semantic event. |
| Screen or scene change | Removed UI no longer receives events; the new screen can initialize from current gameplay state. |
| Accessibility option | The same event can reach a non-color or reduced-motion presentation path. |
| Engine boundary | Unreal and Godot listeners use their local mechanisms without changing the event meaning. |
Run these checks in the smallest representative project for each engine version you support. Record the engine version, test scene, event sequence, and observed result. Do not generalize a local test into a claim about other platforms, multiplayer modes, or performance budgets.
Production considerations
- [ ] Every published event describes a completed gameplay fact, not a rendering command.
- [ ] Payloads contain stable identifiers and semantic values rather than widget or scene references.
- [ ] Gameplay rules resolve state before any presentation consumer reacts.
- [ ] Each event has a documented owner, subscribers, ordering rule, and lifecycle cleanup point.
- [ ] High-priority feedback has a defined policy when several events arrive together.
- [ ] Accessibility alternatives consume the same semantic event.
- [ ] The team has tested damage, death, overlapping effects, and screen or scene transitions in its target engine versions.
- [ ] Performance and network behavior are evaluated separately with project-specific profiling and test environments.
Sources
- Epic Games, Blueprint visual scripting overview: https://dev.epicgames.com/documentation/en-us/unreal-engine/introduction-to-blueprints-visual-scripting-in-unreal-engine
- Godot Engine documentation, CanvasLayer: https://docs.godotengine.org/en/stable/tutorials/2d/canvas_layers.html
- Godot Engine documentation, signals: https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html
