02 - 如何构建游戏世界
本文主要介绍了:
- 如何让游戏世界活起来
- 如何管理游戏对象
- 其他需要处理的复杂情况
Game Objects(GO)
以一个战争游戏为例,来说说一个游戏世界是由哪些部分构成的。
动态物(Dynamic Game Objects)
动态物,就是游戏中会“动”的物体。它会自主动,例如敌方单位(载具,敌人等);也会根据玩家的行为动,例如玩家可操纵的设施(火炮等)
静态物(Static Game Objects)
游戏中不会动/不可交互的物体,是构成游戏世界的重要元素。
环境(Environments)
- 地形系统,无限绵延的大地,是静态物和动态物的托盘。
- 天空,包裹整个游戏世界,有天气系统,日夜更替(Time Of Day)等。
- 植被等其他构成环境的元素。
其他物体
- 检测区域(Trigger Area),例如可以检测玩家是否在这篇区域,是的话会触发某些事件。
- 空气墙(Air Wall),例如在某些跑酷游戏中,会有看不见的空气墙阻挡玩家偷鸡。
- 以及其他物体。。。。。。
这些物体可以被统一抽象为 GO (Game Objects),如何构建好一个游戏世界,其实就是解决“如何管理好GO的问题”。
描述GO
如何描述好GO是很关键的问题。
早期OO风格
以无人机为例,我们描述一个GO通常会得到两类东西:
- 属性(Property):无人机的形状、位置、血量等
- 行为(Behavior):AI行为(巡逻等),行走方式等
熟悉面向对象编程(OOP)的肯定知道,现在可以用 继承 的方式去创建基于无人机基类的各种派生类,例如武装无人机(多了弹药和攻击行为):
但当GO变得很多时,继承关系就比较难区分了,并且容易出现菱形继承等问题。
现代组件化风格(Component)
可以将GO的行为拆分成一个个组件,例如上图中的挖掘机,换掉前面的工具就能改变它的行为;武器的自定义配件也是如此。
对于前面提到的无人机,我们可以利用组件化的思想,将它的行为拆分为若干组件:
- Transform:无人机的位移行为,表示它在空间各个位置的占用关系。
- Model:无人机的外形。
- Motor:无人机的各种运动属性,如惯性、爬升速度等。
- AI:无人机的AI行为。
- Animation:无人机的各种动画(攻击等)。
- Physics
- ……
利用GameObjectBase
和ComponentBase
类派生出来的无人机基类如下:
这样,相较于麻烦的继承,我们直接给无人机加上战斗模块并修改一下AI模块,便能很容易得到武装无人机。
现代商业引擎也是用组件化来进行设计的:
让GO动起来
现在已经有了各种各样的GO,如何让它们动起来?
早期OO风格
在早期游戏引擎中,人们通常遍历每个GO,执行tick()
函数让他们动起来。这样虽然符合人们的直觉,但不符合“机器”的直觉,带来的弊端就是性能方面可能会出问题。
现代组件化风格
在现代游戏引擎中,不对每个对象进行tick()
,而是对每个系统进行tick()
。这样做效率很高,类比一个一个做汉堡和流水线汉堡;并且组件的数据很集中,容易批处理。
如果一个tick()
处理不完所有东西,可以分几个tick()
解决。
GO间的通信
在拥有了一个可以动的游戏世界后,如何让GO之间进行“交互”?例如一个炮弹要爆炸了,它得检查旁边的GO是什么,以便做出不同的反应。
硬编码(HardCode)
传统方式是使用硬编码:
刚开始还是有用的,但随着游戏世界越来越复杂,硬编码逐渐不适用了。
事件机制(Event)
采用事件机制会使GO间的通信变得很清晰且明确,在系统架构中叫做解耦合。炸弹只需将它爆炸的时间发送给其他GO的组件,让他们自己处理就行。
有关事件的调试,可通过log和可视化等方式解决。
现代游戏引擎的事件机制是可扩展的,开发者可以自定义一些消息类型和组件,供他们使用。例如商业引擎是这样做的:
管理GO
为了方便GO的管理,得先清楚它们是怎么被查询的,有两种简单的方法:
- UID:和资源管理的GUID类似,每个GO都有自己独一无二的UID。
- 位置:知道GO的详细位置可以对他进行管理。
场景管理
对于小数量的GO,可以不对它们管理。例如,当炮弹飞过来时,只需检查该场景所有GO的位置是否在爆炸范围内即可。
而对于较大数量的GO(成千上万),这样做会卡爆玩家的电脑。可以使用分治法,将场景用格子分开,进行管理。当然,如何划分好格子又成了一个问题。
均匀划分
在不是很大的场景中,使用均匀划分就足够了,虽然会导致GO在每个格子中分布的不是很均匀。
层级划分
可以对场景进行层级划分,例如用四叉树对场景进行划分:
空间上的(Spatial)数据管理是场景管理的核心,下图是一些用于空间管理的数据结构:
一般来说,游戏引擎要实现两到三种数据结构,让游戏开发者自由选择。
高级话题
制作商业引擎时,需要考虑很多高级话题:
- GO的绑定:不同GO之间存在绑定关系,如车与人,武器与人。而绑定关系也是比较复杂的,例如在
tick()
时,得先对父GO进行tick()
,然后对他绑定的子GO进行tick()
;人在移动时,手里绑定的武器也要跟着移动等等。 - 事件机制的混乱:当各种GO同时收发事件时(并行多线程),会出现混乱。因此得有一个“邮局”的角色充当中继,让GO的通信时序严格一致。
- 组件间的互相依赖:例如Motor组件说让GO跑步时,需要让Animation组件和Physics组件配合。
- …
参考资料
- GAMES104 (boomingtech.com)