TECHNICAL CASE STUDY · Architecture
Evolution of the Battle-System Architecture
How the architecture of a LiveOps game evolved from disconnected implementations to shared contracts and extensible mechanisms.
Context
The battle system grew alongside the game and its continuous content releases. Heroes, abilities, items, and battle entities accumulated over years, so solutions that once suited a single mechanic could later cause duplication, implicit dependencies, and inconsistent behaviour.
My task was an incremental modernisation: introduce common abstractions where they reduced the cost of further development, while keeping content production moving.
Architecture directions
One entry point for battle-state changes
Historically, operations that changed battle state were distributed across several base classes and protected methods. This caused duplication, behavioural differences, and made it difficult for new battle entities to reuse logic.
I led a gradual consolidation of these operations behind the IBattleActions interface, moving damage, healing, mana, energy, statuses, attributes, spawning, and actor actions into it. The changes were made in small domain-by-domain iterations, allowing active content work to continue in parallel with the architectural migration.
Before: base classes and local protected methods
│
├── different behaviour in similar scenarios
└── logic copied by new entities
After: entity script → IBattleActions → shared battle-state rules
The interface consequently became a single entry point for state mutations and can be injected into any script that needs to affect the course of a battle.
A shared model for battle entities
Actors, tokens, token attachments, consumables, battle modifiers, and triggers had a similar role: they carried custom gameplay logic. Previously, their lifecycles and script creation had evolved independently.
I contributed to unifying this model around the BattleEntityScript base class, entity interfaces, and factories with a shared contract. This made adding a new entity more predictable: instead of creating a separate lifecycle, it could fit into an established architectural pattern.
Expressive parameters rather than Boolean chains
Damage and healing calculation is among the most frequently invoked parts of the battle system. A series of positional Boolean parameters is easy to mix up or misinterpret, and the compiler cannot always detect an error.
I migrated these calls to DamageOptions and HealOptions parameter objects with named properties and reusable default values. This made each call’s intent clearer, reduced the risk of mistakes when signatures change, and made the shared calculation rules safer to evolve.
// Illustrative example of the principle, not project code.
// Before: the order of flags is easy to confuse.
ApplyDamage(source, target, value, true, false, true);
// After: the purpose of every parameter is visible at the call site.
ApplyDamage(source, target, value, new DamageOptions
{
IsAbility = true,
IsRaw = false
});
Result
The architectural changes made the battle system more consistent and extensible: there is less duplicated base-class logic, less risk that entity behaviour will diverge, and clearer system responsibilities.
The core principle of this work was not to replace everything at once, but to identify recurring problems, formulate a shared contract, validate it against real content, and progressively migrate the system to the new solution.