我有一个使用实体组件系统 (ECS) 的现有工作 C++ 游戏库。
我的库的用户想要创建一些组件,例如Cat
:-
class Cat{ public:
int hp;
float flyPower;
};
他可以修改hp
每一个的cat
例如:-
for(SmartComponentPtr<Cat> cat : getAll<Cat>()){
cat->hp-=5; //#1
}
几天后,他想分手Cat
to HP
and 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 https://github.com/skypjack/entt
(开源 - 搜索vel.dx = 0.;
line)
-
https://medium.com/@savas/nomad-game-engine-part-2-ecs-9132829188e5 https://medium.com/@savas/nomad-game-engine-part-2-ecs-9132829188e5
(博客 - 搜索int currentHealth;
line)
-
https://www.randygaul.net/2013/05/20/component-based-engine-design/ https://www.randygaul.net/2013/05/20/component-based-engine-design/
(博客 - 搜索comp->DoStuff( dt );
line)
- (C#、Unity3D)http://www.sebaslab.com/learning-svelto-ecs-by-example-the-unity-survival-example/ 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 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
, and Cat_
.
还必须分配正确的 pimpl 指针/句柄。
^ 两个操作都可以使用回调。
缺点是 :-
- 需要创建很多组件。 (浪费内存)
- 如果有一个共同的基类,例如
BaseComponent
,我需要虚拟继承。 (浪费大量内存)
优点 are :-
- 如果用户查询
getAll<Hp_OO>()
, Hp_OO
每一个的Cat_OO
也将在返回的列表中。
- 无需重构。