首页 > 解决方案 > Entity-Component-System 中的拆分组件需要过多的重构

问题描述

我有一个使用实体组件系统 (ECS) 的现有 C++ 游戏库。

我的图书馆的用户想创建一些组件,例如Cat:-

class Cat{ public:
    int hp;
    float flyPower;
};

他可以修改hp每一个cat,例如:-

for(SmartComponentPtr<Cat> cat : getAll<Cat>()){
    cat->hp-=5;  //#1
}

几天后,他想Cat分手HPFlyable-

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 中拆分组件时如何编辑框架/引擎以解决可维护性问题?

我从未发现任何不受此问题影响的方法,例如:-

赏金原因

Yuri 的回答是一个很酷的技术,但它仍然需要一些重构。

我当前糟糕的解决方案(pimpl)

如果我想创建Cat,我将创建 6 个组件:-

这是一个代码示例:-

class Hp_ : public BaseComponent{
    int hp=0;
};
class Hp_OO : public virtual BaseComponent{
    Hp_* hpPimpl;
    public: void damage(float dmg){ hpPimpl->hp-=dmg;}
};
class Flyable_  : public BaseComponent{ public:
    float flyPower;
};
class Flyable_OO: public virtual BaseComponent{
    Flyable_* flyPimpl;
    //other function
};
class Cat_: public virtual BaseComponent{};
class Cat_OO: public virtual Hp_OO , public virtual Flyable_OO{
   Cat_* catPimpl;
};

现在,调用是有效的:-

SmartComponentPtr<Cat_OO> catPtr;
catPtr->damage(5);   //: so convenient - no need to refactor 

实施:-

  1. 如果用户添加Cat_OO到实体,我的游戏引擎会自动将其父类添加到实体,例如Hp_Hp_OOFlyable_Flyable_OOCat_
  2. pimpl 的正确指针/句柄也必须分配。

  3. ^ 两个动作都可以使用回调。

缺点是:-

优点是:-

标签: c++c++14game-enginemaintainabilityentity-component-system

解决方案


会员指点救援:

#include <tuple>

template <typename... Components>
struct MultiComponentPtr {
    explicit MultiComponentPtr(Components*... components)
        : components_{components...}
    {}

    template <typename Component, typename Type>
    Type& operator->*(Type Component::* member_ptr) const {
        return std::get<Component*>(components_)->*member_ptr;
    }

private:
    std::tuple<Components*...> components_;
};

struct Cat {
    int hp;
    float flyPower;
};

struct HP {
    int hp;    
};

struct Flyable {
    float flyPower;    
};

int main() {
    {
        Cat cat;
        MultiComponentPtr<Cat> ptr(&cat);
        ptr->*&Cat::hp += 1;
        ptr->*&Cat::flyPower += 1;
    }

    {
        HP hp;
        Flyable flyable;
        MultiComponentPtr<HP, Flyable> ptr(&hp, &flyable);
        ptr->*&HP::hp += 1;
        ptr->*&Flyable::flyPower += 1;
    }
}

从技术上讲,您仍然需要重构,但是&Cat::hp&HP::hp等自动替换是微不足道的。


推荐阅读