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