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

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

新規登録して質問してみよう
ただいま回答率
85.47%
C#

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

Unity

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

Q&A

解決済

1回答

2777閲覧

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

BinaryNumber

総合スコア13

C#

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

Unity

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

0グッド

0クリップ

投稿2019/10/17 07:55

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

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

ここで、2面以降、前の面のタイムがクリアされていない事態が起きました。
つまり
0. 1面を40秒でクリア
0. 2面が始まって約20秒でイベント2が発生←これが問題
0. 2面開始30秒後イベント1が発生←これは正常

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

##コード
UniRxを使ったイベント操作

C#

1void Start(){ 2 3 // 省略 4 5 Observable.Timer(TimeSpan.FromSeconds(30)).Subscribe(_ => Event1()); 6 Observable.Timer(TimeSpan.FromSeconds(60)).Subscribe(_ => Event2()); 7 Observable.Timer(TimeSpan.FromSeconds(90)).Subscribe(_ => Event3()); 8 Observable.Timer(TimeSpan.FromSeconds(120)).Subscribe(_ => Event4()); 9} 10 11private void Event1() 12{ 13 if (IsPlaying) 14 { 15 GameObject hint1 = GameObject.Find("hint1"); 16 hint1Text = hint1.GetComponent<Text>(); 17 18 //省略 19 20 Debug.Log("hint1!"); 21 } 22} 23 24//Event2以下もほぼ同様の内容なので省略 25

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

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

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

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

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

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

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

guest

回答1

0

ベストアンサー

恐れ入ります…
過去に少し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」を開始した時点』で秒読みを指示した彼ら

C#

1 Observable.Timer(TimeSpan.FromSeconds(30)).Subscribe(_ => Event1()); 2 Observable.Timer(TimeSpan.FromSeconds(60)).Subscribe(_ => Event2()); 3 Observable.Timer(TimeSpan.FromSeconds(90)).Subscribe(_ => Event3()); 4 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/17 13:48

編集2019/10/17 15:47
hermit19901127

総合スコア368

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

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

BinaryNumber

2019/10/18 00:37

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

2019/10/18 13:54 編集

答えに行きつけるような解答が出せるか自信がありませんが、実行の結果に関して、「この可能性がある」「考えうる限りの答えは出しました」程度の解答なら出来るかと… あと、1面だけ抽出して >Observable.Timer(TimeSpan.FromSeconds(30)).Subscribe(_ => Event1()); こちら Observable.Timer (TimeSpan.FromSeconds(30)) .Take (1) .Subscribe (_ => Event1()); と書き換えるのもありかもしれません。 Takeはイベント発行の実行回数(だったと思うのですが間違いでしたらごめんなさい)の指定で 上記だと、「30秒毎に起こるイベントを『1回』だけSubscribeで指定した関数で実行する」という見方になるかと思います
BinaryNumber

2019/10/19 04: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()); と書き直した結果、解決にたどり着きました。 丁寧に教えていただき、また、解決策もいろいろ考えていただき、大変助かりました。 本当にありがとうございました。
hermit19901127

2019/10/20 05:38

TakeUntilDestroy…こんなものが… Binaryさんが答えに行きつけたのは幸いですが、同時に己の学の無さを痛感します… たしかに、「破棄処理」に関しては、説明がめんどくさいので省いたのもありますが TakeUntilDestroyがある事は、初耳です…
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.47%

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

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

質問する

関連した質問