我有一个使用实体组件系统 (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 中拆分组件时如何编辑框架/引擎以解决可维护性问题?
我从未发现任何不受此问题影响的方法,例如:-
- https://github.com/skypjack/entt
(开源-搜索vel.dx = 0.;线路) - https://medium.com/@savas/nomad-game-engine-part-2-ecs-9132829188e5
(博客-搜索int currentHealth;行) - https://www.randygaul.net/2013/05/20/component-based-engine-design/
(博客-搜索comp->DoStuff( dt );行) - (C#,Unity3D)http://www.sebaslab.com/learning-svelto-ecs-by-example-the-unity-survival-example/
(由https://codereview.stackexchange.com/questions/引用的博客48536/an-ecs-model-for-game-development ;
搜索playerGunComponent.timer += _time.deltaTime;)
赏金原因
Yuri 的回答是一个很酷的技术,但它仍然需要一些重构。
我当前糟糕的解决方案(pimpl)
如果我想创建Cat,我将创建 6 个组件:-
Hp_,Hp_OOFlyable_,Flyable_OOCat_,Cat_OO
这是一个代码示例:-
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
实施:-
- 如果用户添加
Cat_OO到实体,我的游戏引擎会自动将其父类添加到实体,例如Hp_、Hp_OO、Flyable_、Flyable_OO和Cat_。 pimpl 的正确指针/句柄也必须分配。
^ 两个动作都可以使用回调。
缺点是:-
- 需要创建很多组件。(浪费记忆)
- 如果有一个通用的基类,例如
BaseComponent,我需要虚拟继承。(浪费大量内存)
优点是:-
- 如果用户查询
getAll<Hp_OO>(),Hp_OOof eachCat_OO也将在返回的列表中。 - 无需重构。