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

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

ただいまの
回答率

89.21%

各面でUniRxを使ったイベントを起こそうとすると、イベントが重複して発生する

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 346

BinaryNumber

score 13

こんにちは。初心者プログラマです。
どうしても自分で解決できなかったので、質問させてください。

質問内容

制限時間180秒、16面でクリアのゲームを作っています。
各面は、基本的に同じSceneを使っていて、30秒ずつ経つごとにUniRxを使ってイベントが起きるようにしています。
クリアしたかどうかは他のプログラムが把握しており、IsPlayingフラグを使って、ゲーム中かそうでないかを判断させています。

ここで、2面以降、前の面のタイムがクリアされていない事態が起きました。
つまり

  1. 1面を40秒でクリア
  2. 2面が始まって約20秒でイベント2が発生←これが問題
  3. 2面開始30秒後イベント1が発生←これは正常

というようなことが発生しています。

コード

UniRxを使ったイベント操作

void Start(){

  // 省略

  Observable.Timer(TimeSpan.FromSeconds(30)).Subscribe(_ => Event1());
  Observable.Timer(TimeSpan.FromSeconds(60)).Subscribe(_ => Event2());
  Observable.Timer(TimeSpan.FromSeconds(90)).Subscribe(_ => Event3());
  Observable.Timer(TimeSpan.FromSeconds(120)).Subscribe(_ => Event4());
}

private void Event1()
{
  if (IsPlaying)
  {
     GameObject hint1 = GameObject.Find("hint1");
     hint1Text = hint1.GetComponent<Text>();

    //省略

     Debug.Log("hint1!");
  }
}

//Event2以下もほぼ同様の内容なので省略

補足

最初はGameObjectの取得を、関数内ではなく、Start()内で行っていました。
しかし、そのときイベントはきちんと30秒ごとに起きていたのですが、2面以降ずっと、MissingReferenceExceptionのエラーが出ていました。
気になり調べた結果、Event1()内に書くことで、MissingReferenceExceptionのエラーが消えました。
ところが、エラーが消えると同時に、上記のような挙動が起きてしまうようになりました。
UniRxを使っているヒントのオブジェクトを破棄(Destroy)すればいいのだろうかと模索したのですが、結局行き詰まってしまいました。

 

いろいろ自分で検索して試しましたが、自力では解決できず、お力を貸していただきたく、書き込ませていただきました。
解決のために、さらに詳細なコードを出す必要がありましたら、ご指摘いただければ追記いたします。
どうか、よろしくお願いいたします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+1

恐れ入ります…
過去に少しUniRxを使った事があり、ソースを見ると、少し怪しい箇所があるので…

Observable.Timer(TimeSpan.FromSeconds(30)).Subscribe(_ => Event1());
Observable.Timer(TimeSpan.FromSeconds(60)).Subscribe(_ => Event2());
Observable.Timer(TimeSpan.FromSeconds(90)).Subscribe(_ => Event3());
Observable.Timer(TimeSpan.FromSeconds(120)).Subscribe(_ => Event4());

おそらく、上記4つのタイマーが「1面のスタート処理が走ったときに、全て走っています」

1面が「40秒」で終わったとなると、2行目の

Observable.Timer(TimeSpan.FromSeconds(60)).Subscribe(_ => Event2());

こちら、40秒経過して、2面が始まった時点でEvent2();が残り20秒で作動する状態になってる結果

>2面が始まって約20秒でイベント2が発生←これが問題

こちらの問題が生じていると思われます。

解決策…は実装方法にも寄りますが

文書を読む限り、1面は30秒経過、2面は60秒でヒントのようなものがでると予想されます。

1面が発生したときに
Observable.Timer(TimeSpan.FromSeconds(30)).Subscribe(_ => Event1()); だけを登録
1面クリア時に
Observable.Timer(TimeSpan.FromSeconds(30)).Subscribe(_ => Event1());の登録を解除

2面が発生したときに
Observable.Timer(TimeSpan.FromSeconds(60)).Subscribe(_ => Event2()); だけを登録
2面クリア時に
Observable.Timer(TimeSpan.FromSeconds(60)).Subscribe(_ => Event2());の登録を解除

のような処理を入れるとよいと思います。

あと、蛇足ですが、これ、1面で30秒経過してヒントみたいな処理画はした後、
1面で苦戦なりして60秒経過すると、2回目の30秒経過でまた同じヒント流れると思います。


すみません、また編集…というより

>クリアしたかどうかは他のプログラムが把握しており、IsPlayingフラグを使って、ゲーム中かそうでないかを判断させています。 

このフラグは残念ながら彼らには意味を成していません。

IsPlayingであなたがゲームの終了をいくら指示しても、『「Stage1」を開始した時点』で秒読みを指示した彼ら

  Observable.Timer(TimeSpan.FromSeconds(30)).Subscribe(_ => Event1());
  Observable.Timer(TimeSpan.FromSeconds(60)).Subscribe(_ => Event2());
  Observable.Timer(TimeSpan.FromSeconds(90)).Subscribe(_ => Event3());
  Observable.Timer(TimeSpan.FromSeconds(120)).Subscribe(_ => Event4());

は、「Stage1」を開始した時点で、秒読みを開始し、
あなたがIsPlayingでゲームの終了をいくら指示しようが、
彼らは『Stage1」を開始した時点』での秒読みを、愚直かつ素直に続けます。


一度購読した命令の「破棄」に関して、こちらが参考になるかもしれません…
https://qiita.com/yaegaki/items/bea845df0b011d515afb

UniRxの機能で一度命令した購読は、こちらの意図するタイミングで「破棄処理」も同時に入れると、意図せぬ場所での購読処理を防止できるかと。


これはまた、違った別の解法というより、別アプローチになりますが

1.別Scene(例:Stage001.Scene,Stage002,Scene)に分ける(わかりやすい)

2.Stage事に別クラスを作る
例:public class Stage001 { / Stage1に必要な処理や変数宣言* / } public class Stage002 { / Stage2に必要な処理*や変数宣言 / }
※)こちらはある程度プログラムの知識が必要
ここはプログラムの知識をそこまで求めなければ気にしなくていいですが
ステージ事の共通部分をStageBaseのような名前でベースクラスを作り
各々のステージのクラスにStageBaseを継承して、それを運用するほうが色々と捗ると思います

ステージのステート管理から見直すのも、一つの手ではあります。
  

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/10/18 09:37

    hermit19901127様
    早速のご指摘と解決策、ありがとうございます。
    ただ、申し訳ありません、今日はUnityの作業ができず、作業後のお返事(動いた/まだ無理、など)は明日以降になります。
    明日以降に、もしまだ行き詰るようなら、また質問させていただいてもよろしいでしょうか。
    よろしくお願いいたします。

    キャンセル

  • 2019/10/18 22:51 編集

    答えに行きつけるような解答が出せるか自信がありませんが、実行の結果に関して、「この可能性がある」「考えうる限りの答えは出しました」程度の解答なら出来るかと…

    あと、1面だけ抽出して
    >Observable.Timer(TimeSpan.FromSeconds(30)).Subscribe(_ => Event1());
    こちら

    Observable.Timer (TimeSpan.FromSeconds(30))
    .Take (1)
    .Subscribe (_ => Event1());

    と書き換えるのもありかもしれません。

    Takeはイベント発行の実行回数(だったと思うのですが間違いでしたらごめんなさい)の指定で
    上記だと、「30秒毎に起こるイベントを『1回』だけSubscribeで指定した関数で実行する」という見方になるかと思います

    キャンセル

  • 2019/10/19 13:29

    hermit19901127様
    お返事と詳細な説明が遅れて申し訳ありません。

    説明が不足しておりまして、1面でも2面でも16面でも、「30秒、60秒、90秒、120秒といった形でイベントが発生する」条件だったので、同じclassを毎回呼び出しておりました。

    まず、ご指摘いただいた

    Observable.Timer (TimeSpan.FromSeconds(30))
    .Subscribe (_ => Event1());

    の記述に問題があることを理解し、
    そして、教えていただいた参考URLの内容や、書いていただいたコード(特に".Take(1)"のあたり)を理解するため調べた結果、

    Observable.Timer(TimeSpan.FromSeconds(30))
    .TakeUntilDestroy(this).
    Subscribe(_ => Event1());

    と書き直した結果、解決にたどり着きました。

    丁寧に教えていただき、また、解決策もいろいろ考えていただき、大変助かりました。
    本当にありがとうございました。

    キャンセル

  • 2019/10/20 14:38

    TakeUntilDestroy…こんなものが…
    Binaryさんが答えに行きつけたのは幸いですが、同時に己の学の無さを痛感します…

    たしかに、「破棄処理」に関しては、説明がめんどくさいので省いたのもありますが
    TakeUntilDestroyがある事は、初耳です…

    キャンセル

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

  • ただいまの回答率 89.21%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

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