Development Insights

Technical challenges and solutions from Aetherfall's development

This page serves as a technical resource where we share the challenges we've encountered during development and the innovative solutions we've implemented to overcome them. From complex interaction systems to performance optimization, we dive deep into the technical aspects that make Aetherfall possible.

September 20, 2024

Building a Tag-Driven Elemental Interaction System

Introduction

Aetherfall's combat revolves around nine elemental types and many ability archetypes (projectiles, melee attacks, dashes, persistent fields, etc.). Because any two elements can combine and because abilities can interact with fields and each other, the number of possible outcomes quickly grows into the hundreds. Hard-coding every combination would be a maintenance nightmare. Instead, Aetherfall uses Unreal's lightweight gameplay tags, data assets, and the GAS framework to build an extensible interaction system.

At a high level, tags describe what something is (e.g., a fire projectile, an earth melee attack, a state like "reflected"), while data assets describe how something should behave (damage numbers, visual effects, audio cues, field durations, modification matrices, etc.). The game resolves interactions by combining tags from the participants and using them as keys to look up the appropriate data assets. This design lets you add new elements or tweak behaviours without touching code – you edit data assets or add new tags, and the system picks them up automatically.

Tag Hierarchy

Aetherfall defines a hierarchical tag schema to describe abilities, elements, and states. A few examples illustrate the structure:

  • Element tags – Element.Fire, Element.Water, Element.Earth, etc. These indicate the element associated with a projectile, melee attack or field.
  • Ability type tags – Ability.Projectile, Ability.Melee, Ability.Dash, and so on. These indicate how the ability behaves mechanically.
  • State tags – State.Modified, State.Reflected, State.Destroyed, etc. These describe what has happened to an object (e.g., a projectile that has been reflected now carries the State.Reflected tag).

Tags are combined to form unique identifiers such as Ability.Projectile + Element.Fire. Because GAS tags are hierarchical, you can write queries that match a broad category (e.g. any Element.* tag) or a specific one (Element.Fire).

Data-Driven Design

Most of the logic lives in data assets, not code. Several categories of data assets drive the system:

  • Element interaction matrices – tables indicating what happens when two elements meet. For example, combining Fire and Water might create a Steam field; Earth and Water could produce Mud.
  • Field effect configurations – data assets that define a field's radius, duration, tick interval, damage per tick, particle system, etc.
  • Projectile property modifications – data specifying how a projectile changes when it passes through a field (e.g. increased damage, changed element, altered velocity).
  • VFX/SFX mappings – lookup tables mapping tag combinations to particle systems, sound cues and material parameters.

The game doesn't hard-code "if Fire meets Water then do X." Instead, each element combination points to a field effect asset. When a combination occurs, the system loads that asset and applies its properties.

Because tags act as keys and data assets hold the payload, adding a new element or ability type usually means creating new tags and filling out the appropriate data tables. The core code never needs to change, and designers can tweak interactions directly in the editor.

Interaction Resolution

When two objects interact, the system gathers their tags and queries the data assets. Here are the main interaction categories:

Projectile + Projectile → Field

When two projectiles collide, they don't simply explode; they combine to create a persistent field. The system takes both projectiles' element tags, looks up the corresponding entry in the element interaction matrix and spawns a field actor defined by that entry.

The field inherits a new tag like Ability.Field + Element.Steam and uses its data asset to configure size, lifetime, damage over time, VFX and audio cues. Because the lookup is data-driven, supporting new pairings is as simple as adding rows to the matrix.

(This is one of the few cases where a new actor is spawned; in most other cases, existing actors are updated in place.)

Projectile + Field → Modified Projectile

If a projectile passes through an existing field, it can pick up new properties. The system constructs a lookup key from the projectile's element tag and the field's element tag, then retrieves a modification asset that describes how to alter the projectile (e.g., increasing damage, changing travel speed, applying a status effect).

Importantly, no new projectile is spawned. The same actor is updated in place: its tags change to reflect its new state (State.Modified), its damage values and movement are adjusted, and its VFX/SFX are swapped according to the modification asset.

Multiple fields can be chained: a projectile could travel through a Fire field to become burning, then through a Wind field to become a flaming whirlwind, with each step driven by data.

Melee + Projectile → Reflect, Destroy or Transform

Melee abilities also carry element tags. When a character swings a melee ability at an incoming projectile, the system uses both tags to decide what to do. For instance:

  • An earth-aligned kick might destroy a water projectile, removing it entirely.
  • The same kick might reflect a fire projectile, reversing its direction and adding a State.Reflected tag. The projectile actor isn't replaced – its velocity and tags are updated, and its visuals/audio are swapped accordingly.
  • A water melee attack might absorb a water projectile, granting a temporary buff instead of interacting with the world.

These outcomes are defined in data tables keyed by the melee ability's tag and the projectile's tag.

Dash + Field → Buff or Explosion

Dash abilities let players quickly traverse the battlefield. When a dash moves through a field, the system combines the dash's element tag with the field's element tag to decide the effect. In some cases, the dash picks up a buff (e.g. a water dash through a lightning field might grant speed and electrified attacks). In other cases, the dash triggers an explosive burst, damaging nearby enemies.

Again, no new buff actor is spawned. Instead, temporary state tags (like State.Buffed or State.Overcharged) are applied directly to the player. These tags then drive visual and audio updates, as well as gameplay effects such as increased movement speed or damage.

As with other interactions, data assets determine the magnitude, area of effect, VFX, and SFX.

Visual and Audio Consistency

Tags drive not just logic but also presentation. The system uses Unreal's gameplay cues to play and stop effects based on tag events:

  • When a field is spawned, its tags trigger a cue that spawns the correct particle system and sound.
  • When a projectile is modified, its tags determine which material instance to swap to and which sound to play on impact.
  • When a dash gains a buff, the player's tags drive which aura or overlay is applied.

Because everything is tag-driven, the appearance and sound of new elements remain consistent automatically, without editing multiple blueprint graphs or C++ classes.

Benefits

  • Scalability – Adding a new element type or ability rarely requires code changes. Designers create new tags and data assets, and the system handles them.
  • Maintainability – All interaction rules live in centralized data tables. There are no sprawling switch statements hidden in code.
  • Network efficiency – GAS tags are lightweight and replicate efficiently, which is important when tracking many stateful objects in a multiplayer game.
  • Extensibility – The same tag/data pattern can support additional systems (e.g., crafting, status effects) without introducing brittle dependencies.

Conclusion

Aetherfall's tag-driven interaction system is a good example of letting data drive behaviour. By separating what something is (tags) from how it behaves (data assets) and using tables to describe element combinations, the game supports a rich set of emergent interactions while keeping the codebase manageable.

Crucially, most interactions don't spawn new actors at all — they update existing ones in place by changing tags, properties, and effects. This keeps the system performant and network-friendly while still allowing for visually dramatic transformations.

Developers adopting Unreal's GAS or building their own systems can take inspiration from this approach to manage complexity in games with many overlapping mechanics.

Technical Note: The full pseudocode implementation for this system can be found in our technical documentation.