Properly link flecs library
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"id": "factory",
|
||||
"type": "application",
|
||||
"value": {
|
||||
"use": [
|
||||
"flecs"
|
||||
],
|
||||
"language": "c++"
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "inventory_system",
|
||||
"type": "application",
|
||||
"value": {
|
||||
"use": [
|
||||
"flecs"
|
||||
],
|
||||
"public": false,
|
||||
"language": "c++"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "scene_management",
|
||||
"type": "application",
|
||||
"value": {
|
||||
"use": [
|
||||
"flecs"
|
||||
],
|
||||
"public": false,
|
||||
"language": "c++"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user