8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Unity】シェーダーでTime.timeScaleを無視する

Last updated at Posted at 2021-06-16

Time.timeScale_Time

Time.timeScaleはUnityの時間進行速度(倍率)です。こいつを0にするとTime.timeが停止し、PhysicsやらAnimatorやらが(デフォルトでは)停止するため、Unityでポーズ画面などを作る際によく使います。
ですが、時にはTimeScaleを0(や他の1以外の値)にした時でも、通常時と同じようにふるまってほしい場合があります。このとき、スクリプトではTime.timeTime.deltaTimeのかわりにTime.unscaledTimeTime.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を設定する処理を挟み込んで終了。

すっきりしててうれしいですね。

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?