Time.timeScale
と_Time
Time.timeScale
はUnityの時間進行速度(倍率)です。こいつを0にするとTime.time
が停止し、PhysicsやらAnimatorやらが(デフォルトでは)停止するため、Unityでポーズ画面などを作る際によく使います。
ですが、時にはTimeScaleを0(や他の1以外の値)にした時でも、通常時と同じようにふるまってほしい場合があります。このとき、スクリプトではTime.time
やTime.deltaTime
のかわりにTime.unscaledTime
、Time.unscaledDeltaTime
を使うことで期待する動作を実現できます。AnimatorもUpdate ModeをUnscaled
にすることでTimeScaleを無視して時間を進め続けてくれます。
ではシェーダーではどうかというと……
シェーダーでアニメーションなどを行う際は_Time
プロパティを使いますが、これはTime.timeScaleの影響を受けます。つまりシェーダーでポーズメニューUIのかっこいいアニメーションエフェクトなどを作ろうとすると、Time.timeScaleの影響を受けてあえなく停止……
①愚直な方法
シェーダープロパティはスクリプトから設定できるのですから、こうすれば動かせそうです。
private void Update()
{
material.SetFloat("_UnscaledTime", Time.unscaledTime);
}
シェーダーに_UnscaledTimeプロパティを設定して参照できます。
ですが……使用箇所毎にこれを書くのはちょっとキモいですね。MonoBehaviourの数も増えてよろしくありません。
②多少マシな方法
Unityではシェーダーのグローバル変数、つまりマテリアル間で共有されるプロパティを設定できます。
UnityEngine.Shader - Unity スクリプトリファレンス
ここにあるShader.SetGlobalFloat
やらがそれです。
private void Update()
{
Shader.SetGlobalFloat("_UnscaledTime", Time.unscaledTime);
}
これでシェーダー内でfloat _UnscaledTime
を宣言すれば、timeScaleに左右されない時刻が取得できます。
これならSceneに一個だけ置いとけばいいので、前の方法よりラクそうです。大抵のケースはこれでカバーできるでしょう。
③もっと根本的な方法
専用スクリプトいっさい置きたくない!!!という方向けに、もっと強力な方法を紹介しておきます。
PlayerLoopをいじってMonoBehaviour非依存でグローバルプロパティを更新する方法です。
PlayerLoopにカスタムの処理を挟む処理ですが……私が以前書いたPlayerLoopをいじるためのヘルパースクリプトをつかってちゃっちゃと書きます。
こちらのPlayerLoopModifier.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
属性で、これをつけたメソッドはMonoBehaviourだろうが関係なくスタートアップ時に実行されます。つまりMonoBehaviourに一切依存することなく、シーンを編集することもなく、任意の処理を挟み込めるわけなのです。あとはUpdate.ScriptRunBehaviourUpdateの直後にUnscaledTimeを設定する処理を挟み込んで終了。
すっきりしててうれしいですね。