Time.timeScale
是 Unity 的时间缩放变量。如果将此设置为0,那么 Time.time
将停止,并且 Physics
和 Animator
在默认情况下也将停止。所以,将 Time.timeScale 设置为0,通常为了做暂停相关的东西。
但是,有时候希望即使将 TimeScale 设置为 0,有些东西也能继续运行,比如在写 Shader 时,经常会用到 _Time
属性,但是它会受到 Time.timeScale
的影响。不过有以下几种方式可以做到不受影响。
方法一
private void Update()
{
material.SetFloat("_UnscaledTime", Time.unscaledTime);
}
这种方式比较直接,但是也有弊端,如果要设置的 Shader 很多,会增加很多 MonoBehaviour。
方法二
Unity 可以设置 Shader 全局变量,即材质之间共享属性。
private void Update()
{
Shader.SetGlobalFloat("_UnscaledTime", Time.unscaledTime);
}
这样场景中只需要一个做这件事情的 MonoBehaviour 的脚本即可。这个方法可以覆盖大多数情况。
方法三
如果场景中不想放任何脚本,还有一个更强大的方法,那就是使用 PlayerLoop 来独立于 MonoBehaviour 更新全局属性。
首先引入一个工具类 PlayerLoopModifier.cs
,代码如下
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.LowLevel;
public class PlayerLoopModifier : IDisposable
{
private PlayerLoopSystem root;
public PlayerLoopModifier()
{
root = PlayerLoop.GetCurrentPlayerLoop();
}
public bool InsertAfter<T>(PlayerLoopSystem subSystem) where T : struct
{
return InsertAfter<T>(subSystem, ref root);
}
public bool InsertBefore<T>(PlayerLoopSystem subSystem) where T : struct
{
return InsertBefore<T>(subSystem, ref root);
}
public bool InsertIn<T>(PlayerLoopSystem subSystem) where T : struct
{
return InsertIn<T>(subSystem, ref root);
}
public void Dispose()
{
PlayerLoop.SetPlayerLoop(root);
}
private static bool InsertAfter<T>(PlayerLoopSystem subSystem, ref PlayerLoopSystem parentSystem)
where T : struct
{
var subSystems = parentSystem.subSystemList?.ToList();
if (subSystems == default) return false;
bool found = false;
for (int i = 0; i < subSystems.Count; i++)
{
var s = subSystems[i];
if (s.type == typeof(T))
{
found = true;
subSystems.Insert(i + 1, subSystem);
break;
}
}
if (!found)
{
for (int i = 0; i < subSystems.Count; i++)
{
var s = subSystems[i];
if (InsertAfter<T>(subSystem, ref s))
{
found = true;
subSystems[i] = s;
break;
}
}
}
if (found)
parentSystem.subSystemList = subSystems.ToArray();
return found;
}
private static bool InsertBefore<T>(PlayerLoopSystem subSystem, ref PlayerLoopSystem parentSystem)
where T : struct
{
var subSystems = parentSystem.subSystemList?.ToList();
if (subSystems == default) return false;
bool found = false;
for (int i = 0; i < subSystems.Count; i++)
{
var s = subSystems[i];
if (s.type == typeof(T))
{
found = true;
subSystems.Insert(i, subSystem);
break;
}
}
if (!found)
{
for (int i = 0; i < subSystems.Count; i++)
{
var s = subSystems[i];
if (InsertBefore<T>(subSystem, ref s))
{
found = true;
subSystems[i] = s;
break;
}
}
}
if (found)
parentSystem.subSystemList = subSystems.ToArray();
return found;
}
private static bool InsertIn<T>(PlayerLoopSystem subSystem, ref PlayerLoopSystem parentSystem)
where T : struct
{
var subSystems = parentSystem.subSystemList?.ToList();
if (subSystems == default) subSystems = new List<PlayerLoopSystem>();
bool found = false;
if (parentSystem.type == typeof(T))
{
subSystems.Insert(0, subSystem);
found = true;
}
else
{
for (int i = 0; i < subSystems.Count; i++)
{
var s = subSystems[i];
if (InsertIn<T>(subSystem, ref s))
{
found = true;
subSystems[i] = s;
break;
}
}
}
if (found)
parentSystem.subSystemList = subSystems.ToArray();
return found;
}
}
然后开始开始编写控制代码 UnscaledShaderTime.cs
using UnityEngine;
using UnityEngine.LowLevel;
using UnityEngine.PlayerLoop;
public static class UnscaledShaderTime
{
[RuntimeInitializeOnLoadMethod]
private static void Register()
{
using (var modifier = new PlayerLoopModifier())
{
modifier.InsertAfter<Update.ScriptRunBehaviourUpdate>(new PlayerLoopSystem()
{
updateDelegate = OnUpdate,
type = typeof(UnscaledShaderTimeUpdate)
});
}
}
private static void OnUpdate()
{
Shader.SetGlobalFloat("_UnscaledTime", Time.unscaledTime);
}
public struct UnscaledShaderTimeUpdate
{
}
}
RuntimeInitializeOnLoadMethod
属性,是 Unity 的东西,不管是不是 MonoBehaviour,都会在游戏启动的时候执行这个方法。