1
2

Time.timeScale=0; にしても1フレだけ FixedUpdate()が呼ばれることがある

Last updated at Posted at 2024-03-22

Unity でゲームをポーズするには一般的に Time.timeScale = 0; とします。すると FixedUpdate()は呼ばれず、物理エンジンの時間が止まります。 Update() は呼ばれますが Time.deltaTime == 0 となります。よって、ゲームの動きを FixedUpdate()内で行うか、または Update()内で Time.deltaTime を動きの速さに掛け算して行えば、 Time.timeScale = 0; にすることでゲームのポーズを実現することができます。

Unityドキュメントにも以下のように書かれています。

TimeScale がゼロに設定されている場合、FixedUpdate 関数は呼び出されません。

しかし Time.timeScale = 0; にするタイミングによっては、 Time.timeScale == 0 にもかかわらず FixedUpdate() が呼ばれたり物理エンジンが1フレームだけ動いたりします

今回私がハマったのはこの点です。

結論

Time.timeScale = 0; にしたのが OnEnable() や FixedUpdate() なら、その直後の 1フレームは FixedUpdate() や物理エンジンが動く。

実験環境

バージョン Unity 2023.2.11f1
image.png

GameObject MySphere には Rigidbody と MySphere (後述) コンポーネントが追加されています。MySphere コンポーネントは球体を往復運動させます。

GameObject GameMain には GameMain (後述) コンポーネントが追加されています。GameMain コンポーネントはキー操作に応じていろいろなタイミングでポーズをかけます。

また Script Execution Order を設定して、 GameMain スクリプトは MySphere よりも前に実行されるようにしています。
image.png

MySphere コンポーネント

MySphere.cs
using UnityEngine;

// Rigidbody を往復運動させる。デバッグ表示もする。
public class MySphere : MonoBehaviour
{
    Rigidbody _rigidbody;

    void OnEnable()
    {
        _rigidbody = GetComponent<Rigidbody>();
        print($"初期位置[{GameMain.FixedUpdateCount}]*{Time.timeScale} {_rigidbody.position}");

        // 初速度
        _rigidbody.velocity = Vector3.right * 10;
    }

    void FixedUpdate()
    {
        if (Time.timeScale == 0)
        {
            print($"FixedUpdate[{GameMain.FixedUpdateCount}]*{Time.timeScale} {_rigidbody.position}");
        }

        // 往復運動させる
        if (Mathf.Abs(_rigidbody.position.x) > 5)
        {
            _rigidbody.velocity *= -1;
        }
    }

    void Update()
    {        
        if (Time.timeScale == 0)
        {
            print($"Update[{GameMain.FixedUpdateCount}]*{Time.timeScale} {_rigidbody.position} d={Time.deltaTime}");
        }
    }
}

GameMain コンポーネント

GameMain.cs
using UnityEngine;

// キー入力などによってポーズをかけるもの。
// シーン初期化時(OnEnable)にポーズ
// Escキーを押すと FixedUpdate()でポーズ
// Pキーを押すと Update()でポーズ
// スペースキーでポーズ解除
public class GameMain : MonoBehaviour
{
    public static int FixedUpdateCount;

    bool _requestPause;

    void OnEnable()
    {
        FixedUpdateCount = 0;
        _requestPause = false;

        print($"[{FixedUpdateCount}] シーン初期化時にポーズ");
        Time.timeScale = 0;
    }

    void FixedUpdate()
    {
        if (_requestPause)
        {
            _requestPause = false;
            print($"[{FixedUpdateCount}] FixedUpdateでポーズ");
            Time.timeScale = 0;
        }

        FixedUpdateCount++;
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            print($"[{FixedUpdateCount}] Updateでポーズ解除");
            Time.timeScale = 1;
            _requestPause = false;
        }

        if (Input.GetKeyDown(KeyCode.Escape))
        {
            _requestPause = true;
        }

        if (Input.GetKeyDown(KeyCode.P))
        {
            print($"[{FixedUpdateCount}] Updateでポーズ");
            Time.timeScale = 0;
        }
    }
}

実験結果

シーン初期化時

Unity Editor で Play すると Console出力は以下のようになります。
image.png

GameMain.OnEnable() ← ここでポーズしてるつもり
MySphere.OnEnable()
GameMain.FixedUpdate() // FixedUpdateCount++ されたのでこれが呼ばれたとわかる
MySphere.FixedUpdate()
MySphere.Update() (Time.deltaTime=0.01999999)
MySphere.Update() (Time.deltaTime=0)

の順に呼ばれていることがわかります。しかも MySphere.FixedUpdate() と MySphere.Update() で _rigidbody.position が違う値になったので、物理エンジンも動いたことがわかります。

Updateでポーズ

Play して、スペースキーを押してポーズ解除し、1秒ほど経ってから Pキーを押しました。GameMain.Update()の中で Time.timeScale = 0; にします。
image.png

この場合、FixedUpdate() は呼ばれず、物理エンジンも止まっているようです。

FixedUpdateでポーズ

Play して、スペースキーを押してポーズ解除し、1秒ほど経ってから Escキーを押しました。
この場合、 GameMain.Update() で Escキー押下を検知して _requestPause=true; にし、次のフレームの GameMain.FixedUpdate() で Time.timeScale = 0; にしています。
image.png

GameMain.Update()
+--------(次のフレーム)--------+
GameMain.FixedUpdate() ← Time.timeScale=0;
MySphere.FixedUpdate()
MySphere.Update() (Time.deltaTime=0.004245302)
MySphere.Update() (Time.deltaTime=0)

の順に呼ばれていることがわかります。しかも MySphere.FixedUpdate() と MySphere.Update() で _rigidbody.position が違う値になったので、物理エンジンも動いたことがわかります。

考察

実験結果をこの図と比べると、図の薄い灰色背景に囲まれた Physics (右側) から上で Time.timeScale = 0; にしても即座に効果は出ず、それより後の OnMouseXXX, Update などから Time.timeScale = 0; にすれば即座に効くのではないか、と推測します。

1
2
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
1
2