Skip to content
Flecs v4.1
game_mechanics/inventory_system

  • C++

    View on GitHub

    1#include <inventory_system.h>
    2#include <iostream>
    3
    4// This example shows one possible way to implement an inventory system using
    5// ECS relationships.
    6
    7// Inventory tags/relationships
    8struct Item { }; // Base item type
    9struct Container { }; // Container tag
    10struct Inventory { }; // Inventory tag
    11struct ContainedBy { }; // ContainedBy relationship
    12
    13// Item / unit properties
    14struct Active { }; // Item is active/worn
    15
    16struct Amount {
    17 int value; // Number of items the instance represents
    18};
    19
    20struct Health {
    21 int value; // Health of the item
    22};
    23
    24struct Attack {
    25 int value; // Amount of damage an item deals per use
    26};
    27
    28// Items
    29struct Sword : Item { };
    30struct Armor : Item { };
    31struct Coin : Item { };
    32
    33// Item prefab types
    34struct WoodenSword { };
    35struct IronSword { };
    36struct WoodenArmor { };
    37struct IronArmor { };
    38
    39// Find Item kind of entity
    40flecs::entity item_kind(flecs::entity item) {
    41 flecs::world world = item.world();
    42 flecs::entity result;
    43
    44 item.each([&](flecs::id id) {
    45 if (id.is_entity()) {
    46 // If id is a plain entity (component), check if component inherits
    47 // from Item
    48 if (id.entity().has(flecs::IsA, world.id<Item>())) {
    49 result = id.entity();
    50 }
    51 } else if (id.is_pair()) {
    52 // If item has a base entity, check if the base has an attribute
    53 // that is an Item.
    54 if (id.first() == flecs::IsA) {
    55 flecs::entity base_kind = item_kind(id.second());
    56 if (base_kind) {
    57 result = base_kind;
    58 }
    59 }
    60 }
    61 });
    62
    63 return result;
    64}
    65
    66// Almost the same as item_kind, but return name of prefab vs item kind. This
    67// returns a more user-friendly name, like "WoodenSword" vs. just "Sword"
    68const char* item_name(flecs::entity item) {
    69 flecs::world world = item.world();
    70 const char *result = NULL;
    71
    72 item.each([&](flecs::id id) {
    73 if (id.is_entity()) {
    74 if (id.entity().has(flecs::IsA, world.id<Item>())) {
    75 result = id.entity().name();
    76 }
    77 } else if (id.is_pair()) {
    78 if (id.first() == flecs::IsA) {
    79 flecs::entity base_kind = item_kind(id.second());
    80 if (base_kind) {
    81 result = id.second().name();
    82 }
    83 }
    84 }
    85 });
    86
    87 return result;
    88}
    89
    90// If entity is not a container, get its inventory
    91flecs::entity get_container(flecs::entity container) {
    92 if (container.has<Container>()) {
    93 return container;
    94 }
    95 return container.target<Inventory>();
    96}
    97
    98// Iterate all items in an inventory
    99template <typename Func>
    100void for_each_item(flecs::entity container, const Func& func) {
    101 container.world().query_builder()
    102 .with<ContainedBy>(container)
    103 .each(func);
    104}
    105
    106// Find item in inventory of specified kind
    107flecs::entity find_item_w_kind(
    108 flecs::entity container, flecs::entity kind, bool active_required = false)
    109{
    110 flecs::entity result;
    111
    112 container = get_container(container);
    113
    114 for_each_item(container, [&](flecs::entity item) {
    115 // Check if we should only return active items. This is useful when
    116 // searching for an item that needs to be equipped.
    117 if (active_required) {
    118 if (!item.has<Active>()) {
    119 return;
    120 }
    121 }
    122
    123 flecs::entity ik = item_kind(item);
    124 if (ik == kind) {
    125 result = item;
    126 }
    127 });
    128
    129 return result;
    130}
    131
    132// Transfer item to container
    133void transfer_item(flecs::entity container, flecs::entity item) {
    134 const Amount* amt = item.try_get<Amount>();
    135 if (amt) {
    136 // If item has amount we need to check if the container already has an
    137 // item of this kind, and increase the value.
    138 flecs::world ecs = container.world();
    139 flecs::entity ik = item_kind(item);
    140 flecs::entity dst_item = find_item_w_kind(container, ik);
    141 if (dst_item) {
    142 // If a matching item was found, increase its amount
    143 Amount& dst_amt = dst_item.ensure<Amount>();
    144 dst_amt.value += amt->value;
    145 item.destruct(); // Remove the src item
    146 return;
    147 } else {
    148 // If no matching item was found, fallthrough which will move the
    149 // item from the src container to the dst container
    150 }
    151 }
    152
    153 // Move item to target container (replaces previous ContainedBy, if any)
    154 item.add<ContainedBy>(container);
    155}
    156
    157// Move items from one container to another
    158void transfer_items(flecs::entity dst, flecs::entity src) {
    159 std::cout << ">> Transfer items from "
    160 << src.name() << " to " << dst.name() << "\n\n";
    161
    162 // Defer, because we're adding/removing components while we're iterating
    163 flecs::world stage = dst.world().get_stage(0);
    164 dst = get_container(dst); // Make sure to replace players with container
    165 src = get_container(src);
    166
    167 for_each_item(src, [&](flecs::entity item) {
    168 transfer_item(dst.mut(stage), item.mut(stage));
    169 });
    170 stage.merge();
    171}
    172
    173// Attack player
    174void attack(flecs::entity player, flecs::entity weapon) {
    175 flecs::world ecs = player.world();
    176
    177 std::cout << ">> " << player.name() << " is attacked with a "
    178 << item_name(weapon) << "!\n";
    179
    180 const Attack *att = weapon.try_get<Attack>();
    181 if (!att) {
    182 // A weapon without Attack power? Odd.
    183 std::cout << " - the weapon is a dud\n";
    184 return;
    185 }
    186
    187 int att_value = att->value;
    188
    189 // Get armor item, if player has equipped any
    190 flecs::entity armor = find_item_w_kind(player, ecs.entity<Armor>(), true);
    191 if (armor) {
    192 Health *armor_health = armor.try_get_mut<Health>();
    193 if (!armor_health) {
    194 // Armor without Defense power? Odd.
    195 std::cout << " - the " << item_name(armor) << " armor is a dud\n";
    196 } else {
    197 std::cout << " - " << player.name() << " defends with "
    198 << item_name(armor) << "\n";
    199
    200 // Subtract attack from armor health. If armor health goes below
    201 // zero, delete the armor and carry over remaining attack points.
    202 armor_health->value -= att_value;
    203 if (armor_health->value <= 0) {
    204 att_value += armor_health->value;
    205 armor.destruct();
    206 std::cout << " - " << item_name(armor) << " is destroyed!\n";
    207 } else {
    208 std::cout << " - " << item_name(armor) << " has "
    209 << armor_health->value << " health left after taking "
    210 << att_value << " damage\n";
    211 att_value = 0;
    212 }
    213 }
    214 } else {
    215 // Brave but stupid
    216 std::cout << " - " << player.name() << " fights without armor!\n";
    217 }
    218
    219 // For each usage of the weapon, subtract one from its health
    220 Health& weapon_health = weapon.ensure<Health>();
    221 if (!--weapon_health.value) {
    222 std::cout << " - " << item_name(weapon) << " is destroyed!\n";
    223 weapon.destruct();
    224 } else {
    225 std::cout << " - " << item_name(weapon) << " has "
    226 << weapon_health.value << " uses left";
    227 }
    228
    229 // If armor didn't counter the whole attack, subtract from the player health
    230 if (att_value) {
    231 Health& player_health = player.ensure<Health>();
    232 if (!(player_health.value -= att_value)) {
    233 std::cout << " - " << player.name() << " died!\n";
    234 player.destruct();
    235 } else {
    236 std::cout << " - " << player.name() << " has "
    237 << player_health.value << " health left after taking "
    238 << att_value << " damage\n";
    239 }
    240 }
    241
    242 std::cout << "\n";
    243}
    244
    245// Print items in inventory
    246void print_items(flecs::entity container) {
    247 std::cout << "-- " << container.name() << "'s inventory:\n";
    248
    249 // In case the player entity was provided, make sure we're working with its
    250 // inventory entity.
    251 container = get_container(container);
    252
    253 int32_t count = 0;
    254 for_each_item(container, [&](flecs::entity item) {
    255 // Items with an Amount component fill up a single inventory slot but
    256 // represent multiple instances, like coins.
    257 int32_t amount = 1;
    258 item.get([&](const Amount& amt) {
    259 amount = amt.value;
    260 });
    261
    262 std::cout << " - " << amount << " " << item_name(item);
    263 if (amount > 1) std::cout << "s";
    264 std::cout << " (" << item_kind(item).name() << ")\n";
    265
    266 count ++;
    267 });
    268
    269 if (!count) {
    270 std::cout << " - << empty >>\n";
    271 }
    272
    273 std::cout << "\n";
    274}
    275
    276int main(int, char *[]) {
    277 flecs::world ecs;
    278
    279 // Register ContainedBy relationship
    280 ecs.component<ContainedBy>()
    281 .add(flecs::Exclusive); // Item can only be contained by one container
    282
    283 // Register item kinds
    284 ecs.component<Sword>().is_a<Item>();
    285 ecs.component<Armor>().is_a<Item>();
    286 ecs.component<Coin>().is_a<Item>();
    287
    288 // Register item prefabs
    289 ecs.prefab<WoodenSword>().add<Sword>()
    290 .set<Attack>({ 1 })
    291 .set_auto_override<Health>({ 5 }); // copy to instance, don't share
    292
    293 ecs.prefab<IronSword>().add<Sword>()
    294 .set<Attack>({ 2 })
    295 .set_auto_override<Health>({ 10 });
    296
    297 ecs.prefab<WoodenArmor>().add<Armor>()
    298 .set_auto_override<Health>({ 10 });
    299
    300 ecs.prefab<IronArmor>().add<Armor>()
    301 .set_auto_override<Health>({ 20 });
    302
    303 // Create a loot box with items
    304 flecs::entity loot_box = ecs.entity("Chest").add<Container>();
    305 ecs.entity().is_a<IronSword>().add<ContainedBy>(loot_box);
    306 ecs.entity().is_a<WoodenArmor>().add<ContainedBy>(loot_box);
    307 ecs.entity().add<Coin>().set<Amount>({ 30 }).add<ContainedBy>(loot_box);
    308
    309 // Create a player entity with an inventory
    310 flecs::entity player_inventory = ecs.entity().add<Container>();
    311 ecs.entity().add<Coin>().set<Amount>({ 20 })
    312 .add<ContainedBy>(player_inventory);
    313 flecs::entity player = ecs.entity("Player").set<Health>({10})
    314 .add<Inventory>(player_inventory);
    315
    316 // Print items in loot box
    317 print_items(loot_box);
    318
    319 // Print items in player inventory
    320 print_items(player);
    321
    322 // Copy items from loot box to player inventory
    323 transfer_items(player, loot_box);
    324
    325 // Print items in player inventory after transfer
    326 print_items(player);
    327
    328 // Print items in loot box after transfer
    329 print_items(loot_box);
    330
    331 // Find armor entity & equip it
    332 flecs::entity armor = find_item_w_kind(player, ecs.entity<Armor>());
    333 if (armor) {
    334 armor.add<Active>();
    335 }
    336
    337 // Create a weapon to attack the player with
    338 flecs::entity my_sword = ecs.entity()
    339 .is_a<IronSword>();
    340
    341 // Attack player
    342 attack(player, my_sword);
    343
    344 std::cout << "\n";
    345
    346 // Output
    347 // -- Chest's inventory:
    348 // - 1 IronSword (Sword)
    349 // - 1 WoodenArmor (Armor)
    350 // - 30 Coins (Coin)
    351
    352 // -- Player's inventory:
    353 // - 20 Coins (Coin)
    354
    355 // >> Transfer items from Chest to Player
    356
    357 // -- Player's inventory:
    358 // - 50 Coins (Coin)
    359 // - 1 IronSword (Sword)
    360 // - 1 WoodenArmor (Armor)
    361
    362 // -- Chest's inventory:
    363 // - << empty >>
    364
    365 // >> Player is attacked with a IronSword!
    366 // - Player defends with WoodenArmor
    367 // - WoodenArmor has 8 health left after taking 2 damage
    368 // - IronSword has 9 health left
    369}
    flecs::component< T > component(Args &&... args) const
    Find or register a component.
    Definition impl.hpp:11
    flecs::query_builder< Comps... > query_builder(Args &&... args) const
    Create a query builder.
    Definition impl.hpp:342
    flecs::entity entity(Args &&... args) const
    Create an entity.
    Definition impl.hpp:195
    flecs::entity prefab(const char *name=nullptr) const
    Create a prefab that's associated with a type.
    Definition impl.hpp:18
    const Self & is_a(entity_t second) const
    Shortcut for add(IsA, entity).
    Definition builder.hpp:122
    flecs::string_view name() const
    Return the entity name.
    void each(const Func &func) const
    Iterate (component) IDs of an entity.
    Definition impl.hpp:155
    flecs::entity target(int32_t index=0) const
    Get target for a given pair.
    Definition impl.hpp:36
    flecs::entity mut(const flecs::world &stage) const
    Return a mutable entity handle for the current stage.
    Definition impl.hpp:72
    Entity.
    Definition entity.hpp:30
    void destruct() const
    Delete an entity.
    Definition entity.hpp:208
    entity()
    Default constructor.
    Definition entity.hpp:32
    Class that wraps around a flecs::id_t.
    Definition decl.hpp:27
    flecs::world world() const
    Get the world.
    Definition impl.hpp:60
    The world.
    Definition world.hpp:129
    void merge() const
    Merge world or stage.
    Definition world.hpp:335
    flecs::id id() const
    Get ID from a type.
    Definition impl.hpp:72
    void each(Func &&func) const
    Iterate over all entities with components in the argument list of the function.
    flecs::world get_stage(int32_t stage_id) const
    Get stage-specific world pointer.
    Definition world.hpp:353
    flecs::entity entity(Args &&... args) const
    Create an entity.