gdc17上面由暴雪的Tim Ford带来。
由于原文是gdcvault上面的付费内容,贴下腾讯的gad上的翻译:http://gad.qq.com/article/detail/7212152?bsh_bid=1732845294
本文可以说是对于entity/component系统(entity component system简称ECS了)有了一个更深的理解和探索,非常喜欢的一篇文章,而且和卡马克的一篇讲“functional programming”的非常好的呼应。
本文可以说是一个“学后感”,结合tim ford的讲解,加上自己的解读和实践中的看法,也有独特的价值吧。
==传统的Entity-Component System==
细节可以看wikipedia:https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system
98年thief这个游戏算是一个开始将Entity-component system发扬光大的游戏,也有近20年的历史了。
大家且不要以为这是一个游戏开发的标配,实际开发中还真遇到过就是不用这种结构的,依靠着继承以及写出超大规模类的方式硬写出来,额滴神,那代码叫一个多一个乱,那bug叫一个多,好不容易调好了,直接设为禁区(谁也不敢碰了),但是随着游戏新的需求还得改,被给与相关任务的程序员颇有发配边关的感觉。。。
传统的ECS,可以想象是一个车的结构,entity就是这个车,component就是车的组件,我们根据需要,按个雨刷的component,车就能清理挡风的雨水了,按个音响系统,就能听音乐了等等。
每个component有自己的数据和功能,然后这样组装的方式,构成一整个车。
实际使用中,结合architype和data driven,实用性非常的好:
==《OverWatch》的ECS==
这个是一个对overwatch的ecs的小结。
这里和传统的最大不同就是,传统的是component除了数据之外,自己有大量的函数,也可以修改自己,这些函数就是component的功能所在。
先贴一个overwatch的ecs的示意图:
(整体示意图)
(爬墙的system,对应到几个component)
(attach系统,操作这么几个component)
所以我们可以看到overwatch则把component的函数去掉了(注意这里不是外放到system中,不是一个概念),然后是通过system来完成各个功能。
tim ford对此的解释,是各个系统看component的时候,概念上更加清晰,他举得例子是一个树:
这个树不同人看是不同的东西,鸟看就是可以栖息的地方,主人看是景观,木工看是用来造家具的木材。
所以一个树如何定义,要看使用它的system(就是鸟,主人和木工),所以把函数拿出到system中来,而不是放到component中,就更符合由使用者来定义,而不是树自己定义自己的概念。
可以说是对于实际情况的一个更加透彻的认识。
这种认识带来更好的解耦和,进一步降低了系统的复杂度。
==个人的认识==
笔者看来,tim ford的解释是一个方面,笔者还想加一个:这个本质上是object-oriented programming到functional programming的转变。
细节可以看“重温“卡马克谈functional programming in c++”文章。
functional programming一个厉害的地方就是把state的变化从object oriented programming的封装倾向变成一个显示呈现的方式。
这更加容易让程序员拥有更好的对于系统state变化的控制力,进一步也控制了系统的复杂度。
tim ford也反复提到了复杂度这个问题,最好的情况就是system只读component的state,而不做修改。
但是实际情况是修改的情况必须要发生,这种时候会引入大量的复杂度,实现的程序员要做好控制。
==实际中的变通==
//singleton component
开始overwatch程序团队是按照比较理想的方式构建系统:system没有状态,component没有函数。
但是时间长了,system没有状态这个有点不切实际,比如玩家输入状态,放在system就太tmd方便了,于是就干了。
结果在开发走到死亡重放的时候就跪了,当有两套系统,各子系统有自己的状态的时候,就各种问题。
最后一个折衷是有了一个singleton component存放全局状态。
可以说是对于原有系统的扩充。
//util function
一些system之间共享的操作,就进一步提取成util function,多个系统之间使用。
不过不本来就应该这么做么。。。
//延迟执行
像放特效这种,tim ford表示这个很多时候要跨系统做判断等等,可以使用延迟执行的方法来降低复杂度。
就是各个system中放特效的操作都放到一起去执行,彼此之间还可以排序,做数量控制等等。
==和卡马克的functional programming in c++的呼应==
我们可以看到,卡马克在他的文章中也是强调复杂度的控制,状态改变的控制,以及实际情况中的折衷优化。
和tim ford整个说法非常好的呼应,读的过程中也一下子想起卡马克的文章了,所以在写这个之前,也写的重温篇。