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
  • ……

利用GameObjectBaseComponentBase类派生出来的无人机基类如下:

这样,相较于麻烦的继承,我们直接给无人机加上战斗模块并修改一下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)