質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Rx.NET

Rx.NETは、リアクティブプログラミングが可能なライブラリの一つ。.NET Framework向けReactive Extensionsライブラリです。マイクロソフト社が初めてRxライブラリとして提供し、ここから多くの言語にRxが移植されるようになりました。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

2964閲覧

UniRxで実装したタイマーを一時停止/再開したい

hamalt

総合スコア23

Rx.NET

Rx.NETは、リアクティブプログラミングが可能なライブラリの一つ。.NET Framework向けReactive Extensionsライブラリです。マイクロソフト社が初めてRxライブラリとして提供し、ここから多くの言語にRxが移植されるようになりました。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2022/05/04 03:07

編集2022/05/04 03:09

ゲームのポーズ機能のため、UniRxで実装したタイマーを一時停止/再開させたいのですが、知識不足でキレイな方法が浮かびません。

実装コード(Observable)

C#

1public class TimerManager : MonoBehaviour 2{ 3 // 試合開始前のカウントダウン(Observable) 4 public IObservable<int> GameStartCountDownObservable => gameStartCountDownObservable.AsObservable(); 5 private IConnectableObservable<int> gameStartCountDownObservable; 6 7 // 試合中のカウントダウン(Observable) 8 public IObservable<int> InPlayCountDownObservable => inPlayCountDownObservable.AsObservable(); 9 private IConnectableObservable<int> inPlayCountDownObservable; 10 11 // カウントダウン秒数 12 public int CountDownTime { get; private set; } = 2; 13 14 // プレイ秒数 15 public int PlayTime { get; private set; } = 60; 16 17 void Awake() 18 { 19 // 試合前のカウントダウンタイマ 20 // CountDownTime秒タイマのストリームをPublishでHot変換(まだConnectはしない) 21 gameStartCountDownObservable = CreateCountDownObservable(CountDownTime).Publish(); 22 23 // 試合中のタイマ 24 // PlayTime秒タイマのストリームをPublishでHot変換(まだConnectはしない) 25 inPlayCountDownObservable = CreateCountDownObservable(PlayTime).Publish(); 26 27 // 3秒タイマのOnCompleteで60秒タイマをConnectする(60秒タイマの起動) 28 // おそらく第二引数がコンプリート時のコールバック処理 29 gameStartCountDownObservable.Subscribe(_ => {; }, () => inPlayCountDownObservable.Connect()); 30 } 31 32 // CountTimeだけカウントダウンするストリーム 33 private IObservable<int> CreateCountDownObservable(int countTime) 34 { 35 return Observable 36 .Timer(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1)) 37 .Select(x => (int)(countTime - x)) 38 .TakeWhile(x => x > 0); 39 } 40 41 // カウントダウンを開始 42 public void StartCountDown() 43 { 44 // ゲーム開始前のカウントダウンのタイマ起動 45 gameStartCountDownObservable.Connect(); 46 } 47}

購読側

あとは外部スクリプトでTimeManager.StartCountDown()を実行してConnect。
以下のように購読してカウントダウンの秒数を取得しています。

C#

1// Connect 2TimeManager.StartCountDown(); 3 4// カウントダウンの購読 5TimerManager.GameStartCountDownObservable 6 .Subscribe(time => 7 { 8 Debug.Log(time); 9 }, () => 10 { 11 // カウントダウンが完了したとき 12 // ゲーム開始 13 GameManager.StartGame(); 14 }) 15 .AddTo(this);

この状態で

  • ポーズボタンを押したらカウントダウンを停止
  • 再開ボタンを押したらカウントダウンを再開

したいのですが、上手いやり方が中々思いつきません。

経過秒数を小数点含めて保持しポーズ時にDisposeで破棄、再開時に経過秒数で初期化しConnectする他ないのでしょうか。
ご教授いただけますと幸いです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

「経過秒数を小数点含めて保持しポーズ時にDisposeで破棄、再開時に経過秒数で初期化しConnect」とおっしゃる方針でも行けるかもしれませんが、ちょっと複雑になってしまいそうな気配がしますね...
Timerの代わりにコルーチンで時間を計るというのはどうでしょうかね?

試しにCreateCountDownObservableが作るストリームをコルーチンベースに変更してみました。

C#

1using System; 2using System.Collections; 3using UniRx; 4using UnityEngine; 5 6public class TimerManager : MonoBehaviour 7{ 8 private IConnectableObservable<int> gameStartCountDownObservable; 9 10 private IConnectableObservable<int> inPlayCountDownObservable; 11 12 // 試合開始前のカウントダウン(Observable) 13 public IObservable<int> GameStartCountDownObservable => gameStartCountDownObservable.AsObservable(); 14 15 // 試合中のカウントダウン(Observable) 16 public IObservable<int> InPlayCountDownObservable => inPlayCountDownObservable.AsObservable(); 17 18 // カウントダウン秒数 19 public int CountDownTime { get; } = 2; 20 21 // プレイ秒数 22 public int PlayTime { get; } = 60; 23 24 private void Awake() 25 { 26 // 試合前のカウントダウンタイマ 27 // CountDownTime秒タイマのストリームをPublishでHot変換(まだConnectはしない) 28 gameStartCountDownObservable = CreateCountDownObservable(CountDownTime).Publish(); 29 30 // 試合中のタイマ 31 // PlayTime秒タイマのストリームをPublishでHot変換(まだConnectはしない) 32 inPlayCountDownObservable = CreateCountDownObservable(PlayTime).Publish(); 33 34 // 3秒タイマのOnCompleteで60秒タイマをConnectする(60秒タイマの起動) 35 // おそらく第二引数がコンプリート時のコールバック処理 36 gameStartCountDownObservable.Subscribe(_ => { ; }, () => inPlayCountDownObservable.Connect()); 37 } 38 39 // CountTimeだけカウントダウンするストリーム 40 private IObservable<int> CreateCountDownObservable(int countTime) 41 { 42 return Observable.FromCoroutine<int>(observer => CountDownCoroutine(observer, countTime)); 43 } 44 45 private float timeScale = 1.0f; 46 47 // 時間経過速度の倍率 48 public float TimeScale 49 { 50 get => timeScale; 51 set => timeScale = Mathf.Max(value, 0.0f); 52 } 53 54 private IEnumerator CountDownCoroutine(IObserver<int> observer, int countTime, float interval = 1.0f) 55 { 56 var phase = interval; 57 while (true) 58 { 59 yield return null; 60 61 // 経過時間を積算していく 62 // このとき、経過時間にタイムスケールを乗じることで 63 // 時間経過の速さをコントロールする 64 phase += Time.deltaTime * timeScale; 65 66 // phaseがintervalを超えるたびに値を発行する 67 if (phase >= interval) 68 { 69 var invocationCount = (int)(phase / interval); 70 phase %= interval; 71 for (var i = 0; i < invocationCount; i++) 72 { 73 if (countTime > 0) 74 { 75 observer.OnNext(countTime); 76 } 77 else 78 { 79 observer.OnCompleted(); 80 yield break; 81 } 82 83 countTime--; 84 } 85 } 86 } 87 } 88 89 // カウントダウンを開始 90 public void StartCountDown() 91 { 92 // ゲーム開始前のカウントダウンのタイマ起動 93 gameStartCountDownObservable.Connect(); 94 } 95}

上記のように改変したTimerManagerを、別途実験用に用意した下記スクリプトから利用したところ...

C#

1using System.Collections; 2using System.Diagnostics; 3using UniRx; 4using UnityEngine; 5using Debug = UnityEngine.Debug; 6 7public class TimerTest : MonoBehaviour 8{ 9 [SerializeField] private TimerManager timerManager; 10 11 private bool isCounting = true; 12 13 private IEnumerator Start() 14 { 15 // スペースキーを押して実験開始 16 Debug.Log("Hit space to start countdown."); 17 var waitUntilSpace = new WaitUntil(() => Input.GetKeyDown(KeyCode.Space)); 18 yield return waitUntilSpace; 19 20 // 参考情報として経過時間を測定する 21 var stopwatch = new Stopwatch(); 22 stopwatch.Start(); 23 24 // Connect 25 timerManager.StartCountDown(); 26 27 // カウントダウンの購読 28 timerManager.GameStartCountDownObservable 29 .Subscribe( 30 time => 31 { 32 Debug.Log($"GameStartCountDown: {time}, Elapsed: {stopwatch.ElapsedMilliseconds * 0.001:F3}"); 33 }, 34 () => 35 { 36 // カウントダウンが完了したとき 37 // ゲーム開始 38 Debug.Log($"Start game! Elapsed: {stopwatch.ElapsedMilliseconds * 0.001:F3}"); 39 }) 40 .AddTo(this); 41 timerManager.InPlayCountDownObservable 42 .Subscribe( 43 time => { Debug.Log($"InPlayCountDown: {time}, Elapsed: {stopwatch.ElapsedMilliseconds * 0.001:F3}"); }, 44 () => 45 { 46 // カウントダウンが完了したとき 47 // ゲーム終了 48 Debug.Log($"Time is up! Elapsed: {stopwatch.ElapsedMilliseconds * 0.001:F3}"); 49 stopwatch.Stop(); 50 }) 51 .AddTo(this); 52 53 // スペースキーを押したらタイムスケールを1.0または0.0に切り替えることで 54 // カウントを一時停止、または再開する 55 // なお、停止中は参考用のストップウォッチも一時停止することにした 56 while (true) 57 { 58 yield return null; 59 yield return waitUntilSpace; 60 61 isCounting = !isCounting; 62 if (isCounting) 63 { 64 timerManager.TimeScale = 1.0f; 65 stopwatch.Start(); 66 Debug.Log($"Resumed. Elapsed: {stopwatch.ElapsedMilliseconds * 0.001:F3}"); 67 } 68 else 69 { 70 timerManager.TimeScale = 0.0f; 71 stopwatch.Stop(); 72 Debug.Log($"Paused. Elapsed: {stopwatch.ElapsedMilliseconds * 0.001:F3}"); 73 } 74 } 75 } 76}

下図のような挙動になりました。

図

投稿2022/05/05 02:36

Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hamalt

2022/05/08 14:23 編集

あまりにもご丁寧な回答をいただきビックリしております…! コードの理解と既存コードへの組み込み&動作確認に時間がかかり、返信まで遅くなってしまいました。 問題なく停止/再開させることができました! `TimeScale`を1か0に切り替えるだけのシンプルな変更ですね…恐れ入ります。 `Stopwatch`クラスや`WaitUntil`の使い方なども勉強させていただきました。 この度は本当にありがとうございました。 今回の内容を糧に精進いたしますm(__)m
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問