PR

[Unity][UniRx]Observable.Timerで時間がずれると思ったら、使い方を理解していないせいではまった件

Unity

はじめに

みなさんはUniRxObservable.Timerを使っているでしょうか?

Observable.Timerを使うと何秒後に何かをするというコードが簡単に実装できます。

ただ、今回そのObservable.Timerで失敗したサンプルコード対応方法を備忘録として残しておきます。

環境

  • Unity 2021.1.18f1
  • UniRx 7.1.0

問題のコード

Observable.Timerを使って10秒後に時間を表示するサンプルコードです。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UniRx;

public class SampleSceneController : MonoBehaviour
{
    [SerializeField] private Button button;

    // Start is called before the first frame update
    void Start()
    {
        button?.onClick.AddListener(OnClickedButton);
    }

    private void OnClickedButton()
    {
        Debug.Log(DateTime.Now.ToString());
        Observable.Timer(TimeSpan.FromSeconds(10)).Subscribe(_ =>
        {
            Debug.Log(DateTime.Now.ToString());
        }).AddTo(this);
    }
}

実はこのコードは間違ってはいません。きっと正しく10秒後に時間を表示してくれるでしょう。

じゃぁ、何が問題なのか?

それは「このコードがTimeScaleの影響を受ける」ということです。

つまり、このコードをTimeScaleを2倍にするように書き換える10秒後ではなく5秒後に時間が表示されます。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UniRx;

public class SampleSceneController : MonoBehaviour
{
    [SerializeField] private Button button;

    // Start is called before the first frame update
    void Start()
    {
        // TimeScaleを2倍にする.
        Time.timeScale = 2.0f;
        button?.onClick.AddListener(OnClickedButton);
    }

    private void OnClickedButton()
    {
        Debug.Log(DateTime.Now.ToString());
        Observable.Timer(TimeSpan.FromSeconds(10)).Subscribe(_ =>
        {
            // 10秒後ではなく、5秒後に実行される.
            Debug.Log(DateTime.Now.ToString());
        }).AddTo(this);
    }
}

なぜこの現象がおきるかというと。。。

そういう設計になっているからです。

ちゃんとGitHubにあるREADME.mdにもしっかり書いてあります。

UniRx’s default time based operations (Interval, Timer, Buffer(timeSpan), etc) use Scheduler.MainThread as their scheduler. That means most operators (except for Observable.Start) work on a single thread, so ObserverOn isn’t needed and thread safety measures can be ignored. This is differet from the standard RxNet implementation but better suited to the Unity environment.

Scheduler.MainThread runs under Time.timeScale’s influence. If you want to ignore the time scale, use Scheduler.MainThreadIgnoreTimeScale instead.

https://github.com/neuecc/UniRx#defaultscheduler

そして、そこにはTimeScaleの影響を無視したい場合のやり方もちゃんと書いてありました

対応方法

ドキュメントに書いてあった対策方法で、今までのコードを修正すると以下のようなコードになります。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UniRx;

public class SampleSceneController : MonoBehaviour
{
    [SerializeField] private Button button;

    // Start is called before the first frame update
    void Start()
    {
        // TimeScaleを2倍にする.
        Time.timeScale = 2.0f;
        button?.onClick.AddListener(OnClickedButton);
    }

    private void OnClickedButton()
    {
        Debug.Log(DateTime.Now.ToString());
        Observable.Timer(TimeSpan.FromSeconds(10), Scheduler.MainThreadIgnoreTimeScale).Subscribe(_ =>
        {
            // TimeScaleに影響なく10秒後に実行される.
            Debug.Log(DateTime.Now.ToString());
        }).AddTo(this);
    }
}

さいごに

APIを使う際はドキュメントは良く読みましょう。

おすすめ参考書

コメント

タイトルとURLをコピーしました