Roguelike combat becomes expensive to change when one script or graph owns tuning data, device input, rule execution, status effects, and visual feedback. A safer starting point is a small set of contracts: data defines what can happen, commands state what was requested, effects change authoritative state, and presentation observes the result. Unity and Unreal offer different APIs, but both can support this separation without claiming that their systems are interchangeable.
The goal is not a universal framework. It is a reviewable boundary that makes a new attack, item effect, or status condition easier to reason about before it spreads across combat code.
Implementation
Start by writing one contract per combat feature. Record the data it reads, the command or activation that requests it, the state it may change, and the feedback events it emits. Keep the contract independent of a particular input device or animation asset.
In Unity, place shared definitions such as attack ranges, cooldown values, targeting rules, and effect parameters in assets where that suits the project. ScriptableObject instances can live independently of GameObjects and Unity documents their use for centralized data shared across scenes and assets. Treat these assets as definitions, not as the mutable state of one running combatant.
Translate Input Actions into game commands at the edge of the system. An action such as AttackPrimary may be bound to several controls, but the combat layer should receive a request such as TryActivate(attackId, targetHint). Unity describes Actions as notifying code about input through phases and callbacks; the action does not itself define the response. This keeps rebinding and device support from leaking into combat rules.
In Unreal, use the Gameplay Ability System vocabulary to make the state boundary visible. The Ability System Component owns and activates abilities, Attribute Sets represent values used by the system, and Gameplay Effects modify attributes and tags. Model a damage-over-time effect, temporary buff, or cooldown as an effect decision with a clear owner and end condition. Let gameplay cues and other listeners consume the result for sound, VFX, camera, or UI rather than treating a visual completion as the rule that ends gameplay.
Choose the C++ and Blueprint seam after the contract is clear. A compact C++ base can own repeated invariants and deliberately expose a small surface for Blueprint composition: content-specific effect classes, montages, cues, and tuned values. Epic's guidance describes C++ as a foundation with Blueprint classes built on top, which is a useful default when several abilities share the same policy.
A feature review can use this table:
| Contract | Typical owner | Question to answer |
|---|---|---|
| Definition data | asset or data class | Which values are reusable and safe to tune? |
| Command or activation | input adapter or ability entry point | What player or game event requested the action? |
| Rule and effect | combat service or ability lifecycle | Which attributes, tags, costs, and end conditions change? |
| Feedback | presentation listener | Which result should animation, audio, VFX, or UI display? |
Tradeoffs
Explicit contracts add names, data types, and review work before a prototype shows immediate results. A tiny game may not need a separate type for every attack. Start with boundaries that prevent known repetition: shared costs, common cancellation rules, reusable status effects, and input mappings that support more than one device.
Asset-backed definitions make tuning easier to inspect, but can become a maze if runtime state is stored beside reusable configuration. Keep ownership obvious: a definition may be shared; a combatant's remaining cooldown or current health belongs to the running game state.
A hybrid Unreal workflow has a maintenance cost. C++ APIs exposed to Blueprint become a product for designers and gameplay programmers. Broad exposure creates fragile graphs; overly narrow exposure turns ordinary content iteration into a code request. Prefer a few stable extension points over a base class that attempts to anticipate every ability.
Failure modes
Device details escape into combat rules
If damage code checks a keyboard key, gamepad button, or callback context directly, every new input path can change gameplay behavior. Convert device input to a command at the adapter boundary, then test the command handler without needing a physical device.
Shared definitions carry per-instance state
A shared configuration asset that also stores a target, elapsed duration, or remaining charges can make one actor affect another. Keep mutable state on the actor, effect instance, or combat session; use the definition only as input to create or evaluate that state.
Presentation decides game completion
An animation can be interrupted, skipped, or delayed. If a hit is considered complete only when an animation event fires, game rules become coupled to a presentation schedule. Record the gameplay transition first, emit a result, and allow presentation to react to it.
Repeated policy is copied into content graphs
When every ability graph reimplements eligibility, tag checks, or cancellation, changes drift. Move only genuinely shared policy to a common service or base class. Keep the per-ability choices visible in the content layer.
Testing
Test the contracts separately before treating an ability as complete:
- Given a definition and a command, verify that eligible and ineligible actors produce the expected rule result.
- Verify that applying an effect changes the intended attributes or tags and that duration, removal, interruption, and cancellation leave valid state.
- Verify that input adapters turn each supported binding into the same game command, while the command handler runs without input-system objects.
- Verify that presentation listeners receive results but cannot become the only source of gameplay completion.
- In Unreal projects, test representative ability activation, effect application, cancellation, and end conditions in the project modes the game will ship.
- In Unity projects, exercise the authored data and Input Action flow in a representative scene, then record the project and engine version used.
These checks establish the chosen boundaries; they do not prove performance, networking behavior, or deterministic simulation for every project. Measure those separately with the actual target build and content.
Production considerations
Treat contract names and payloads as compatibility surfaces. A saved run, replay log, content pack, or debugging tool may depend on an effect identifier or command shape long after an ability has been redesigned. Version data deliberately when it can outlive one build.
Keep a lightweight ownership record for every shared combat concept: who creates it, who may mutate it, who removes it, and which presentation systems are listeners. This is particularly valuable for stacking effects, interrupts, death cleanup, and UI that reflects temporary state.
Avoid making engine comparisons into performance promises. Unity ScriptableObjects, Unity Input Actions, Unreal GAS, C++, and Blueprint solve different parts of the problem. Profile and test the actual project before setting scale, memory, frame-time, or multiplayer requirements.
Before adding a new ability family, confirm that it has a definition boundary, an activation or command boundary, a rule and effect boundary, and a feedback boundary. If one component appears to own all four, split the responsibility before additional content makes the seam harder to change.
Sources
- Unity, ScriptableObject API: https://docs.unity3d.com/2022.3/Documentation/ScriptReference/ScriptableObject.html - supports the use of ScriptableObject assets for data that exists independently of GameObjects and can be centralized across a project.
- Unity Input System, Actions: https://docs.unity3d.com/Packages/com.unity.inputsystem@1.7/manual/Actions.html - supports the distinction between bound controls, Action phases, callbacks, and the game code that responds.
- Epic Games, Gameplay Ability System overview: https://dev.epicgames.com/documentation/en-us/unreal-engine/understanding-the-unreal-engine-gameplay-ability-system - supports the component roles for abilities, attributes, gameplay effects, tags, and gameplay cues.
- Epic Games, Blueprint vs. C++: https://dev.epicgames.com/documentation/en-us/unreal-engine/coding-in-unreal-engine-blueprint-vs-cplusplus - supports the mixed C++ foundation and Blueprint extension approach.
