如果你看不明白原帖中的ECS(似乎很多人有这个反应),我觉得其实是很正常的,尤其是从开始写代码到今天只接触过OOP的人,而OW本身是一个很宏大的项目,你没法很好的去理解这个例子,假如你真的有兴趣了解ECS,那么我给你们举个很简单的例子,以及说一下ECS的基本Motif(调性):
首先是规则区别,我还是要重复一个Key(我记得似乎在别的帖子说过):
1,OOP的核心思想是:我是什么——我是一个角色对象,我是一个子弹对象。
2,ECS的核心思想是:我有什么——我有render,我有move,我有motion。
就拿子弹和角色来说,如果我们是一个很简单的射击游戏,或者是Top-Down-Shooter的,或者更简单直白的,FC上的坦克相信都知道吧?
如果用OOP的方式做,它里面有角色(坦克)对象和子弹对象,伪代码
class tank {
//这里仅说一些有代表性的,就不细节设计了
public tank()
public Vector2 move(direction) //有一个move方法来移动坦克,在这个方法里面,我们根据面向、速度等因素来让坦克的坐标发生位移,同时我们让坦克的动作发生了变化。注意:我们把坦克的渲染其实也放在了这里,所谓的渲染包括了移动时候播放的动画等。
public bullet fire() //我们通过这个来让坦克开火,产生子弹
public void kill() //我们通过这个来让坦克在HP小于等于0的时候死亡。
}
class bullet {
public bullet(){}
public Vector2 move(direction) //子弹也会根据方向移动,这毫无疑问。注意,我们把子弹的渲染也放在了这里,所谓渲染包括了子弹移动的时候贴出他的位置。
public void hit() //子弹命中了什么东西之后要调用这个,用来消除子弹。
}
我相信不用写game,大多人也知道这个game要怎么写,毕竟这么简单的游戏。
那么我们来看看ECS是怎么做这个的?伪代码
moveComponent{
enum direction //移动方向,不做进一步解释了
float moveSpeed //移动速度,我想你也明白
}
positionComponent{
float x; //位于地图的坐标,不解释了
float y;
}
function moveSystem(entities){ //这个system只关心带有positionComponent和moveComponent的entity,所以entities里面的所有entity都必然有个moveComponent和positionComponent
for (entity in entities){
这里就不详细写了,执行的事情就是根据moveComponent中的direction和moveSpeed,为positionComponent中的x,y重新赋值。
}
}
renderComponent{
string resource; //贴图的信息,可以是资源名,等等,老做游戏了你应该理解这个
}
function renderSystem(entities){
//这个系统的工作,就是根据自己关心的带有positionComponent和renderComponent的对象,来进行渲染工作。
}
collisionComponent{
rect body; //FC的坦克大战中,所有的子弹和坦克,其实都是矩形的。
int side; //可以理解为阵营,同一阵营的互相忽略碰撞,当然写在这里面并不是最好的方案,具体要看需求,目前的需求下这还算凑合。
hitEntities: array<entity>; //用于记录碰撞到的entity
}
tankComponent{
//证明这是一个tank,并且记录坦克需要的数据
int hp
}
bulletComponent{
//证明这是一个子弹,并记录子弹需要的信息
int power
bool hitSomeThing
}
function collisionSystem(entities){
//这个system关心的是collisionComponent和positionComponent,他的工作就是根据position和collisionComponent.rect来判断2个entity是否碰撞,如果碰撞,则将彼此加入到对方的collisionComponent.hitEntities数组中去。
}
function damageSystem(entities){
//这是一个很有意思的system,他只关心带有collisionComponent,然后会根据collisionComponent.hitEntities中的entity提供的信息配合被捕捉的entity的其他component(比如tankComponent和bulletComponent)进行特殊处理。
//这个system的工作之一:如果这个entity并没有tankComponent,同时还没有bulletComponent,for循环执行continue,检查下一个entity。
//如果这个entity拥有tankComponent,证明这是坦克,则遍历collisionComponent.hitEntities,根据其中带有bulletComponent的entity进行处理,你可以同时销毁他们全部,并且都对这个坦克进行伤害,也可以根据被认为是子弹(带有bulletComponent的entity)的个数特殊处理,比如被3个子弹命中则hp提高10000等,根据设计需求来实现。
//值得注意的是:即使entity.tankComponent.hp<=0,也不由我来进行下一步操作,我就是一个damageSystem,我负责的就是damage(造成伤害),造成伤害的后果,管我鸟事儿。
}
function tankDestroySystem(entities){
//我只关心带有tankComponent的entity,我的任务是如果entity.tankComponent.hp<=0,则将他们杀死。
}
function bulletDestroySystem(entities){
//和tankDestroySystem差不多,但我只负责对bulletComponent.hitSomeThing==true的对象进行dispose()
}
这就是一个典型的ECS的游戏写法,他没有坦克对象,子弹对象,但是他有坦克和子弹
tankEntity = entities.new()
.add(new positionComponent(x,y))
.add(new renderComponent(xxxx))
.add(new collisionComponent(rect(x,x,x,x), y)
.add(new tankComponent(x))
bulletEntity = entities.new()
.add(new positionComponent(x,y))
.add(new renderComponent(xxxx))
.add(new collisionComponent(rect(x,x,x,x), y)
.add(new bulletComponent(x))
你会发现,子弹和坦克唯一的区别就是一个是子弹,一个是坦克。你可能要问,为什么没有moveComponent?因为moveComponent只有在移动的时候才需要添加上去,当移动完毕,就可以把它移除掉,这对于效率来说,还算是一种优化。
到这里,我们是不是可以思考另外一个问题——在一个3岁小孩子眼里,画出来的羊和活着的羊有什么区别?区别就是:活着的羊会动,他有motionComponent和moveComponent。当然话题收回来,现在,我们发现游戏中还没有设计地形,对,没有地形玩什么?
OOP方式:
class ObstacleGrid{
//tilebased游戏,地图格当然不能没有,当然这种游戏需要的只是会阻挡的地形格子信息而已。
string resource; //贴图资源
bool breakable; //能否被子弹击穿
Vector2 position; //位置
.....
public void break() //被子弹销毁了
}
看起来加一个对象不复杂,但是当地图上有了这些obstacleGrids之后,你要在game中coding什么呢?很多,比如坦克移动的时候,你不能穿越他,再比如子弹命中他的时候……等等等等。
ECS方式,我们要做的事情很清晰:
wallComponent{
//证明我是阻挡地形,别在意名字的细节……
bool breakable;
}
function obstacleSystem(entities){
//这个system关心的,只有带有wallComponent和collisionComponent的entity,根据collisionComponent.hitEntities进行对应的处理,相信你动动脑子能想明白。
//这里有一个独特与oop的地方,如果我(墙壁entity)阻挡了坦克或者子弹,我要做的是对对方entity.remove(moveComponent),导致对方不能发生移动。
}
阻挡地形:
obstacleGrid = entities.new() .add(new positionComponent(x,y)) .add(new renderComponent(xxxx)) .add(new collisionComponent(rect(x,x,x,x), y) .add(new wallComponent(x))
看到这里,发现为啥他和子弹和坦克如此接近?是不是能突然冒出一个有意思的想法?如果策划说了,我的阻挡地形也会移动!是不是OOP很懵逼?但ECS就很好处理了?
以上是一个简单的思路上的例子,区别了ECS和OOP,ECS中你说一点耦合不存在可能吗?是可能的,system之间是否必须耦合(其实必须优先执行aSystem再执行bSystem就可以认为是耦合)取决于你的programming和对业务的理解能力。当然,我想说的是,请看