我一直在研究游戏引擎设计(特别关注 2d 游戏引擎,但也适用于 3d 游戏),并且对如何进行的一些信息感兴趣。我听说现在许多引擎正在转向基于组件的设计,而不是传统的深层对象层次结构。
您是否知道有关此类设计通常如何实施的信息的任何良好链接?我已经看到了你的层次结构的进化,但我真的找不到更多的详细信息(他们中的大多数似乎只是说“使用组件而不是层次结构”,但我发现改变我的想法需要一些努力在两个模型之间)。
任何关于这方面的好的链接或信息都将不胜感激,甚至是书籍,尽管这里的链接和详细答案将是首选。
architecture game-engine entity-system entity-component-system
我正在研究一个面向数据的实体组件系统,其中组件类型和系统签名在编译时是已知的.
一个实体是一个组件的集合体.可以在运行时从实体添加/删除组件.
甲组件是一个小的逻辑少类.
一个签名是组件类型的编译时间列表.如果实体包含签名所需的所有组件类型,则称该实体与签名匹配.
一个简短的代码示例将向您展示用户语法的外观以及预期用途:
// User-defined component types.
struct Comp0 : ecs::Component { /*...*/ };
struct Comp1 : ecs::Component { /*...*/ };
struct Comp2 : ecs::Component { /*...*/ };
struct Comp3 : ecs::Component { /*...*/ };
// User-defined system signatures.
using Sig0 = ecs::Requires<Comp0>;
using Sig1 = ecs::Requires<Comp1, Comp3>;
using Sig2 = ecs::Requires<Comp1, Comp2, Comp3>;
// Store all components in a compile-time type list.
using MyComps = ecs::ComponentList
<
    Comp0, Comp1, …我正在设置一个非常小的Entity-Component-System示例,并且组件池存在一些问题.
目前我的实体只是ID(GUID),这很好.
每个系统都必须实现该ISystem接口
internal interface ISystem
{
    void Run();
}
并存储在KeyedByTypeCollection.此集合确保每个系统都是唯一的.
每个组件都必须实现该IComponent接口.
internal interface IComponent
{
}
通过这样做,我可以将所有不同的组件类型存储到其匹配的组件池中.每个游泳池都是Dictionary<Guid, IComponent>.键表示实体的ID,值是该实体的组件.每个池都存储在一个池中KeyedByTypeCollection,以确保组件池是唯一的.
目前我的EntityManager类包含核心逻辑.我不知道它是否需要静态但目前我的系统需要从中获取一些信息.
处理组件池的重要核心方法是:
internal static class EntityManager
{
    public static List<Guid> activeEntities = new List<Guid>();
    private static KeyedByTypeCollection<Dictionary<Guid, IComponent>> componentPools = new KeyedByTypeCollection<Dictionary<Guid, IComponent>>();
    public static KeyedByTypeCollection<ISystem> systems = new KeyedByTypeCollection<ISystem>(); // Hold unique Systems
    public static Guid CreateEntity() // Add a new GUID and return it
    {
        Guid entityId …使用Entity-Component-System模式我想要将一些系统与事件连接起来.所以有些系统不应该在循环中运行,它们应该只是按需运行.
鉴于Health系统的示例,死亡系统应仅在组件低于1健康时运行.
我想过有两种类型的系统.第一种类型是周期性系统.这将运行每帧一次中,例如一个渲染或运动系统.另一种类型是基于事件的系统.作为之间的连接之前提到的健康和死亡.
首先,我创建了两种系统类型使用的基本接口.
internal interface ISystem
{
    List<Guid> EntityCache { get; } // Only relevant entities get stored in there
    ComponentRequirements ComponentRequirements { get; } // the required components for this system
    void InitComponentRequirements();
    void InitComponentPools(EntityManager entityManager);
    void UpdateCacheEntities(); // update all entities from the cache
    void UpdateCacheEntity(Guid cacheEntityId); // update a single entity from the cache
}
我进一步创建了接口
internal interface IReactiveSystem : ISystem
{
// event …我有一个使用实体组件系统(ECS)的现有工作C ++游戏库。
我的图书馆的用户想创建一些组件,例如Cat:-
class Cat{ public:
    int hp;
    float flyPower;
};
他可以通过以下方式修改hp每个内容cat:
for(SmartComponentPtr<Cat> cat : getAll<Cat>()){
    cat->hp-=5;  //#1
}
几天后,他想拆分Cat为HP和Flyable:-
class HP{ public:
    int hp;
};
class Flyable{ public:
    float flyPower;
};
因此,每次cat访问hp都会编译错误(例如,#1在上面的代码中)。
为了解决这个问题,用户可以将他的代码重构为:
for(MyTuple<HP,Flyable> catTuple : getAllTuple<HP,Flyable>()){
    SmartComponentPtr<HP> hpPtr=catTuple ; //<-- some magic casting
    hpPtr->hp-=5; 
}
它可以工作,但是需要在用户代码中进行大量重构(调用的各个位置cat->hp)。
在ECS中拆分组件时如何编辑框架/引擎以解决可维护性问题?
我从未发现过任何不会受到此问题困扰的方法,例如:-
vel.dx = 0.;行)int currentHealth; …c++ maintainability game-engine c++14 entity-component-system
由于 Unity ECS,我最近阅读了很多有关 ECS 的文章。
ECS架构有很多明显的优势:
ECS 是面向数据的:数据倾向于线性存储,这是系统访问它的最佳方式。在体面的 ECS 实现中,数据是按顺序存储和处理的,对于任何给定的系统处理它的组件集几乎没有中断。
ECS 非常分割:它自然地将数据与行为分开,强制执行“组合而非继承”(google it)等。
ECS 对并行处理和多线程非常友好:由于事物的结构方式,许多实体和组件可以避免冲突并与其他系统并行处理。
然而,ECS 的缺点(与 OOP 或实体组件 [没有系统] 相比,直到最近在包括 Unity 在内的游戏引擎中很常见)很少被谈论,如果有的话。它们存在吗?如果他们这样做了,他们是什么?
architecture oop unity-game-engine data-oriented-design entity-component-system
我想知道如何在C++中实现最快版本的实体组件系统(从现在开始的ECS).
首先,关于术语:
我列出了我们在下面提出的所有设计.
场景包含所有无序的实体.
随着系统更新,每个系统都必须遍历所有实体并检查每个实体是否包含所有必需的组件,然后对这些实体执行更新.
显然,当拥有大量系统和/或许多实体时,这种方式并不太高效.
每个Component包含一个位掩码形式的类型标识符(例如1u << 5/ binary [0...]100000).然后,每个实体可以组成所有Component的类型标识符(假设所有typeID在Entity中都是唯一的),所以它看起来像
1u << 5 | 1u << 3 | 1u << 1
binary [0...]101010
场景包含某种类型的地图,系统可以轻松查找实体:
MovementSystem::update() {
    for (auto& kv : parent_scene_) { // kv is pair<TypeID_t, vector<Entity *>>
        if (kv.first & (POSITION | VELOCITY))
            update_entities(kv.second); // update the whole set of fitting entities
    } …我有一个OOP实体组件系统,目前的工作方式如下:
// In the component system
struct Component { virtual void update() = 0; }
struct Entity
{
    bool alive{true};
    vector<unique_ptr<Component>> components;
    void update() { for(const auto& c : components) c->update(); }
}
// In the user application
struct MyComp : Component
{
    void update() override { ... }
}
要创建新的实体和组件,我使用C++的常规new和delete:
// In the component system
struct Manager
{
    vector<unique_ptr<Entity>> entities;
    Entity& createEntity() 
    { 
        auto result(new Entity);
        entities.emplace_back(result);
        return *result;
    }
    template<typename TComp, typename... TArgs>
        TComp& …这是一个类比:我有一个由细胞组成的有机体,这些细胞可以进一步由混合的附件组成。
我目前拥有的是子/父级之间的一种事件链,用于处理附加和分离组件(这可能会影响链上的任何内容),根本不涉及 ecs,它们是实体中的函数。
现在我已经使用了事件组件(用于对象上的鼠标事件)。如果我希望系统是纯粹的,我会在附加组件等时创建一个附加组件吗?即使如此,我如何才能让所有必要的接收者到达使用该组件的系统?以这种方式而不是一系列函数来处理它是否值得?有没有更好的办法?
我已经写了一个ECS,但我对更新阶段有一些疑问。(在系统中)\n我读过很多文章,但没有找到此类问题的参考。
\n\n为了从 ECS 中获益(例如缓存友好),需要满足以下要求:
\n\n因此,每个系统中应用的逻辑都很好,并且在没有 \xe2\x80\x9cuser 代码\xe2\x80\x9d 时一切正常。\n但是,当我们处理用户代码时(例如用户可以将 C++ 代码附加到一个对象(如 Unity、Unreal)),问题就来了:
\n\n将逻辑添加到组件中会破坏纯 ECS 的优势(以缓存友好的方式对所有相同的组件执行相同的操作),因此恕我直言,这不是一个解决方案。
\n\n有人有解决办法吗?我想知道您在 ECS 实施中如何处理此类问题。
\n\n谢谢!
\nc++ architecture game-development game-engine entity-component-system
c++ ×4
game-engine ×4
architecture ×3
c# ×2
c++11 ×2
c++14 ×2
memory ×1
oop ×1
optimization ×1
performance ×1
theory ×1