GAMES104.王希.03.如何构建游戏世界

如何构建游戏世界

  • How to Build a Game World
  • 游戏世界由什么组成?
  • 我们如何描述这些东西?
  • 这些东西是如何组织的?

游戏世界中的物体

  • Dynamic Game Object(动态的游戏物体)
    • 移动的 NPC
  • Static Game Object(静态物)
    • 房子
    • 树木
  • Environments
    • 天空(sky)、地形(terrain)、植被(vegetation)
    • tod:time of the day
  • Other Game Objects
    • 空气墙(air wall)
    • 检测体(trigger area)
      • 例如巫师三检测玩家走到了世界的尽头
      • 例如玩家走到特定区域才会触发剧情,走到特定区域的检测使用 trigger
    • 游戏规则
  • 任何东西都是游戏物体(Game Object)
  • GO:Game Object
    • 将游戏世界中的物体都抽象成 GO

如何描述 GO

  • 属性(property) + 行为(behavior)

  • 继承(inheritance)

  • 问题:当物体变多的时候,父子关系并不明显
  • 组件化(component base)

组件化

  • 代码
    • tick() 用于更新组件的状态

  • 无人机组件化

  • 现代游戏引擎架构的理念
    • 开发者好维护、好理解
    • 要符合艺术家设计师对于游戏的理解(游戏引擎是生产力工具)
  • 商业引擎也是基于组件化的设计
  • unreal
    • 这里的 UObject 更像是 java 中的 Object 集类,用于内存管理、GC 之类的(生命周期控制),和我们上面讲的 GO 还有所不同,这里的 AActor 更像是 GO

  • unity

如何让世界动起来

运动

  • Object-based Tick
  • 每一个 GO 里面的 component 调用 tick() 进行更新

  • 但是在实际的游戏引擎里面,一般是逐系统 tick(),而不是逐对象 tick()
    • 为了效率(pipeline)
    • 一个系统的数据通常放在一起(cache)

  • 对比
Object-based tick Component-based tick
Simple and intuitive
Easy to debug
Parallelized processing
Reduced cache miss

交互

  • GO 之间的交互
    • 例如我砍了你一刀,你要受伤
  • hardcode
    • 效率很低,新加入 GO 则需要修改大量代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void Bomb::explode() {
// ...
switch(go_type) {
case GoType.human_type : {
// ...
break;
}
case GoType.drone_type : {
// ...
break;
}
case GoType.tank_type : {
// ...
break;
}
case GoType.stone_type : {
// ...
break;
}
default : {
break;
// ...
}
}
// ...
}
  • 事件机制(event)
    • GO 和 component 的解耦合
1
2
3
4
5
void Bomb::explode() {
// ...
sendExplodeEvent(go_id);
// ...
}
  • 商业引擎中的 event 机制
  • unity

  • unreal

  • 游戏引擎的工作
    • 允许用户定制事件
    • 允许用户在组件中对事件进行处理

总结

如何管理 GO

  • GO 的查询
    • 通过 GO 的唯一标识(unique game object id)
    • 通过 GO 的位置

场景管理

  • 不管理
    • 事件发生(例如爆炸),向场景中的所有 GO 都发一遍消息(全都扫描一遍)
    • \(n^2\) 的挑战,场景中每个物体都扫描一遍其他物体
      • 对于场景中物体不多的情况下,还是可行的
      • 场景中 GO 增加,则带来大量的计算负担
  • Grid
    • 只向周围 Grid 进行发送消息
    • 当 GO 在场景中分散的不均匀的时候,又慢又浪费

  • 四叉树(QuadTree)
    • 层级结构

  • 其他空间数据结构
    • BVH:层次包围盒
      • 子弹打中某个物体 \(\Longleftrightarrow\) 光线和物体求交
    • BSP:空间划分
    • Octree:八叉树
    • Scene Graph:场景图

总结

  • Everything is a game object in the game world
    • 游戏世界中的所有东西都是 GO
  • Game object could be described in the component-based way
    • GO 使用组件化的方式描述
  • States of game objects are updated in tick loops
    • GO 的状态通过调用 tick() 进行更新
  • Game objects interact with each other via event mechanism
    • GO 之间通过事件机制进行交互
  • Game objects are managed in a scene with efficient strategies
    • GO 在场景中使用搞笑的空间数据结构进行管理

其他内容

绑定

  • Binding
  • 物体之间的绑定、联动
    • 例如你骑上马之后,你和马之间应该有相同的移动
  • 更新的时候按照父子关系更新
    • 先更新父结点,子结点再更新(基于父节点更新)
    • 顺序很重要

事件

  • 允许 GO 之间直接通信,可能会导致不一致情况的发生
    • 因为 tick() 可能是并行的,并行情况下执行顺序可能会不一致
    • 精彩回放:只是记录了玩家的输入,由于游戏引擎保证了一致性,回放和之前的画面是完全相同的
      • 一致性:同样的用户输入,引擎运行之后得到的结果是相同的
  • 引入第三方,用于控制时序

  • 解决时序问题
    • pre_tick()post_tick()

依赖

  • 玩家从走路变成跑步,此时速度会变化,如果此时玩家的动作影响了环境(例如腿碰到其他物体),环境会反过来影响玩家的状态

  • 如何更新?
    • 变化马上在这一帧更新?下一帧更新?
    • 精妙的设计

QA

  • 一个 tick() 的时间过长怎么办?
    • 每一个 tick() 的时候传入步长,补偿计算
    • 跳过一个 tick()
    • 更多的是去优化引擎设计
      • 例如保证突然产生很多物体,一帧处理不过来,可以分散到相邻几帧去做(人眼看不出来)
  • 渲染线程和逻辑线程的同步
    • 一般而言,tick_logic() 会更早一点,tick_logic(),tick_render() 二者会放在不同的线程处理
    • 输入延迟tick_logic()tick_render() 早了几帧
      • frame_buffer 的交换还需要一帧
    • 如何实时响应是引擎设计中的一个重要问题
  • 空间划分如何处理动态的 GO
    • BVH 比 BSP 更新效率更高
    • 一般需要引擎支持多种空间划分的数据结构,用于适应不同的应用场景
  • 组件模式的缺点
    • 效率可能比 class 低(为了弥补讲相同组件的数据放到一起,快速更新)
    • 组件之间越需要有通信机制,但是组件之间并不知道对方是否一定存在,因此需要询问,高频的询问会影响效率
  • Debug
    • 逐帧分析
    • 可视化 debug
    • Log
  • 物理和动画互相影响的时候如何处理?
    • 插值过渡:一开始更多地使用定义好的动画,后面一步步转向物理计算的结果
    • 设计感 + 物理真实感