はじめに
みなさんはUniRxのObservable.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 forObservable.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.https://github.com/neuecc/UniRx#defaultscheduler
Scheduler.MainThread
runs under Time.timeScale’s influence. If you want to ignore the time scale, useScheduler.MainThreadIgnoreTimeScale
instead.
そして、そこには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を使う際はドキュメントは良く読みましょう。
コメント