|
| 1 | +# AER World |
| 2 | + |
| 3 | +AER World is a prototype game state container designed to model games like [Hearthstone](https://hearthstone.blizzard.com/en-us) and [Super Auto Pets](https://teamwood.itch.io/super-auto-pets), where entities can react to actions by spawning more actions at any point in time. |
| 4 | + |
| 5 | +## AER |
| 6 | + |
| 7 | +AER (Action Event Reaction) is a design pattern used in AER World that describes a way to clearly track and manage nested state changes caused by actions. The best way to demonstrate this is with an example. |
| 8 | + |
| 9 | +The following is an example of AER World simulating the game state as `Golem 1` attempts to move away from `Player 0`. |
| 10 | + |
| 11 | +``` |
| 12 | +---- Player 0 ---- |
| 13 | +life: 10/10 + 10 |
| 14 | +position: (0, 0) |
| 15 | +reactions: [OpportunityAttack { damage_amount: 3 }] |
| 16 | +
|
| 17 | +
|
| 18 | +---- Golem 1 ---- |
| 19 | +life: 2/3 + 2 |
| 20 | +position: (0, 0) |
| 21 | +reactions: [Reinforce { armor_amount: 3 }] |
| 22 | +
|
| 23 | +[Action] 1 -> 1 Move { to_position: (0, 1) } |
| 24 | +[Event] 1 -> 1 Moved { from_position: (0, 0) } |
| 25 | +[Reaction] 0 OpportunityAttack { damage_amount: 3 } |
| 26 | + [Action] 0 -> 1 DealDamage { amount: 3 } |
| 27 | + [Event] 0 -> 1 Damaged |
| 28 | + [Reaction] 1 Reinforce { armor_amount: 3 } |
| 29 | + [Action] 1 -> 1 GainArmor { amount: 3 } |
| 30 | +
|
| 31 | +---- Golem 1 ---- |
| 32 | +life: 1/3 + 3 |
| 33 | +position: (0, 1) |
| 34 | +reactions: [Reinforce { armor_amount: 3 }] |
| 35 | +``` |
| 36 | + |
| 37 | +1. When `Golem 1` moves away from `Player 0` as its enemy, `Player 0`'s "Opportunity Attack" reaction triggers. This interrupts the move action, and spawns a new action to deal 3 damage to the Golem. |
| 38 | +2. When `Golem 1` takes 3 damage, its own "Reinforce" reaction triggers. This interrupts the damage action, and spawns a new action to gain 3 armor for the Golem. |
| 39 | +3. Since there are no more reactions to any of the actions on the stack, the actions are popped. The golem is left with 1 HP and 3 Armor as a result of moving away from the player. |
| 40 | + |
| 41 | +The AER pattern does not permit direct mutation of systems. Instead, every possible state change must come from an **action**. An action must be performed by a source entity onto a target entity (the target can also be the source). |
| 42 | + |
| 43 | +Entity queries can be used to provide a "fuzzy search" for action targets, like AOE attacks that attack a map region as opposed to a single target. |
| 44 | + |
| 45 | +An action may mutate state multiple times, across multiple systems. For instance, dealing damage might first interact with the armor system to absorb some damage before interacting with the health system. |
| 46 | + |
| 47 | +After each system state mutation, an action may choose to emit an event based on the result having met some criteria. |
| 48 | + |
| 49 | +**Events** should only be created and used if there is a reaction that depends on it. For example, it does not make sense to create a `Death` event if there are no reactions in the world that care about death. However, it does make sense to add an event for `Move` because Opportunity Attacks may occur if an entity moves away from an enemy. |
| 50 | + |
| 51 | +**Reactions** are pre-defined event handlers, which conditionally perform more actions. They happen after an event is fired, which can happen at any point while an existing action is occuring. When a reaction occurs, it pauses the existing action and executes immediately. For example, the `Reinforce` reaction gains the reactor 3 armor whenever they are damaged. |
| 52 | + |
| 53 | +AER does not support custom runtime reactions for entities. Every reaction must already exist in the world, although you may add or remove reactions from entities during runtime as part of an action. |
| 54 | + |
| 55 | +## ECS |
| 56 | + |
| 57 | +Game world objects are represented using the [ECS](https://en.wikipedia.org/wiki/Entity_component_system) pattern. This is done for three reasons: |
| 58 | + |
| 59 | +1. **State change clarity** - when actions are performed, it becomes clear which systems are involved with the action, while not caring about what the entity actually is. |
| 60 | +2. **Flexible component storage** - because an entity's data is scattered across components, we are free to store the components in any way that we see fit. We could use a bidirectional map in a system for example, to efficiently query all entities in a specific position, as well as query the position of any entity. This also means that we don't have to store many variations of the same entity just to improve performance. |
| 61 | +3. **Powerful entity querying** - with ECS, we can making entity querying simple yet exhaustive by doing a simple set intersection for entities that have a specific component, or meet some criteria relating to the component. |
| 62 | + |
| 63 | +To keep things simple, AER World systems currently use identity hash maps to store mappings between entities and components. |
| 64 | + |
| 65 | +AER World uses [hierarchical sparse bitsets](https://github.com/tower120/hi_sparse_bitset) to perform entity queries, which provide extremely fast set intersection performance across any number of sets, at any size. |
| 66 | + |
| 67 | +## Development Flow |
| 68 | + |
| 69 | +AER World has two crates. `world` is the library for the game state container and `playground` is the simulation binary that allows you to test the public interface of `world`. |
| 70 | + |
| 71 | +Just execute `cargo run` to start the `playground` simulation. |
| 72 | + |
| 73 | +`world` will only log actions, events, and reactions to `stdout` when `debug_assertions` is enabled. The `World::describe(entity: &EntityId)` method can be used to log every component of an entity. |
0 commit comments