时间速率拆分系统TimeScaleManager
时间速率拆分系统TimeScaleManager
背景
控制时间速率是非常重要的功能,传统做法是修改Time.Scale,这会影响游戏的整体时间速率流逝,但是更多的需求则是单独控制各模块的时间速率,尤其是在有多种游戏机制同时运作的场景下,玩家或系统可能需要通过不同的时间速率处理各个部分的逻辑。
通过时间速率拆分系统,不同部分的逻辑可以在不同时间速率下独立运行,比如动画、物理、特定事件等,TimeScaleManager将常用unity组件进行了封装,开发者无需关心其他组件对时间速率值的应用。
目前TimeScaleManager继承mono,方便进行调试,实际上该系统可以脱离mono运行。
使用
解决方案说明
时间速率拆分系统的核心由以下三个主要类组成:
- TimeScaleManager:时间速率的管理器,用于管理所有时间相关的逻辑。
- TimeHolder:时间控制器,负责控制每个时间块的速率及其父子关系。
- TimeUser:时间使用者,绑定特定的TimeHolder,并根据时间速率进行自身组件的更新。
- 时间速率继承结构举例如下图:
添加时间控制器TimeHolder
开发者可以直接在GameObject挂载TimeHolder组件,也可以通过TimeScaleManager添加TimeHolder:
1 | // 由于整套解决方案可以脱离mono运行,所以可以通过代码直接添加 |
TimeHolder
TimeHolder 是每个时间控制块的核心类。它能够控制自己以及子TimeHolder的时间速率,可以用于实现更复杂的时间速率嵌套机制。TimeHolder 通过局部时间速率和父级时间速率的混合计算出实际的时间速率。
1 | // 父子时间速率的混合计算方式 |
在Inspector中可以很直观地看出各个TimeHolder的父子关系,这也是为什么这套解决方案可以脱离mono但还是继承mono的原因:
TimeUser
TimeUser 是 TimeHolder 的 消费者,通常是某个游戏对象或组件,它们需要依据 TimeHolder 的时间速率进行更新,比如动画、物理、音效等。
以暂停界面PauseView的动画播放速度举例,其使用名为 “UI” 的TimeHolder的速率:
角色则使用以名为 “Game” 的TimeHolder的速率:
自动使用TimeUser的速率
挂载了TimeUser组件的GameObject,会自动检索这个GameObject挂载的其他组件,对于已封装的unity内置组件,TimeUser会更改该组件的一系列使用到时间速率的行为。而开发者并不需要关心内部实现逻辑,也不需要手动修改组件的函数实现。
以动画播放器Animator举例:
1 | // 代码内部重写Animator的函数 |
手动使用TimeUser的速率
以手动计算角色GameObject的移动速度举例:
1 | public void Player |
内部实现
- 通过父子继承,类似树形结构实现TimeHolder的嵌套计算。
- 方案实现主要运用了装饰器的设计模式,对unity的内置组件往上再封装一层,在该层中应用TimeUser的速率,并重写组件的原函数实现。以装饰Rigibody举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 封装unity内置的Rigibody组件
public abstract class RigidbodyTimeUser<TComponent>: ComponentTimeUser<TComponent>, IRigidbodyTimeUser where TComponent : Component
{
// 重写组件的 Update()
public override void Update()
{
// 应用 TimeUser的时间速率
switch (TimeUser.timeScale)
{
case <= 0:
IsReallyManual = true;
break;
case > 0:
IsReallyManual = IsManual;
WakeUp();
break;
}
}
}