游戏物体拆解
GameObject
现代游戏引擎中,将物体统一称为GameObject(GO),主要包括如下:
可交互动态物(DynamicGameObject)
静态物(staticGameObject)
环境(地形系统、天空、植被系统...)
其他物体(TriggerArea、navigationMesh,ruler)
对GO的描述:
Property:包括几何模型、位置、Transform等等
Behavior:行为逻辑
早期引擎大多都是按照这样的方式,使用面向对象的思维,但是面向对象无法解决繁杂交错的派生关系,所以便有了组件化(ComponentBase)
组件模块
如图。每个派生类中都有一个Tick()函数,方便更新


Object-based Tick
在每一个Tick,依次更新每一个GO的Component
Component-based Tick
但是在现代引擎中,一般是按照系统依次Tick,来提高效率

GO之间如何交互
事件机制:在下一个Tick时接收事件(订阅广播)
组件模式有何缺点?
效率不如class,每次Tick需要查询组件(这一点在ECS架构中可以进行数据层面的优化)
// 传统组件模式的性能问题示例
public class GameObject {
private List<Component> components;
public void Update() {
foreach(var component in components) {
component.Update(); // 缓存不友好,虚函数调用开销
}
}
}
// ECS架构优化(Unity DOTS)
public class MovementSystem : SystemBase {
protected override void OnUpdate() {
Entities.ForEach((ref Translation translation, in Velocity velocity) => {
translation.Value += velocity.Value * Time.deltaTime;
}).ScheduleParallel(); // 数据导向,批量处理
}
}组件之间也需要设计通讯系统
如何管理GO
标识物体:使用UID与物体的世界坐标
不管理GO、直接广播事件:在少量GO时可以使用,但是会导致n平方问题
分治管理:画格子,在格子中广播事件,但是当GO分布不均匀时导致问题
四叉树/八叉树管理:记录事件节点
BVH(BoundingVolumeHierarchies):现代引擎常用的boundingBox
动态物体如何处理?
每种管理方式都有各自的更新数据的方式,会根据不同的游戏需求去设计不同的空间管理来减少性能开销,比如静态的单机游戏使用四叉树或者八叉树,多人的开放世界游戏使用BVH
处理复杂情况
GOTick时序问题:
一般是父节点先Tick
面对复杂情况时,由于不同的Component一般是在不同的CPU上并行执行,如果此时GO相互发送消息,容易出现逻辑混乱,影响游戏的确定性,所以需要一个管理事件的中心作为中转来做到同步(类似于同步锁?)同时也需要preTick/postTick解决Tick依赖时序
并且许多组件可能出现循环依赖的问题,比如Animation和physics

主流的方法是采用插值的方式,某几帧是动画,将动画的输出作为物理的输入
Tick时间过长怎么办?
传递步长,下一帧补偿
分帧处理
Tick时,渲染线程与逻辑线程如何同步?
会先更新逻辑帧再更新渲染
参与讨论
(Participate in the discussion)
参与讨论