我有一个使用实体组件系统 (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_OO
Flyable_
,Flyable_OO
Cat_
,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_OO
of eachCat_OO
也将在返回的列表中。 - 无需重构。