Lecture 03 How to build a Game World
Everything is a Game Object (GO)
-
面向对象的方式
有些GO之间并没有清晰的继承关系
- Unreal中的UObject、Unity中的Object并不是这里讲的GameObject概念,而是更类似如C#中的Object,用于确定任何对象的生命周期需要的句柄
- Unreal中的GameObject概念对应的应该是Actor,Unity中是GameObject
-
Component Base
-
组件化,如果涉及到多重继承就应该用组合
-
Transform
-
Model
-
Motor
-
Animation
-
Physics
-
AI
-
...
-
-
所有组件都有一个
tick
函数 -
组件模式的缺点
- 如果用最基础的实现时,效率不如class,所以ECS中同样的组件同样的数据放在一起,用方法快速处理很重要
- 组件之间也需要一套通讯机制,一个组件并不知道同一个GameObject还有多少组件,所以当一个组件要访问另一个组件时,需要不停查询有没有挂载这个组件,高频调用时很消耗性能
-
-
总结
- 任何物体都是GameObject
- GameObject应该以component-based的方式描述
How to Make the World Alive?
将GameObject中的component都依次tick一遍
Component-based Tick
现代游戏引擎中不是按GameObject进行tick的,而是按Component进行tick
-
Object-based tick、
- 简单并且符合直觉
- 容易debug
-
Component-based tick
- 并行运算
- 减少缓存未命中
- 将Component的数据集中在一起
-
tick时序
-
父节点先tick,子节点后tick
-
这样做的话component-based tick会变得复杂
因为这些tick是并行执行的,而并行执行的时序非常重要 ,时序不一样会带来逻辑上的混乱
-
于是引入一个消息中心,GameObject的Event先发给中心,由中心按照设定顺序转发给GameObject
-
Component Dependencies
-
Motor tick Animation,Animation 有可能又tick了Physics,又影响了Motor
-
物理和动画互相影响时的处理
-
当人受击时
-
切换到布娃娃系统(看着像没有骨骼),看着物理真实,但是觉得假
-
受击动画,这部分基于深度学习有更好的结果
现在主流的做法是插值,受击开始时切动画的表现,越往后,动画的位移作为物理的初输出,交给物理去模拟,这样不会看着假,并且结果也很随机,符合物理真实
-
-
-
-
收到Event是当场处理还是下一帧再处理?对游戏逻辑非常重要
-
-
如果一个tick过长
-
每个tick将步长传进去,这样就能补偿起来
-
直接跳过下一个tick,tick两帧(比较危险)
-
deferred process
比如发生一个爆炸,带来了大量需要处理的内容,这时候可以用五六帧的时间去处理,而五帧也只有0.2s左右,不太影响视觉
-
-
tick时,渲染线程和逻辑线程怎么同步
- 一般二者在不同线程进行
- 逻辑线程会比渲染线程早一些
GameObject之间的交互
- Hardcore
- 比如坦克开炮,生成一个GameObject炮弹,炮弹爆炸时查询周边对象
- 当游戏世界复杂起来时,Hardcore就不work了
- Events
- Message sending and handling
- Decoupling event sending and handling
- 本来需要知道所有GameObject的类型,现在只需要发出一个Event给对应的GameObject,让它自己来处理即可,这里不同GameObject就解耦合了
- 开发引擎时要制作可拓展的消息机制
- 如果GameObject之间直接通过Event通信会产生很多逻辑上的混乱性
- 比如A向B发出event,B同时也向A发出Event,那么结果可能与处理顺序(不确定)有关,而我们希望结果是可控的
- 难调试
- 打Log
- 可视化方法(游戏中的Debug模式)
Scene Management
-
GameObject
- 有一个unique ID
- 有一个position
-
Scene Management
-
最简单的管理就是不划分
-
划分
-
划格子
- 小场景可用
- 现代游戏中GO分布不均匀
-
Hierarchy 层级结构
-
2D四叉树
-
3D八叉树
-
BVH
每个物体有一个boundingbox,将boundingbox不断合成成大的boundingbox
-
-
动态物体的处理
- 最简单的做法,每一帧都生成一遍hierarchy,效率低
- 一般选择更新起来比较轻量的数据结构
- 比如BVH,如果用球来表示boundingbox,BVH的合并只是让球的半径变长,消耗小
- 引擎一般支持两三种以上经典空间划分的算法,让游戏产品自己去选择
-
-
总结
- 任何东西都是一个Object
- GameObject应该以component-based形式描述
- GameObject的状态以tick循环的形式更新
- GameObject通过event机制交互
- GameObject过一些有效率的策略来做场景管理(层级结构)