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實現的暫停機制。