好问题,不过有点庞大且复杂,我尝试抛砖引玉回答一下。

以目前市面上非常成熟且成功的商业引擎——虚幻引擎举例,他的很多设计无疑是非常优秀的。我接触虚幻有三年多的时间,这里从自己的角度,来谈一谈其中的一些设计思路。

一、组件式设计

怎么理解呢?就是把某一种特定的功能封装到一个叫做“组件Component“的对象里面,通过添加与卸载组件达到灵活的复用与组合效果。包括unity在内,里面的基础对象和很多功能都是分开的。一个普通的对象可以什么功能都没有,放到场景里面既不会移动也不会显示,需要给他添加特定的组件才行,包括移动能力,显示模型能力,碰撞能力等。那举一个Unreal中的例子,比如说我们有一个角色character,也就是我们经常在网上见到的下面这个家伙。

他身上有几个默认的组件,包括一个胶囊体碰撞组件,一个模型显示的组件,一个摄像机组件以及一个移动组件。正因为有了这些组件,你的角色才可以自由的在场景内移动,与地面有碰撞交互甚至是执行网络同步。如果没有他们的话,你连看到他在哪都做不到。

从代码的角度来看,就是大概下面这个样子,一个Character下面有几个成员变量,

当然,最近ECS比较火,Unity已经提供相关的模块,Unreal也在尝试,但是好像还没有加入到引擎里面。

二、模块式设计

对于虚幻这样复杂的项目,管理与维护起来是相当困难的,为了尽可能降低各个模块之间的耦合,肯定是要将各个模块尽可能的拆分成独立的模块,不会相互影响。看一下虚幻引擎的目录结构以及其dll的文件夹,

基本上都是最小粒度的将一个模块封装成一个dll,每个dll的生成以及与其他dll的相互引用等内容要在模块.build.cs文件里配置。

三、插件设计

一个引擎想要成为商业引擎,只靠官方自己造轮子肯定是不行的。所以,一定要提供给大家足够灵活的方式去添加或者发布自己的轮子,简单来说就是插件功能。其实这一条与上面第二条是紧密结合的,我们如果一开始就能做到将各个模块都拆分成不同的库,那么插件也可以用类似的方式形成一个独特的模块,除了路径以及一些引用内容不同外,他基本上与其他模块式差不多的。

另外,第三方库肯定也是要支持的,不过这个可以由开发团队自己去搞。

四、抽象分层设计

这个比较宽泛,是我自己起的名。大概意思就是说,如果一个功能很复杂,就要想办法给其抽象一个出概念。接触虚幻引擎的朋友可能知道,虚幻引擎的代码之所以复杂,就是因为做了很多抽象和封装,你即使有过其他引擎的基础,看虚幻代码还是有很多新的概念要去理解。当然,我个人觉得如果不做抽象和封装,这个引擎就你就更别想看懂了。

这里拿UE4内置的网络模块做描述,正常我们写一个普通的网络通信逻辑一般也就是Client、Servedr、Socket、TCP/UDP这些概念,但是在虚幻引擎里面,为了考虑哪些包要可靠传输、哪些不要、粒度是如何的、同步的基本单位是什么等等就会引出来一系列复杂的概念,比如bunch、Channel、Connection等等,其实这些都是其多年的引擎设计经验不断完善的结果。我猜想一开始肯定也都是非常简单,随着不断的完善与添加新功能逐步做了多层的封装与设计。至于在设计时要封装到什么程度,取决于当前模块的复杂程度与目前的规划。如果对虚幻的网络模块有兴趣,可以参考我的文章《Exploring in UE4》网络同步原理深入,这里我不展开讲解。

五、更新逻辑

我们知道,其实游戏就是不断Tick的一个死循环,那么在一帧的时间里,哪些东西先更新,哪些东西后更新就是一个不小的学问。比如,网络收包在角色Tick之前还是之后?摄像机的位置在什么时机去更新?

虚幻里面,逻辑大概是这样。 网络收包—》物理执行前相关ACtor执行Tick—》物理执行中的相关ACtor执行Tick—》物理执行后的相关ACtor执行Tick—》摄像机更新—》网络发包—》渲染

六、引擎功能设计的取舍

这里是指我们对引擎的设计有哪些预期,比如如何处理内存泄漏问题、是否要有垃圾回收(C#就省心很多),是否要有反射与类型系统(我认为对于引擎来说必须要有),是否要内置序列化功能等。这其实都是些很朴素的问题,你有没有足够的时间去做?这个东西是不是可有可无?你是否可以用别人的轮子?(这些问题大钊的专栏InsideUE4里面有很多思考,建议大家看看)

为什么当前很多公司都放弃做游戏引擎?就是因为市面上的东西已经很优秀且丰富了,你再造一个轮子既费时间又并不见得有意义甚至可能还没有人家的好。



游戏引擎是一个相当复杂的软件系统,不仅仅限制于渲染、物理、AI、网络等模块的实现,如何组织他们以及如何提供给玩家一个舒适的编辑器环境都是非常重要的内容。