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

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

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

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

Unity

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

Q&A

解決済

2回答

5182閲覧

IEnumerator型に代入したコルーチンは一度しか実行出来ない件について

Hawn

総合スコア1222

C#

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

Unity

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

1グッド

2クリップ

投稿2020/07/16 09:09

編集2020/11/02 06:43

ボタンイベントを登録してコルーチンを呼び出す際、
IEnumerator型に代入して呼び出すと最初の一回しか呼び出せないのはどういった理由からでしょうか?

下記の様にボタン二つを用意して同じコルーチンを「そのまま呼び出す」、「代入して呼び出す」を試しましたが後者の方は一回しか呼び出せません。

C#

1using UnityEngine; 2using System.Collections; 3using UnityEngine.UI; 4 5public class TestCode : MonoBehaviour 6{ 7 [SerializeField] 8 Button[] button = null; 9 10 private void Start() 11 { 12 IEnumerator enumerator = CorutineTest(); 13 14 button[ 0 ].onClick.AddListener( () => StartCoroutine( CorutineTest() ) ); // 何度も呼び出せる 15 button[ 1 ].onClick.AddListener( () => StartCoroutine( enumerator ) ); // 一度しか呼び出せない 16 } 17 18 IEnumerator CorutineTest() 19 { 20 Debug.Log( "A" ); 21 yield return null; 22 } 23}

UnityAction型での関数呼び出しではこういった違いが無いのは確認しています。
UnityActiondelegateIEnumeratorinterfaceなのでこの辺りの違いから発生している事かと調べていますが詳しい記述がヒットしないので質問させて頂きました。

bboydaisukeさんの回答を受けての追記:デリゲートでの記述

bboydaisukeさんが提示してくれたサイトを参考にコルーチン用のデリゲートを用意してそれに代入して呼び出すように変更すれば問題無く複数回呼び出せるようになりました。

内部的処理の説明などまだ完璧では無いですが対処法は今後大丈夫そうです。

C#

1using UnityEngine; 2using System.Collections; 3using UnityEngine.UI; 4 5public class TestCode : MonoBehaviour 6{ 7 [SerializeField] 8 Button[] button = null; 9 10 private delegate IEnumerator CoroutineDelegate(); 11 12 private void Start() 13 { 14 CoroutineDelegate corutineDelegate = CorutineTest; 15 16 button[ 0 ].onClick.AddListener( () => StartCoroutine( CorutineTest() ) ); // 何度も呼び出せる 17 button[ 1 ].onClick.AddListener( () => StartCoroutine( corutineDelegate () ) ); // 何度も呼び出せる 18 } 19 20 IEnumerator CorutineTest() 21 { 22 Debug.Log( "A" ); 23 yield return null; 24 } 25}

katsukoさんの回答を受けての結論

コルーチンはプログラムを任意の箇所で中断・再開するしくみという認識が正確に出来ていなかったようです。

() => StartCoroutine( CorutineTest() )は毎回初期位置に戻って呼び出されるのでDebug.Logが呼ばれる。
() => StartCoroutine( enumerator )は初期位置に戻らないのでDebug.Logが呼ばれない。

上記動作の違いがある為に差異が発生していました。

Tto777👍を押しています

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

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

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

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

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

guest

回答2

0

回答を書いている間に解決しちゃったけど、せっかくなのであげておきますね。

まず、「C# IEnumerator」でググって、IEnumeratorについて理解してください。
簡単に説明すると、「MoveNextメソッドで次々に列挙して、Currentプロパティで値を取得するインターフェイス」です。

さて、yield文を含むメソッドを呼び出すと一体何を返すかというと、MoveNextメソッドを呼び出すたびにyield文まで実行し、Currentプロパティでyield文で返した値を取得するIEnumeratorインターフェイスを実装したインスタンスを返します。
試しに、以下のスクリプトを実行してみます。

cs

1using UnityEngine; 2using System.Collections; 3 4public class Test01 : MonoBehaviour { 5 IEnumerator Func() 6 { 7 Debug.Log("Point 1."); 8 yield return 1; 9 Debug.Log("Point 2."); 10 yield return 2; 11 Debug.Log("Point 3."); 12 yield return 3; 13 Debug.Log("Point 4."); 14 } 15 16 void Start() 17 { 18 Debug.Log("Point A."); 19 IEnumerator e = Func(); 20 Debug.Log("Point B."); 21 while (e.MoveNext()) { 22 Debug.LogFormat("Point C, {0}.", e.Current); 23 } 24 Debug.Log("Point D."); 25 } 26} 27

ログは、以下のように出力されます。

Point A. Point B. Point 1. Point C, 1. Point 2. Point C, 2. Point 3. Point C, 3. Point 4. Point D.

ここまで理解すると、コルーチンがどのように実装されているか、見えてくるのではないでしょうか。
nullしか返さないコルーチンであれば、StartCoroutineを呼ばなくても、以下のように実装できます。

cs

1using UnityEngine; 2using System.Collections; 3 4public class Test01 : MonoBehaviour { 5 IEnumerator Func() 6 { 7 Debug.Log("Point 1."); 8 yield return 1; 9 Debug.Log("Point 2."); 10 yield return 2; 11 Debug.Log("Point 3."); 12 yield return 3; 13 Debug.Log("Point 4."); 14 } 15 16 IEnumerator enumerator; 17 18 void Start() 19 { 20 this.enumerator = Func(); 21 } 22 23 void Update() 24 { 25 if (this.enumerator != null) { 26 if (!this.enumerator.MoveNext()) { 27 this.enumerator = null; 28 } 29 } 30 } 31} 32

以上の事を踏まえ、質問文に戻ります。

cs

1 button[ 0 ].onClick.AddListener( () => StartCoroutine( CorutineTest() ) ); // 何度も呼び出せる

この場合は、ボタンが押されるたびにIEnumeratorを作成しているので、最初から実行されます。

cs

1 IEnumerator enumerator = CorutineTest(); 2 button[ 1 ].onClick.AddListener( () => StartCoroutine( enumerator ) ); // 一度しか呼び出せない

この場合は、IEnumeratorはStartメソッドが呼ばれたときのみ作成されます。
ですので、Resetメソッドを呼ばない限り最初から実行されません。

投稿2020/07/16 11:11

編集2020/11/04 13:00
katsuko

総合スコア3538

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

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

Hawn

2020/11/02 06:25 編集

英文でもそれなりに理解出来たつもりで早い段階でBA決めさせて頂きましたが、 こちら日本語で詳細に説明して頂き十分に理解出来ました。 MoveNextを使ってテストしていくと理解早かったようです。 今までコルーチンは処理の待ち合わせで結構使っていたのですが完璧ではない理解度だったと身に沁みました。 ありがとうございました。
Zuishin

2020/11/02 06:41

Reset メソッドは使ってはいけません。あまりにも使われないので実装されない場合、またバージョンアップによって突然実装されなくなる場合があります。
Hawn

2020/11/02 15:41 編集

知見として見やすいように結論を記述して上がってしまったのかと思いますがコメントありがとうございます。 Reset メソッドに関しては質問前にも試して"NotSupportedException: Specified method is not supported."が発生するのは確認していました。 これが機能していれば「コルーチン用のデリゲートを用意」という対処しなくて良かったのですが何の為に存在しているメソッドなのでしょう?
Zuishin

2020/11/02 06:59

元々は役に立つはずだったメソッドじゃないかと思っていますが、IEnumerable があるために無用の長物と化していますね。今更削除できないので残っているだけだと思います。
Hawn

2020/11/02 07:02

誰からも必要とされていないのに放置されているだけなのですか。 悲しみしかないです。
guest

0

ベストアンサー

答えが正しいかは判断できませんが...。

Problem storing a coroutine in IEnumerator type variable - only works once?

投稿2020/07/16 10:24

bboydaisuke

総合スコア5308

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

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

Hawn

2020/07/16 11:05 編集

ありがとうございます。 質問した後そちらの記事を丁度見ていたところでした。 「同じインスタンスをStartCoroutineに渡し、Unityが最終的にMoveNextを呼び出すと、それはすでに最終状態にあるため、何も実行されません。」との記述があるので同じインスタンスを参照する後者の方では状態が戻らない事が原因みたいでした。 直接呼び出しの方は毎回生成で初期に戻るので何度でも呼び出して問題ないといった感じでしょうか。 言語化して人に説明出来るレベルでは無いですが大筋理解出来ました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問