Properly link flecs library

This commit is contained in:
2023-11-09 11:38:29 +01:00
parent dc585396c3
commit 8edcf9305c
1392 changed files with 390081 additions and 164 deletions

View File

@@ -0,0 +1,16 @@
#ifndef FACTORY_H
#define FACTORY_H
/* This generated file contains includes for project dependencies */
#include "factory/bake_config.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,24 @@
/*
)
(.)
.|.
| |
_.--| |--._
.-'; ;`-'& ; `&.
\ & ; & &_/
|"""---...---"""|
\ | | | | | | | /
`---.|.|.|.---'
* This file is generated by bake.lang.c for your convenience. Headers of
* dependencies will automatically show up in this file. Include bake_config.h
* in your main project file. Do not edit! */
#ifndef FACTORY_BAKE_CONFIG_H
#define FACTORY_BAKE_CONFIG_H
/* Headers of public dependencies */
#include <flecs.h>
#endif

View File

@@ -0,0 +1,10 @@
{
"id": "factory",
"type": "application",
"value": {
"use": [
"flecs"
],
"language": "c++"
}
}

View File

@@ -0,0 +1,53 @@
using factories
resources {
// Ore stacks can contain 50 items
with StackCount{50} {
// Raw materials can't be produced
Copper
Iron
Aluminium
Steel {
- Requires[{Iron, 1}]
- TimeToProduce{2}
- StackCount{50}
}
}
Gear {
- Requires[{Iron, 1}]
- TimeToProduce{1}
- StackCount{100}
}
Circuit {
- Requires[{Iron, 1}, {Copper, 3}]
- TimeToProduce{2}
- StackCount{100}
}
SolarPanel {
- Requires[{Copper, 5}, {Circuit, 15}, {Steel, 5}]
- TimeToProduce{10}
- StackCount{20}
}
HullMaterial {
- Requires[{Aluminium, 10}, {Copper, 5}, {Steel, 20}]
- TimeToProduce{10}
- StackCount{20}
}
Radar {
- Requires[{Gear, 5}, {Circuit, 5}, {Iron, 10}]
- TimeToProduce{20}
- StackCount{1}
}
Satellite {
- Requires[{HullMaterial, 10}, {SolarPanel, 5}, {Radar, 1}]
- TimeToProduce{30}
- StackCount{1}
}
}

View File

@@ -0,0 +1,79 @@
using factories
using resources
// Resource depots
depot {
iron {
- (Stores, Iron){amount: 5000}
}
copper {
- (Stores, Copper){amount: 5000}
}
aluminium {
- (Stores, Aluminium){amount: 5000}
}
gear {
- (Stores, Gear){amount: 0}
}
steel {
- (Stores, Steel){amount: 0}
}
circuit {
- (Stores, Circuit){amount: 0}
}
}
// Factories
factory {
with Factory{ recipe: Gear, inputs: [depot.iron], output: depot.gear } {
gear_1
gear_2
}
with Factory{recipe: Steel, inputs: [depot.iron], output: depot.steel} {
steel_1
steel_2
steel_3
steel_4
steel_5
steel_6
}
with Factory{recipe: Circuit, inputs: [depot.iron, depot.copper], output: depot.circuit } {
circuit_1
circuit_2
}
radar {
- Factory{
recipe: Radar,
inputs: [depot.gear, depot.circuit, depot.iron]
}
}
solar_panel {
- Factory{
recipe: SolarPanel,
inputs: [depot.copper, depot.circuit, depot.steel]
}
}
hull {
- Factory{
recipe: HullMaterial,
inputs: [depot.aluminium, depot.copper, depot.steel]
}
}
satellite {
- Factory{
recipe: Satellite,
inputs: [hull, solar_panel, radar]
}
}
}

View File

@@ -0,0 +1,343 @@
#include <factory.h>
#include <iostream>
// This example shows how a simple production hierarchy can be simulated in ECS.
// The example has the following concepts:
//
// - A recipe describes the requirements and time required to produce a resource
// - Factories produce resources based on an assigned recipe
// - Depots store resources
//
// A factory needs to be connected to inputs that provide the resource. Once the
// inputs are connected correctly, the factory will start producing the resource
// once it has collected all of the required resources from the inputs.
//
// Factories can act as inputs for other factories. Depots can be used to
// combine the output of multiple factories.
//
// The resource definitions and scene are loaded at runtime from resource.flecs
// and scene.flecs.
//
// Few notes:
// - The demo doesn't have graphics, but can be viewed with the explorer.
// - Components store references to entities as entity_t's. They could also
// flecs::entity's, but this requires a bit less memory.
// - Some data is duplicated between resource and factory components so that
// systems can mostly operate on local data
//
using namespace flecs;
// Factory module
struct factories {
// Maximum number of resources a recipe can depend on
static constexpr int MaxInputs = 3;
// -- Recipe components
// Single resource requirement for a recipe
struct Requirement {
entity_t resource;
int32_t amount;
};
// All resource requirements for a recipe
struct Requires {
Requirement items[MaxInputs];
};
// Time it takes to produce a resource
struct TimeToProduce {
float value;
};
// Limit how many items can be stacked for a specific resource
struct StackCount {
int32_t amount;
};
// -- Depot & Factory components
// Resource storage
struct Stores {
int32_t amount;
};
// Factory configuration
struct Factory {
// Recipe for the resource to produce
entity_t recipe;
// Must provide resources as specified by recipe
entity_t inputs[MaxInputs];
// Optional output which allows a factory to forward items to a depot
entity_t output;
};
// Factory state
enum class FactoryState {
// Factory isn't connected to (correct) inputs yet
Idle,
// Factory is connected, but doesn't have the required resources yet
WaitingForResources,
// Factory is producing resource
Producing,
// Factory is done producing
TransferResource
};
// Tracks which resources have been supplied to factory
struct FactorySupply {
int32_t required[MaxInputs];
int32_t collected[MaxInputs];
flecs::ref<Stores> inputs[MaxInputs];
flecs::ref<Stores> output;
};
// Track production progress
struct FactoryProduction {
float value;
float duration;
int32_t max_stack;
};
// Module import function
factories(world& world) {
// Units improve visualization of component values in the explorer.
world.import<flecs::units>();
// -- Component registration
// Reflection data is registered so we can view the components from the
// explorer and assign them from .flecs files.
world.component<Stores>()
.member<int32_t>("amount");
world.component<Requirement>()
.member(flecs::Entity, "resource")
.member<int32_t>("amount");
world.component<Requires>()
.array<Requirement>(3);
world.component<FactorySupply>()
.member<int32_t>("required", 3)
.member<int32_t>("collected", 3);
world.component<TimeToProduce>()
.member<float, units::duration::Seconds>("value");
world.component<StackCount>()
.member<int32_t>("amount");
world.component<FactoryProduction>()
.member<float, units::Percentage>("value")
.member<float, units::duration::Seconds>("duration")
.member<int32_t>("max_stack");
world.component<Factory>()
// Factories start out idle
.on_add([](entity factory, Factory) {
factory.add(FactoryState::Idle);
})
// Check if factory is properly setup when value is assigned to component
.on_set(factory_init)
.member(flecs::Entity, "recipe")
.member(flecs::Entity, "inputs", 3)
.member(flecs::Entity, "output");
// System that collects resources from inputs
world.system<FactorySupply>("Collect")
.with(FactoryState::WaitingForResources)
.interval(1.0f)
.each([](entity factory, FactorySupply& s) {
bool satisfied = true;
for (int i = 0; i < MaxInputs; i ++) {
int32_t needed = s.required[i] - s.collected[i];
if (needed) {
Stores *p = s.inputs[i].get();
if (p->amount >= needed) {
s.collected[i] += needed;
p->amount -= needed;
} else {
s.collected[i] += p->amount;
p->amount = 0;
satisfied = false;
}
}
}
// If all resources are satisfied, change state of factory to producing
if (satisfied) {
// Consume collected resources
for (int i = 0; i < MaxInputs; i ++) {
s.collected[i] = 0;
}
factory.add(FactoryState::Producing);
}
});
// System that produces a resource once all input requirements are met
world.system<FactoryProduction>("Produce")
.with(FactoryState::Producing)
.interval(0.1f)
.each([](flecs::iter& it, size_t i, FactoryProduction& p) {
p.value += it.delta_system_time() / p.duration;
if (p.value >= 1) {
p.value = 1;
it.entity(i).add(FactoryState::TransferResource);
}
});
// System that transfers resource and resets factory for next item
world.system<FactorySupply, FactoryProduction, Stores>("Transfer")
.term_at(3).second(Wildcard)
.with(FactoryState::TransferResource)
.interval(1.0f)
.each([](entity factory, FactorySupply& s, FactoryProduction& p, Stores& out) {
// Reset production progress
p.value = 0;
// If depot is configured, transfer local resources first
Stores *depot = s.output.try_get();
if (depot) {
int32_t available_space = p.max_stack - depot->amount;
if (out.amount > available_space) {
depot->amount += available_space;
out.amount -= available_space;
} else {
depot->amount += out.amount;
out.amount = 0;
}
}
// Now check which storage to output the new resource item to. If a
// depot is configured, attempt to store it there, otherwise store it
// in factory.
Stores *store = &out;
if (depot) {
if (!p.max_stack || (depot->amount < p.max_stack)) {
store = depot;
}
}
if (store->amount < p.max_stack) {
// Add resource to storage and go back to collecting resources
store->amount ++;
factory.add(FactoryState::WaitingForResources);
} else {
// No space in output, do nothing
}
});
}
private:
// on_set hook for Factory component. When Factory component value is written,
// check if the factory has a recipe and whether the factory inputs satisfy
// the recipe requirements.
static void factory_init(entity factory, Factory& config) {
world world = factory.world();
entity recipe = world.entity(config.recipe);
recipe.get([&](const Requires& r) {
entity output = world.entity(config.output);
if (output) {
if (!output.has<Stores>(recipe)) {
std::cout << factory.path() << ": output doesn't provide resource "
<< recipe.path() << "\n";
return;
}
}
// For each recipe requirement, make sure a correct input is connected
bool satisfied = true;
for (int i = 0; i < MaxInputs; i ++) {
entity resource = world.entity(r.items[i].resource);
entity input = world.entity(config.inputs[i]);
if (!resource) {
if (input) {
std::cout << factory.path() <<
": input connected to empty input slot\n";
}
continue;
}
if (resource && !input) {
satisfied = false;
break;
}
if (!input.has<Stores>(resource)) {
std::cout << factory.path() << ": input doesn't provide resource "
<< recipe.path() << "\n";
satisfied = false;
break;
}
}
// If we got through all requirements without issues, factory is ready
// to start collecting resources
if (satisfied) {
factory.add(FactoryState::WaitingForResources);
// Initialize supply component
factory.set([&](FactorySupply& s) {
for (int i = 0; i < MaxInputs; i ++) {
entity resource = world.entity(r.items[i].resource);
entity input = world.entity(config.inputs[i]);
int32_t amount = r.items[i].amount;
s.required[i] = amount;
s.collected[i] = 0;
if (!resource) {
continue;
}
// Take a ref to the resource provider for the factory input. Refs
// provide quick and convenient access for repeatedly accessing a
// component from the same entity.
s.inputs[i] = input.get_ref<Stores>(resource);
}
if (output) {
// If the factory has an output configured, also take a ref to it so
// we can quickly add the produced resources to it.
s.output = output.get_ref<Stores>(recipe);
}
});
// Add component that tracks how much time is left to produce a resource
recipe.get([&](const TimeToProduce& ttp, const StackCount& sc) {
factory.set<FactoryProduction>({ 0, ttp.value, sc.amount });
});
// Set output resource for factory
factory.add<Stores>(recipe);
}
});
}
};
int main(int argc, char *argv[]) {
flecs::world world(argc, argv);
world.import<factories>();
ecs_plecs_from_file(world, "resources.flecs");
ecs_plecs_from_file(world, "scene.flecs");
return world.app()
.enable_rest()
.target_fps(60)
.delta_time(1.0f / 60.0f) // Run at fixed time step
.run();
}

View File

@@ -0,0 +1,16 @@
#ifndef INVENTORY_SYSTEM_H
#define INVENTORY_SYSTEM_H
/* This generated file contains includes for project dependencies */
#include "inventory_system/bake_config.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,24 @@
/*
)
(.)
.|.
| |
_.--| |--._
.-'; ;`-'& ; `&.
\ & ; & &_/
|"""---...---"""|
\ | | | | | | | /
`---.|.|.|.---'
* This file is generated by bake.lang.c for your convenience. Headers of
* dependencies will automatically show up in this file. Include bake_config.h
* in your main project file. Do not edit! */
#ifndef INVENTORY_SYSTEM_BAKE_CONFIG_H
#define INVENTORY_SYSTEM_BAKE_CONFIG_H
/* Headers of public dependencies */
#include <flecs.h>
#endif

View File

@@ -0,0 +1,11 @@
{
"id": "inventory_system",
"type": "application",
"value": {
"use": [
"flecs"
],
"public": false,
"language": "c++"
}
}

View File

@@ -0,0 +1,371 @@
#include <inventory_system.h>
#include <iostream>
// This example shows one possible way to implement an inventory system using
// ECS relationships.
// Inventory tags/relationships
struct Item { }; // Base item type
struct Container { }; // Container tag
struct Inventory { }; // Inventory tag
struct ContainedBy { }; // ContainedBy relationship
// Item / unit properties
struct Active { }; // Item is active/worn
struct Amount {
int value; // Number of items the instance represents
};
struct Health {
int value; // Health of the item
};
struct Attack {
int value; // Amount of damage an item deals per use
};
// Items
struct Sword : Item { };
struct Armor : Item { };
struct Coin : Item { };
// Item prefab types
struct WoodenSword { };
struct IronSword { };
struct WoodenArmor { };
struct IronArmor { };
// Find Item kind of entity
flecs::entity item_kind(flecs::entity item) {
flecs::world world = item.world();
flecs::entity result;
item.each([&](flecs::id id) {
if (id.is_entity()) {
// If id is a plain entity (component), check if component inherits
// from Item
if (id.entity().has(flecs::IsA, world.id<Item>())) {
result = id.entity();
}
} else if (id.is_pair()) {
// If item has a base entity, check if the base has an attribute
// that is an Item.
if (id.first() == flecs::IsA) {
flecs::entity base_kind = item_kind(id.second());
if (base_kind) {
result = base_kind;
}
}
}
});
return result;
}
// Almost the same as item_kind, but return name of prefab vs item kind. This
// returns a more user-friendly name, like "WoodenSword" vs. just "Sword"
const char* item_name(flecs::entity item) {
flecs::world world = item.world();
const char *result = NULL;
item.each([&](flecs::id id) {
if (id.is_entity()) {
if (id.entity().has(flecs::IsA, world.id<Item>())) {
result = id.entity().name();
}
} else if (id.is_pair()) {
if (id.first() == flecs::IsA) {
flecs::entity base_kind = item_kind(id.second());
if (base_kind) {
result = id.second().name();
}
}
}
});
return result;
}
// If entity is not a container, get its inventory
flecs::entity get_container(flecs::entity container) {
if (container.has<Container>()) {
return container;
}
return container.target<Inventory>();
}
// Iterate all items in an inventory
template <typename Func>
void for_each_item(flecs::entity container, const Func& func) {
container.world().filter_builder()
.with<ContainedBy>(container)
.build()
.each(func);
}
// Find item in inventory of specified kind
flecs::entity find_item_w_kind(
flecs::entity container, flecs::entity kind, bool active_required = false)
{
flecs::entity result;
container = get_container(container);
for_each_item(container, [&](flecs::entity item) {
// Check if we should only return active items. This is useful when
// searching for an item that needs to be equipped.
if (active_required) {
if (!item.has<Active>()) {
return;
}
}
flecs::entity ik = item_kind(item);
if (ik == kind) {
result = item;
}
});
return result;
}
// Transfer item to container
void transfer_item(flecs::entity container, flecs::entity item) {
const Amount *amt = item.get<Amount>();
if (amt) {
// If item has amount we need to check if the container already has an
// item of this kind, and increase the value.
flecs::world ecs = container.world();
flecs::entity ik = item_kind(item);
flecs::entity dst_item = find_item_w_kind(container, ik);
if (dst_item) {
// If a matching item was found, increase its amount
Amount *dst_amt = dst_item.get_mut<Amount>();
dst_amt->value += amt->value;
item.destruct(); // Remove the src item
return;
} else {
// If no matching item was found, fallthrough which will move the
// item from the src container to the dst container
}
}
// Move item to target container (replaces previous ContainedBy, if any)
item.add<ContainedBy>(container);
}
// Move items from one container to another
void transfer_items(flecs::entity dst, flecs::entity src) {
std::cout << ">> Transfer items from "
<< src.name() << " to " << dst.name() << "\n\n";
// Defer, because we're adding/removing components while we're iterating
dst.world().defer([&] {
dst = get_container(dst); // Make sure to replace players with container
src = get_container(src);
for_each_item(src, [&](flecs::entity item) {
transfer_item(dst, item);
});
});
}
// Attack player
void attack(flecs::entity player, flecs::entity weapon) {
flecs::world ecs = player.world();
std::cout << ">> " << player.name() << " is attacked with a "
<< item_name(weapon) << "!\n";
const Attack *att = weapon.get<Attack>();
if (!att) {
// A weapon without Attack power? Odd.
std::cout << " - the weapon is a dud\n";
return;
}
int att_value = att->value;
// Get armor item, if player has equipped any
flecs::entity armor = find_item_w_kind(player, ecs.entity<Armor>(), true);
if (armor) {
Health *armor_health = armor.get_mut<Health>();
if (!armor_health) {
// Armor without Defense power? Odd.
std::cout << " - the " << item_name(armor) << " armor is a dud\n";
} else {
std::cout << " - " << player.name() << " defends with "
<< item_name(armor) << "\n";
// Subtract attack from armor health. If armor health goes below
// zero, delete the armor and carry over remaining attack points.
armor_health->value -= att_value;
if (armor_health->value <= 0) {
att_value += armor_health->value;
armor.destruct();
std::cout << " - " << item_name(armor) << " is destroyed!\n";
} else {
std::cout << " - " << item_name(armor) << " has "
<< armor_health->value << " health left after taking "
<< att_value << " damage\n";
att_value = 0;
}
}
} else {
// Brave but stupid
std::cout << " - " << player.name() << " fights without armor!\n";
}
// For each usage of the weapon, subtract one from its health
Health *weapon_health = weapon.get_mut<Health>();
if (!--weapon_health->value) {
std::cout << " - " << item_name(weapon) << " is destroyed!\n";
weapon.destruct();
} else {
std::cout << " - " << item_name(weapon) << " has "
<< weapon_health->value << " uses left";
}
// If armor didn't counter the whole attack, subtract from the player health
if (att_value) {
Health *player_health = player.get_mut<Health>();
if (!(player_health->value -= att_value)) {
std::cout << " - " << player.name() << " died!\n";
player.destruct();
} else {
std::cout << " - " << player.name() << " has "
<< player_health->value << " health left after taking "
<< att_value << " damage\n";
}
}
std::cout << "\n";
}
// Print items in inventory
void print_items(flecs::entity container) {
std::cout << "-- " << container.name() << "'s inventory:\n";
// In case the player entity was provided, make sure we're working with its
// inventory entity.
container = get_container(container);
int32_t count = 0;
for_each_item(container, [&](flecs::entity item) {
// Items with an Amount component fill up a single inventory slot but
// represent multiple instances, like coins.
int32_t amount = 1;
item.get([&](const Amount& amt) {
amount = amt.value;
});
std::cout << " - " << amount << " " << item_name(item);
if (amount > 1) std::cout << "s";
std::cout << " (" << item_kind(item).name() << ")\n";
count ++;
});
if (!count) {
std::cout << " - << empty >>\n";
}
std::cout << "\n";
}
int main(int, char *[]) {
flecs::world ecs;
// Register ContainedBy relationship
ecs.component<ContainedBy>()
.add(flecs::Exclusive); // Item can only be contained by one container
// Register item kinds
ecs.component<Sword>().is_a<Item>();
ecs.component<Armor>().is_a<Item>();
ecs.component<Coin>().is_a<Item>();
// Register item prefabs
ecs.prefab<WoodenSword>().add<Sword>()
.set<Attack>({ 1 })
.set_override<Health>({ 5 }); // copy to instance, don't share
ecs.prefab<IronSword>().add<Sword>()
.set<Attack>({ 2 })
.set_override<Health>({ 10 });
ecs.prefab<WoodenArmor>().add<Armor>()
.set_override<Health>({ 10 });
ecs.prefab<IronArmor>().add<Armor>()
.set_override<Health>({ 20 });
// Create a loot box with items
flecs::entity loot_box = ecs.entity("Chest").add<Container>().with<ContainedBy>([&]{
ecs.entity().is_a<IronSword>();
ecs.entity().is_a<WoodenArmor>();
ecs.entity().add<Coin>().set<Amount>({ 30 });
});
// Create a player entity with an inventory
flecs::entity player = ecs.entity("Player").set<Health>({10}).add<Inventory>(
ecs.entity().add<Container>().with<ContainedBy>([&]{
ecs.entity().add<Coin>().set<Amount>({ 20 });
})
);
// Print items in loot box
print_items(loot_box);
// Print items in player inventory
print_items(player);
// Copy items from loot box to player inventory
transfer_items(player, loot_box);
// Print items in player inventory after transfer
print_items(player);
// Print items in loot box after transfer
print_items(loot_box);
// Find armor entity & equip it
flecs::entity armor = find_item_w_kind(player, ecs.entity<Armor>());
if (armor) {
armor.add<Active>();
}
// Create a weapon to attack the player with
flecs::entity my_sword = ecs.entity()
.is_a<IronSword>();
// Attack player
attack(player, my_sword);
std::cout << "\n";
// Output
// -- Chest's inventory:
// - 1 IronSword (Sword)
// - 1 WoodenArmor (Armor)
// - 30 Coins (Coin)
// -- Player's inventory:
// - 20 Coins (Coin)
// >> Transfer items from Chest to Player
// -- Player's inventory:
// - 50 Coins (Coin)
// - 1 IronSword (Sword)
// - 1 WoodenArmor (Armor)
// -- Chest's inventory:
// - << empty >>
// >> Player is attacked with a IronSword!
// - Player defends with WoodenArmor
// - WoodenArmor has 8 health left after taking 2 damage
// - IronSword has 9 health left
}

View File

@@ -0,0 +1,16 @@
#ifndef SCENE_MANAGEMENT_H
#define SCENE_MANAGEMENT_H
/* This generated file contains includes for project dependencies */
#include "scene_management/bake_config.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,24 @@
/*
)
(.)
.|.
| |
_.--| |--._
.-'; ;`-'& ; `&.
\ & ; & &_/
|"""---...---"""|
\ | | | | | | | /
`---.|.|.|.---'
* This file is generated by bake.lang.c for your convenience. Headers of
* dependencies will automatically show up in this file. Include bake_config.h
* in your main project file. Do not edit! */
#ifndef SCENE_MANAGEMENT_BAKE_CONFIG_H
#define SCENE_MANAGEMENT_BAKE_CONFIG_H
/* Headers of public dependencies */
#include <flecs.h>
#endif

View File

@@ -0,0 +1,11 @@
{
"id": "scene_management",
"type": "application",
"value": {
"use": [
"flecs"
],
"public": false,
"language": "c++"
}
}

View File

@@ -0,0 +1,190 @@
#include <scene_management.h>
#include <iostream>
#include <string>
// This example shows one possible way to implement scene management
// using pipelines.
// Scene relationships/tags
struct ActiveScene { }; // Represents the current scene
struct SceneRoot { }; // Parent for all entities unique to the scene
// Scenes
using Pipeline = flecs::entity;
struct MenuScene { Pipeline pip; };
struct GameScene { Pipeline pip; };
// Components for Example
struct Position { float x, y; };
struct Button { std::string text; };
struct Character { bool alive; };
struct Health { int amount; };
// Removes all entities who are children of
// the current scene root.
// (NOTE: should use defer_begin() / defer_end())
void reset_scene(flecs::world& ecs) {
ecs.delete_with(flecs::ChildOf, ecs.entity<SceneRoot>());
}
void menu_scene(flecs::iter& it, size_t, ActiveScene) {
std::cout << "\n>> ActiveScene has changed to `MenuScene`\n\n";
flecs::world ecs = it.world();
flecs::entity scene = ecs.component<SceneRoot>();
reset_scene(ecs);
// Creates a start menu button
// when we enter the menu scene.
ecs.entity("Start Button")
.set(Button{ "Play the Game!" })
.set(Position{ 50, 50 })
.child_of(scene);
ecs.set_pipeline(ecs.get<MenuScene>()->pip);
}
void game_scene(flecs::iter& it, size_t, ActiveScene) {
std::cout << "\n>> ActiveScene has changed to `GameScene`\n\n";
flecs::world ecs = it.world();
flecs::entity scene = ecs.component<SceneRoot>();
reset_scene(ecs);
// Creates a player character
// when we enter the game scene.
ecs.entity("Player")
.set(Character{ })
.set(Health{ 2 })
.set(Position{ 0, 0 })
.child_of(scene);
ecs.set_pipeline(ecs.get<GameScene>()->pip);
}
void init_scenes(flecs::world& ecs) {
// Can only have one active scene
// in a game at a time.
ecs.component<ActiveScene>()
.add(flecs::Exclusive);
// Each scene gets a pipeline that
// runs the associated systems plus
// all other scene-agnostic systems.
flecs::entity menu = ecs.pipeline()
.with(flecs::System)
.without<GameScene>() // Use "without()" of the other scenes
// so that we can run every system that
// doesn't have a scene attached to it.
.build();
flecs::entity game = ecs.pipeline()
.with(flecs::System)
.without<MenuScene>()
.build();
// Set pipeline entities on the scenes
// to easily find them later with get().
ecs.set<MenuScene>({ menu });
ecs.set<GameScene>({ game });
// Observer to call scene change logic for
// MenuScene when added to the ActiveScene.
ecs.observer<ActiveScene>("Scene Change to Menu")
.event(flecs::OnAdd)
.second<MenuScene>()
.each(menu_scene);
// Observer to call scene change logic for
// GameScene when added to the ActiveScene.
ecs.observer<ActiveScene>("Scene Change to Game")
.event(flecs::OnAdd)
.second<GameScene>()
.each(game_scene);
}
void init_systems(flecs::world& ecs) {
// Will run every time regardless of the
// current scene we're in.
ecs.system<const Position>("Print Position")
.each([](flecs::entity e, const Position& p) {
// Prints out the position of the
// entity.
std::cout << e.name() << ": {" << p.x << ", " << p.y << "}\n";
});
// Will only run when the game scene is
// currently active.
ecs.system<Health>("Characters Lose Health")
.kind<GameScene>()
.each([](Health& h) {
// Prints out the character's health
// and then decrements it by one.
std::cout << h.amount << " health remaining\n";
h.amount--;
});
// Will only run when the menu scene is
// currently active.
ecs.system<const Button>("Print Menu Button Text")
.kind<MenuScene>()
.each([](const Button& b) {
// Prints out the text of the menu
// button.
std::cout << "Button says \"" << b.text << "\"\n";
});
}
int main(int, char *[]) {
flecs::world ecs;
init_scenes(ecs);
init_systems(ecs);
ecs.add<ActiveScene, MenuScene>();
ecs.progress();
ecs.add<ActiveScene, GameScene>();
ecs.progress();
ecs.progress();
ecs.progress();
ecs.add<ActiveScene, MenuScene>();
ecs.progress();
ecs.add<ActiveScene, GameScene>();
ecs.progress();
ecs.progress();
ecs.progress();
// Output
// >> ActiveScene has changed to `MenuScene`
// Start Button: {50, 50}
// Button says "Play the Game!"
// >> ActiveScene has changed to `GameScene`
// Player: {0, 0}
// 2 health remaining
// Player: {0, 0}
// 1 health remaining
// Player: {0, 0}
// 0 health remaining
// >> ActiveScene has changed to `MenuScene`
// Start Button: {50, 50}
// Button says "Play the Game!"
// >> ActiveScene has changed to `GameScene`
// Player: {0, 0}
// 2 health remaining
// Player: {0, 0}
// 1 health remaining
// Player: {0, 0}
// 0 health remaining
}