Unity3D可以通过Time.timeScale实现暂停、快进和慢进功能。
关于Time.timeScale和Update()普遍有个误区,认为当Time.timeScale = 0时,Update()停止工作。
在FPS = 60、FixedTimestep = 0.02f 的条件下测试了一下:
- 当Time.timeScale = 0时,Update()和LateUpdate()每秒执行60次,FixedUpdate()不执行,Time.deltaTime = 0.0167f。
- 当Time.timeScale = 0.5f时,Update()和LateUpdate()每秒执行60次,FixedUpdate()每秒执行25次,Time.deltaTime = 0.0167f。
- 当Time.timeScale = 1时,Update()和LateUpdate()每秒执行60次,FixedUpdate()每秒执行50次,Time.deltaTime = 0.0083f。
- 当Time.timeScale = 2时,Update()和LateUpdate()每秒执行60次,FixedUpdate()每秒执行100次,Time.deltaTime = 0.0333f。
结论,Update()和LateUpdate()每秒执行FPS次,FixedUpdate()每秒执行Time.timeScale/FixedTimestep次,Time.deltaTime = Time.timeScale/FPS。
所以,Update()和LateUpdate()的执行与Time.timeScale无关。
Time.timeScale改变的是Time.time和Time.deltaTime,由于FixedUpdate()的执行是与时间相关的,所以FixedUpdate()会受到影响。
从上面的测试中,我们得知当Time.timeScale = 0时,Update()还是会正常执行的,只是Time.deltaTime = 0造成了如transform.position += Vector3.forward * 60 * Time.deltaTime
不执行的假象。
如果Update()中有transform.position += Vector3.forward
这种语句,物体将持续运动,不会暂停。
我们将通过以下脚本解决暂停问题:
using UnityEngine;
using System.Collections;
public class PauseObject : MonoBehaviour
{
protected bool _isPause = false;
public virtual bool Pause()
{
if (_isPause) return false;
_isPause = true;
return true;
}
public virtual bool Resume()
{
if (!_isPause) return false;
_isPause = false;
return true;
}
}
将需要暂停的脚本继承PauseObject,然后在继承后的脚本Update()里写:
void Update()
{
if (_isPause) return;
DoingSomething();
}
也可以在Pause()中添加enable = false
,Resume()添加enable = ture
解决,这样就不用在每个继承的脚本中判断_isPause了。
但是设置enable会触发OnEnable()或OnDisable(),如果代码中某些功能依赖于OnEnable()或OnDisable()则会造成一些麻烦,为了保险起见,最好不要这样做。
然后编写PauseController:
using UnityEngine;
using System.Collections;
public class PauseController : MonoBehaviour
{
static PauseObject[] _pauseObjects;
public static void Pause()
{
Time.timeScale = 0;
_pauseObjects = FindObjectsOfType<PauseObject>();
foreach(PauseObject pauseObject in _pauseObjects)
{
pauseObject.Pause();
}
}
public static void Resume()
{
Time.timeScale = 1;
if (null == _pauseObjects) return;
foreach(PauseObject pauseObject in _pauseObjects)
{
if (pauseObject != null) pauseObject.Resume();
}
}
}
暂停调用PauseController.Pause()
,恢复调用PauseController.Resume()
在Resume()中最好不要使用_pauseObjects = FindObjectsOfType<PauseObject>();
一是防止不必要的开销,二是如果在暂停后场景中新添了可暂停物体,那么会带了一些不必要的麻烦,即使在PauseObject做了状态判断。
if (pauseObject != null)
是为了防止在暂停后,场景有物体销毁造成pauseObject为空。
好了,现在我们已经实现暂停和恢复功能了,但还是有点问题。
一般在暂停后,我们会弹出一个暂停窗口,暂停窗口需要一个弹出的动画。
此时Time.deltaTime = 0,暂停窗口不会有位移,所以我们需要自己实现一个计时器:
using UnityEngine;
using System.Collections;
public class PauseRealTime : MonoBehaviour
{
static float _deltaTime;
static float _curPointTime;
static float _lastPointTime;
static bool _isInstance = false;
public static float time
{
get
{
if (!_isInstance) Instance();
return _curPointTime;
}
}
public static float deltaTime
{
get
{
if (!_isInstance) Instance();
return _deltaTime;
}
}
static void Instance()
{
_isInstance = true;
GameObject obj = new GameObject("Pause Real Time");
obj.AddComponent<PauseRealTime>();
DontDestroyOnLoad(obj);
}
void Start()
{
_curPointTime = Time.realtimeSinceStartup;
}
void LateUpdate()
{
_lastPointTime = _curPointTime;
_curPointTime = Time.realtimeSinceStartup;
_deltaTime = Mathf.Clamp01(_curPointTime - _lastPointTime);
}
}
把Time.deltaTime替换为PauseRealTime.deltaTime就好了。
写完发现NGUI自己也实现了一个计时器RealTime,有NGUI的同学,也可以直接调用RealTime.deltaTime。
这样就实现了暂停后的动画。
目前在项目中还不需要暂停后播放Animator或Animation,所以还未实现。
等有时间时再补完这部分。
下篇将写不使用Time.timeScale实现的暂停机制。