Why not login to Qiita and try out its useful features?

We'll deliver articles that match you.

You can read useful information later.

5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

コルーチンの仕様確認

Posted at

コルーチンについての教科書的なQiita
https://qiita.com/4_mio_11/items/3030f9a39f59cd68f705
基本的にUpdateで1フレームごとの処理しかできないUnity
コルーチンを使うと、処理を1フレーム遅らせたり、1秒遅らせたりできる。
非同期処理と呼ばれるものらしい。

やりたいこと3つのタイプ

ゲームでコルーチンを使うってどういう状況?
①簡単なのは使い捨ての処理
1秒使ってアニメーションした後に、処理を終了する。
image.png
②自分が詰まってるのはメインの処理の一部として動いている処理
この場合返り値を設定する必要がある
image.png
③もっと複雑になった処理。複数の返り値を待ってメインの処理に戻る
image.png

どう実装したらいいの?

すごいかっこ悪い実装

sample.cs
    void Start(){
        StartCoroutine(Coro());  
    }
    public void NextStep() {
        Debug.Log("次の処理");
    }
    IEnumerator Coro() {
        yield return new WaitForSeconds(1.0f);//1秒待ってから
        NextStep();
    }

コルーチンの終了を待ってからその中で次の処理を動かす。
ただしオブジェクト指向的な考えだとかっこ悪い感じ。

判定の変数を用意してあげる

sample.cs
    private bool check;
    void Start(){
        check = false;
        StartCoroutine(Coro());  
    }
    private void Update() {
        if (check) {
            NextStep();
        }
    }
    public void NextStep() {
        Debug.Log("次の処理");
    }
    IEnumerator Coro() {
        yield return new WaitForSeconds(1.0f);//1秒待ってから
        check = true;
    }

このやり方も簡単ではある。
がしかし、わかりずらい。checkってなんやねん。

STARTもIEnumeratorにして、コルーチンの終了を待つ

sample.cs
    bool TF = true;
    IEnumerator Start(){
        Debug.Log("START : " + Time.time);
        IEnumerator ie = CoroutineA();
        Coroutine c = StartCoroutine(ie);//コルーチンを開始する
        Debug.Log("START Coroutine : " + Time.time);
        yield return c; //コルーチンの終了を待つ
        Debug.Log("FINISHED Coroutine : " + Time.time);
        yield return null;
    }
    private void Update() {
        if (TF) {Debug.Log("Update : " + Time.time);TF = false;}
    }
    IEnumerator CoroutineA() {
        yield return new WaitForSeconds(1.0f);//1秒待ってから         
        yield return null;
    }

yield return c;
でコルーチンの終了を待っている。よってSTARTもIEnumerator扱い。
しかし、この遅延がUpdateを遅らせる能力はない。
image.png

正直なんでyield return c;で遅延(待機?)できるのかわからないが、なんかそういうもんらしい。かしこい人教えてください。

複数コルーチンを待つ実装で躓いた

sample.cs
    IEnumerator Start() {
        //リストから全部順番に実行する
        List<IEnumerator> ie = new List<IEnumerator>();
        ie.Add(CoroutinA());
        ie.Add(CoroutinB());
        foreach (IEnumerator item in ie){
            StartCoroutine(item);
            yield return item.Current;// <===ここが重要
        }
        yield return null;
    }
    IEnumerator CoroutinA() {
        Debug.Log("CA SATRT" + Time.time);
        yield return new WaitForSeconds(1.0f);
        Debug.Log("CA FIN" + Time.time);
        yield return null;
    }
    IEnumerator CoroutinB() {
        Debug.Log("CB SATRT" + Time.time);
        yield return new WaitForSeconds(2.0f);
        Debug.Log("CB FIN" + Time.time);
    }

やりたいこと
CoroutineAをやり切った後にCoroutinBを発動させたい。

yield return item.Current; の .Current がなかった場合の結果↓

image.png

WaitForSecondsが全く機能せず、出力されている。
どうやら、IEnumaratorのreturnにはコルーチン機能とか使って時間稼がないで早く返事(return)くれという強烈な能力があるらしい。中にあるコルーチンの情報は無意味になるが、CA FINが実行されているので、中身の完遂はされ、1フレーム内でコルーチンを無視して終了させる。まあーあんまりそういう目的で使わない方がいいと思う。

yield return item.Current; の .Current がある場合の結果↓

image.png

ちゃんと思い通りの挙動をしてくれました。
Aが動いて、A終了後、Bが動いて、Bが終了する。
IEnumarator.Currentをreturnするとコルーチン終了まで待ってくれるみたいです。

複数処理を同時に初めて、すべての終了を検知する

失敗したコード

sample.cs
    IEnumerator Start() {
        List<IEnumerator> ie = new List<IEnumerator>();
        ie.Add(CoroutinA());
        ie.Add(CoroutinB());
        foreach (IEnumerator item in ie) {
            StartCoroutine(item);
        }
        foreach (IEnumerator item in ie) {
            yield return item.Current;
        }
        Debug.Log("FIN ALL " + Time.time);
    }

もう一度foreachを回して、すべて終了するまで待つやり方なのだが、ご覧の通りFIN ALLが3秒後になっている。

image.png

理由はよくわからないがitem.Currentを呼び出したタイミングで再度StartCoroutin()の時間経過を計算しているように見える。
解決策としてはIEnumeratorのCurrentの終了を待つのではなくCoroutineの終了を待つ方法を試してみる。

sample.cs
    IEnumerator Start() {
        List<IEnumerator> ie = new List<IEnumerator>();
        List<Coroutine> clist = new List<Coroutine>();
        ie.Add(CoroutinA());
        ie.Add(CoroutinB());
        foreach (IEnumerator item in ie) {
            clist.Add(StartCoroutine(item));
        }
        foreach (Coroutine c in clist) {
            yield return c;
        }
        Debug.Log("FIN ALL " + Time.time);
    }

うまくいった!
無事、すべてのコルーチン終了を確認してFIN ALLを表示することができた。

image.png

STARTをIEnumeratorにしない別のやり方

コールバックを使うとコルーチンの終了を検知できる
正確に言うと、終了を検知ではなく、終了したときに実行する処理を実装できる。
簡単でわかりやすい記事(Wixなのでいつか消えそう)

がっつり解説記事

sample.cs
using UnityEngine.Events;//これが必要だったはず
void Start(){
    StartCoroutine(Test(()=>{
        Debug.Log("FIN");//終了したときの処理を記述する
    }));
}
IEnumerator Test(System.Action callback){
    yield return new WaitForSeconds(1.0f);    
    callback();
}

記述方法にラムダ式が使われているため、ラムダ式初心者には難しいかもしれないが読める程度には理解しておくと便利
これをどう活かすか考えたとき、enumとの相性いいかな?と思う

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Qiita Conference 2025 will be held!: 4/23(wed) - 4/25(Fri)

Qiita Conference is the largest tech conference in Qiita!

Keynote Speaker

ymrl、Masanobu Naruse, Takeshi Kano, Junichi Ito, uhyo, Hiroshi Tokumaru, MinoDriven, Minorun, Hiroyuki Sakuraba, tenntenn, drken, konifar

View event details
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?