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

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

ただいまの
回答率

90.51%

  • C#

    9048questions

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

  • Unity

    5510questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

  • ゲーム開発

    212questions

Unity : Unirxで、5秒間のカウントダウンを繰り返し行えるボタンを実装したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 1,252

japomondo

score 9

5秒間のカウントダウンを繰り返し行えるボタンを実装したいと考えています。

5,4,3,2,1,0と1秒毎に減っていき、0になると5に戻り、再度ボタンをクリックするとまた5からカウントダウンが始まる、という仕様です。

そこで下記のコードを書きました。

using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UniRx;
using UniRx.Triggers;

public class PlayerControl : MonoBehaviour
{

public bool IsRead;
public Text remainingTime;
public float time = 5.0f;

void Awake ()
{
      this.UpdateAsObservable()
            .Where(_ => IsRead)
            .TakeWhile(_ => time > 0)
            .Subscribe(_ => DiscountReadingTime());
}

// ボタンを押すと呼ばれるメソッド
public void Read ()
{
      IsRead = true;
      StartCoroutine(EndReading());
}

// Readメソッドが呼ばれることでUnirxにより走るメソッド
void DiscountReadingTime ()
{
      time -= Time.deltaTime;
      remainingTime.text = ((int)time).ToString();
}

// Readメソッドから呼ばれるメソッド
IEnumerator EndReading ()
{
      yield return new WaitForSeconds(time);
      IsRead = false;

      time = 5;
      remainingTime.text = time.ToString();
}

}

すると、DiscountReadingTimeメソッドについて問題が発生しました。1回目のクリックでは100%呼ばれるのですが、2回目以降になると呼ばれたり呼ばれなかったりします。ボタンを押してもカウントダウンが始まらないのです。

インスペクタでIsReadとtimeの値を確認したところ、いずれもDiscountReadingTimeメソッドが呼ばれる条件は満たしていました。

なお、ReadメソッドとEndReadingメソッドは2回目以降も100%呼ばれます。

TakeWhileは指定した条件が真である間値をプッシュし続けると認識しているのですが、Awakeメソッドに書いたUpdateAsObservableは、条件を満たしているはずなのになぜ実行されないのでしょうか?

もしお分かりになる方がいらっしゃいましたら、ご助言いただけますと大変幸いです。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

ちょっとご確認いただきたいのですが、コードを下記のようにすると、コンソールにはどのように表示されるでしょうか?

// 省略

    void Awake()
    {
        this.UpdateAsObservable()
            .Where(_ => IsRead)
            .TakeWhile(_ => time > 0)
            .Subscribe(_ =>
                {
                    Debug.LogFormat("time: {0}", time);
                    DiscountReadingTime();
                },
                () => Debug.LogFormat("Completed! time: {0}", time));
    }

// 省略

    // Readメソッドから呼ばれるメソッド
    IEnumerator EndReading()
    {
        yield return new WaitForSeconds(time);
        Debug.Log("Reset!");
        IsRead = false;

        time = 5;
        remainingTime.text = time.ToString();
    }

// 省略

私の予想ですと、うまくいく場合では最後に「Reset!」だけが表示され、ボタンが無反応になる場合では先に「Completed! time:...」が表示されて、その後に「Reset!」が表示されるのではないでしょうか。

ちょっと調べた感じでは、TakeWhilefalseになるとそこでUpdateAsObservableのストリームは終了してしまい、以降timeIsReadを元に戻しても、ストリームがなくなっているので何も起こらない...という気がします。
想像するに、コルーチンによるリセットとTakeWhileによるストリーム終了判定が拮抗しており、先にリセットされた場合はうまくいくが、先にストリームが終了した場合は失敗するという状況ではないでしょうか?

改善案としては...

案1:
TakeWhileをやめて、time > 0でフィルタリングするだけにし、ストリームを止めないようにする。

// 省略
    void Awake()
    {
        this.UpdateAsObservable()
            .Where(_ => IsRead && time > 0)
            .Subscribe(_ => DiscountReadingTime());
    }
// 省略

案2:
Awakeは削除、EndReadingは通常のメソッドにし、Read内でストリーム購読を開始して、ストリーム完了時にEndReadingを実行する。

public class PlayerControl : MonoBehaviour
{

    public bool IsRead;
    public Text remainingTime;
    public float time = 5.0f;

    // ボタンを押すと呼ばれるメソッド
    public void Read()
    {
        if (IsRead)
        {
            return;
        }

        IsRead = true;
        this.UpdateAsObservable()
            .TakeWhile(_ => time > 0)
            .Subscribe(_ => DiscountReadingTime(), EndReading);
    }

    // Readメソッドが呼ばれることでUnirxにより走るメソッド
    void DiscountReadingTime()
    {
        time -= Time.deltaTime;
        remainingTime.text = ((int)time).ToString();
    }

    // Readメソッドから呼ばれるメソッド
    void EndReading()
    {
        IsRead = false;
        time = 5;

        if (remainingTime != null)
        {
            remainingTime.text = time.ToString();
        }
    }
}

案3:
案2をベースとしつつ、イベントストリームをUpdateAsObservableから1秒間隔のIntervalに置き換える(参考: UniRxのシンプルなサンプル その9(TimerとInterval 一定時間後に実行) - Qiita)。
timeは初期値5.0fのまま書き換えないようにし、ストリームに流れてくるプッシュ回数がtime未満の間テイクしてDiscountReadingTimeを実行する。
DiscountReadingTimeは引数としてプッシュ回数を受け取れるようにし、残り時間に換算してremainingTimeに表示する。
案2と同様、ストリーム完了時にEndReadingを実行する。

public class PlayerControl : MonoBehaviour
{

    public bool IsRead;
    public Text remainingTime;
    public float time = 5.0f;

    // ボタンを押すと呼ばれるメソッド
    public void Read()
    {
        if (IsRead)
        {
            return;
        }

        IsRead = true;
        Observable.Interval(TimeSpan.FromSeconds(1))
            .TakeWhile(i => i < time)
            .Subscribe(DiscountReadingTime, EndReading);
    }

    // Readメソッドが呼ばれることでUnirxにより走るメソッド
    void DiscountReadingTime(long eventIndex)
    {
        remainingTime.text = ((int)(time - (eventIndex + 1))).ToString();
    }

    // Readメソッドから呼ばれるメソッド
    void EndReading()
    {
        IsRead = false;

        if (remainingTime != null)
        {
            remainingTime.text = time.ToString();
        }
    }
}

ずらずらと色々書いた後ですみませんが、恥ずかしながら私はUniRxについては名前しか聞いたことがなく(かなり便利で強力だという評判は目にしていたものの、リアクティブプログラミングに馴染みがなく手出ししていなかったのです)、ご質問者さんの方がお詳しいかと思います。
もし「こんな書き方だとこんな不具合があるよ」とか「こう修正するともっと効率的だよ」とかご指摘ありましたらぜひお願いします。

UniRxは多機能で十分理解するのに時間を要しそうですが、面白そうですね。もっと積極的に取り入れてみようかと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/01/27 11:41

    またしてもご回答いただき、本当にありがとうございます。

    早速冒頭のコードを試してみたところ、うまくいく場合では最初に「Completed! time:...」、最後に「Reset!」が表示され、ボタンが無反応になる場合では「Reset!」だけが表示される、という結果でした。

    ご提案くださった案1を試させていただいたところ、何度クリックしてもカウントダウンされるようになりました。本当にありがとうございます。

    Updateメソッドを呼び出すスクリプトを可能な限り少なくすることで負荷を減らしたいと思っていたところ、UniRxを知りその便利さに感動したのですが、自分などはまだ全然理解が浅く使いこなせていないので…。
    ご提案いただいた案2、案3についても大変勉強になると思いますので、色々と試しつつ学びつつやってみます。

    キャンセル

同じタグがついた質問を見る

  • C#

    9048questions

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

  • Unity

    5510questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

  • ゲーム開発

    212questions