百科.dev
全部条目AI 编程趋势榜开源项目技术资讯提交条目
登录
< 返回工具列表
E

EntityPlus

> 编程语言
开源

一个 C++14 实体组件系统

190 stars0 点赞0 次浏览
访问官网GitHub

工具介绍

一个 C++14 实体组件系统

NOTICE:

This project is currently in the progress of being rewritten for C++17. Check out this issue if you have any suggestions/know a good way to transition!

EntityPlus

EntityPlus is an Entity Component System library written in C++14, offering fast compilation and runtime speeds. The library is header only, saving you the trouble of fidgeting with build systems and there are no external dependencies.

The ECS framework is an attempt to decouple data from mechanics. In doing so, it lets you create objects out of building blocks that mesh together to create a whole. It models a has-a relationship, letting you expand without worrying about dependency trees and inheritance. The three main aspects of an ECS framework are of course Entities, Components, and Systems.

Requirements

EntityPlus requires C++14 conformance, and was mainly developed on MSVC. It has been tested to work on

  • MSVC 2015 update 3
  • Clang 3.5.0
  • GCC 5.3.0

Building

Since EntityPlus is header only, there is no need to build the library to use it. Just #include ! However, there are tests and examples you can build with cmake.

To build the tests, you need to specify the catch directory. An in tree build could look like this:

cmake -D Catch_dir=dir/to/catch ..
make test

Components

Components contain information. This can be anything, such as health, a piece of armor, or a status effect. An example component could be the identity of a person, which could be modeled like this:

cpp
struct identity {
    std::string name_;
    int age_;
    identity(std::string name, int age) : name_(name), age_(age) {}
};

Components don't have to be aggregate types, they can be as complicated as they need to be. For example, if we wanted a health component that would only let you heal to a maximum health, we could do it like this:

cpp
class health {
    int health_, maxHealth_;
public:
    health(int health, int maxHealth) :
        health_(health), maxHealth_(maxHealth){}
    
    int addHealth(int health) {
        return health_ = std::max(health+health_, maxHealth);
    }
}

As you may have noticed, these are just free classes. Usually, to use them with other ECSs you'd have to make them inherit from some common base, probably along with CRTP. However, EntityPlus takes advantage of the type system to eliminate these needs. To use these classes we have to simply specify them later.

Components must have a constructor, so aggregates are not allowed. This restriction is the same as all emplace() methods in the standard library. There are no other requirements.

Entities

Entities model something. You can think of them as containers for components. If you want to model a player character, you might want a name, a measurement of their health, and an inventory. To use an entity, we must first create it. However, you can't just create a standalone entity, it needs context on where it exists. We use an entity_manager to manage all our entitys for us.

cpp
using CompList = component_list;
using TagList = tag_list;
entity_manager entityManager;
using entity_t = typename entity_manager::entity_t;

Don't be scared by the syntax. Since we don't rely on inheritance or CRTP, we must give the entity_manager the list of components we will use with it, as well as a list of tags. To create a list of components, we simply use a component_list. component_lists and tag_lists have to be unique, and the component_list and tag_list can't have overlapping types. If you mess up, you'll be told via compiler error.

cpp
error C2338: component_list must be unique

Not so bad, right? EntityPlus is designed with the end user in mind, attempting to achieve as little template error bloat as possible. Almost all template errors will be reported in a simple and concise manner, with minimal errors as the end goal. With C++17 most code will switch over to using constexpr if for errors, which will reduce the error call stack even further.

Now that we have a manager, we can create an actual entity.

cpp
entity_t entity = entityManager.create_entity();

You probably want to add those components to the entity.

cpp
auto retId = entity.add_component("John", 25);
retId.first.name_ = "Smith";
entity.add_component(health{100, 100});

If we supply a component that wasn't part of the original component list, we will be told this at compile time. In fact, any sort of type mismatch will be presented as a user friendly error when you compile. Adding a component is quite similar to using map::emplace(), because the function forwards its args to the constructor and has a similar return semantic. A pair is returned, indicating error or success and the component. The function can fail if a component of that type already exists, in which case the returned component& is a reference to the already existing component. Otherwise, the function succeeded and the new component is returned.

Sometimes you know all the tags and components you want from the get go. You can create an entity with all these parts just as easily:

cpp
entity_t ent = entityManager.create_entity(A{3}, health{100, 200});

The arguments are fully formed components you wish to add to the entity and the template arguments are the tags the entity should have once it's created.

What happens if we create a copy of an entity? Well, since entities are just handles, this copy doesn't represent a new entity but instead refers to the same underlying data that you copied.

cpp
auto entityCopy = entity;
assert(entityCopy.get_component() == entity.get_component();

What happens if we modify one copy of the entity? Well, the modified entity is the freshest, and so it is fine, but the old entity is stale. Using a stale entity will give you an error at best, but it can go unnoticed under certain circumstances (if using a release build). You can query the state of an entity with get_status(). The 4 statuses are OK, stale, deleted, and uninitialized. To make sure you have the newest version of an entity, you can use sync(), which will update your entity to the latest version. If the entity has been deleted, sync() will return false.

Systems

The last thing we want to do is manipulate our entities. Unlike some ECS frameworks, EntityPlus doesn't have a system manager or similar device. You can work with the entities in one of two ways. The first is querying for a list of them by type

cpp
auto ents = entityManager.get_entities();
for (const auto &ent : ents) {
    std::cout ()` or `for_each(...)`. In addition, `for_each` has an optional control parameter, which you can modify to break out of the for loop early.

```c++
entity_t secretAgent;
entityManager.for_each([&](auto ent, auto &id, control_block_t &control) {
	if (id.name_ == "Secret Agent") {
		secretAgent = ent;
		control.breakout = true;
	}
}

That's about it! You can obviously wrap these methods in your own system classes, but having specific support for systems felt artificial and didn't add any impactful or useful changes to the flow of usage.

Tags

Tags are like components that have no data. They are simply a typename (and don't even have to be complete types) that is attached to an entity. An example could be a player tag for the entity that is controlled by a player. Tags can be used in any way a component is, but since there is no value associated with it except if it exists or not, it can only be toggled.

cpp
ent.set_tag(true);
assert(ent.get_tag() == true);

Events

Events are orthogonal to ECS, but when used in conjunction they create better decoupled code. Because of this, events are fully integrated into the entity manager. The first two template arguments of the event_manager must be the same component_list and tag_list as the ones used for the entity_manager. Additional events can be used by supplying their type after the components/tags.

cpp
struct MyCustomEvent {
	std::string msg;
};

event_manager eventManager;
subscriber_handle handle;
handle = eventManager.subscribe([](const auto &ev) {
    std::cout >([](const auto &event) {
	event.ent.get_component() == event.component;
}

Here is a full list of predefined events.

entity_created
entity_destroyed
component_added
component_removed
tag_added
tag_remved

…

c++
entity_grouping groupAB = entityManager.create_grouping();

for_each(...);

// later
groupAB.destroy()

…


Entity Count | Iterations | Probability | EntityPlus | EntityX
----------------------------------------------------------------
    1 000    | 1 000 000  |    1 in 3   |   1706 ms  |  20959 ms
   10 000    | 1 000 000  |    1 in 3   |  16541 ms  | 208156 ms
   30 000    |   100 000  |    1 in 3   |   5308 ms  |  63012 ms
  100 000    |   100 000  |    1 in 5   |  14780 ms  | 133365 ms
   10 000    | 1 000 000  |  1 in 1000  |    396 ms  |  16883 ms
  100 000    | 1 000 000  |  1 in 1000  |   4610 ms  | 170271 ms
   

Big O Analysis

cpp
n = amount of entities

Entity:
has_(component/tag) = O(1)
(add/remove)_component = O(n)
set_tag = O(n)
get_component = O(log n)
get_status = O(log n)
sync = O(log n)
destroy = O(n)

Entity Manager:
create_entity = O(n)
get_entities = O(n)
for_each = O(n)
create_grouping = O(n)

Reference

Entity

cpp
entity_status get_status() const 

Returns: Status of entity, one of OK, UNINITIALIZED, DELETED, or STALE.

cpp
template 
bool has_component() const 

Returns: bool indicating whether the entity has the Component.

Prerequisites: entity is OK.

cpp
template 
std::pair add_component(Args&&... args)

template 
std::pair&, bool> add_component(Component&& comp)

Returns: bool indicating if the Component was added. If it was, a reference to the new Component. Otherwise, the old Component. Does not overwrite old Component.

Prerequisites: entity is OK.

Throws: bad_entity if the entity is not OK.

Can invalidate references to all components of type Component, as well as a for_each involving Component.

Can turn entity copies STALE.

cpp
template 
bool remove_component()

Returns: bool indicating if the Component was removed.

Prerequisites: entity is OK.

Can invalidate references to all components of type Component, as well as a for_each involving Component.

Can turn entity copies STALE.

cpp
template 
(const) Component& get_component() (const) 

Returns: The Component requested.

Prerequisites: entity is OK.

Throws: bad_entity if the entity is not OK. invalid_component if the entity does not own a Component.

cpp
template 
bool has_tag() const 

Returns: bool indicating if the entity has Tag.

Prerequisites: entity is OK.

cpp
template 
bool set_tag(bool set) 

Returns: bool indicating if the entity had Tag before the call. The old value of has_tag().

Prerequisites: entity is OK.

Throws: bad_entity if the entity is not OK.

Can invalidate a for_each involving Tag, only if set != set_tag(set).

Can turn entity copies STALE.

cpp
bool sync()

Returns: true if the entity is still alive, false otherwise.

Prerequisites: entity is not UNINITIALIZED.

cpp
void destroy()

Prerequisites: entity is OK.

Throws: bad_entity if the entity is not OK.

Can invalidate a for_each.

Turns entity copies 'DELETED'.

entity vs entity_t

entity is the template class while entity_t is the template class with the same template arguments as the entity_manager. That is, entity_t = entity.

Entity Manager

cpp
template 
entity_t create_entity(Components&&... comps)

Returns: entity_t that was created with the given Tags and Components.

Can invalidate references to all components of types Components, as well as a `for

Issues· 0 开放

查看全部 Issues在 GitHub 打开

暂无开放 Issues,或尚未同步最近议题。

> 标签

C++entityentity-componententity-component-systemgamedev

暂无评论,来聊聊你的看法吧

> 工具信息

发布日期2026年8月1日
最后更新2026年9月18日
分类编程语言
定价开源

> 相关工具

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言