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 | void Bomb::explode() { |
- 事件机制(event)
- GO 和 component 的解耦合
1 | void Bomb::explode() { |
- 商业引擎中的 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:场景图
- BVH:层次包围盒
总结
- 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()
进行更新
- GO 的状态通过调用
- 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
- 物理和动画互相影响的时候如何处理?
- 插值过渡:一开始更多地使用定义好的动画,后面一步步转向物理计算的结果
- 设计感 + 物理真实感