Unity3D之暫停



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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM