5
4

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.

大学生限定クリエイティブコミュニティGeekSalonAdvent Calendar 2021

Day 19

【Unity】WaitUntilが正常に止まらないときに確認すること

Last updated at Posted at 2021-02-27

はじめに

ゲーム内で準備が整うまで処理を停止するときに使う WaitUntil が正常に停止してくれない!!! :cry: となったので、備忘録を兼ねて書き残しておきます。

環境

  • Windows 10
  • Unity 2019.4.5f1(64-bit)

WaitUntil とは

WaitUntil は、yield return new WaitUntil(() => bool値); のように書き、bool 値が true になるまで処理を中断します。

以下にサンプルとして、ゲームのスタート画面の例を示します。

WaitUntilSample.cs
using System.Collections;
using UnityEngine;

public class WaitUntilSample : MonoBehaviour
{
    void Start()
    {
        StartCoroutine("Sample");
    }

    IEnumerator Sample () {
        Debug.Log("Waiting...");
        yield return new WaitUntil(() => Input.GetKeyDown("space"));
        Debug.Log("Let's play!!!");
        // 以下ゲーム開始の処理
    }
}

ユーザーがスペースキーを押すまで、
yield return new WaitUntil(() => Input.GetKeyDown("space"));
の部分で処理が止まります。

ゲーム開始の処理はユーザーがスペースキーを押すまで行われず、ユーザーを待つことが出来ます。

このほかにも、ノベルゲームでのセリフ送りやロード画面など、ユーザーの入力を待機させたいときや準備が整うまで処理を停止する場合は WaitUntil を使うと比較的綺麗に実装することができます。

詰まったところ

以下のコードを書いたところ、コンソールにバグは出ないものの、偶数番目の WaitUntil は正常に作動しませんでした。

WaitUntilTest.cs
using System.Collections;
using UnityEngine;

public class WaitUntilTest : MonoBehaviour
{
    void Start()
    {
        StartCoroutine("Sample");
    }

    IEnumerator Sample () {
        Log("Waiting...");
        yield return new WaitUntil(() => Input.GetKeyDown("space"));
        Log("first");
        yield return new WaitUntil(() => Input.GetKeyDown("space"));
        Log("second");
        yield return new WaitUntil(() => Input.GetKeyDown("space"));
        Log("third");
        yield return new WaitUntil(() => Input.GetKeyDown("space"));
        Log("fourth");
        yield return new WaitUntil(() => Input.GetKeyDown("space"));
        Log("fifth");
        yield return new WaitUntil(() => Input.GetKeyDown("space"));
        Log("sixth");
    }
    void Log(string message)
    {
        Debug.Log(message + " (" + Time.time.ToString("n4") + " s)");
    }
}

mv4x0-ctrrs.gif

スペースキーを 1 回押すと 2 つの出力を受けていることが分かります。

少々話はそれますが、さらに注意深く観察すると、同時にコンソールに出ているのにも関わらず時間が異なっていることも分かります。

Debug.Log の処理はほぼ 0 秒で行われるため、それ以外の処理に時間がかかっているということです。

実は、WaitUntil の判定には最低 1 フレームかかるという仕様があり、詳しくは先人の「UnityのWaitUntilは使わない」の記述に譲りますが、この仕様が影響していると考えられます。

解決法

結論から言うと、yield return nullyield return new WaitUntil(() => Input.GetKeyDown("space")) の後ろに書くと、予想通りに動きます。

WaitUntilTest.cs
using System.Collections;
using UnityEngine;

public class WaitUntilTest : MonoBehaviour
{
    void Start()
    {
        StartCoroutine("Sample");
    }

    IEnumerator Sample () {
        Log("Waiting...");
        yield return new WaitUntil(() => Input.GetKeyDown("space"));
        yield return null;
        Log("first");
        yield return new WaitUntil(() => Input.GetKeyDown("space"));
        yield return null;
        Log("second");
        yield return new WaitUntil(() => Input.GetKeyDown("space"));
        yield return null;
        Log("third");
        yield return new WaitUntil(() => Input.GetKeyDown("space"));
        yield return null;
        Log("fourth");
        yield return new WaitUntil(() => Input.GetKeyDown("space"));
        yield return null;
        Log("fifth");
        yield return new WaitUntil(() => Input.GetKeyDown("space"));
        yield return null;
        Log("sixth");
    }
    void Log(string message)
    {
        Debug.Log(message + " (" + Time.time.ToString("n4") + " s)");
    }
}

qiita2.gif

やりました!成功です!

バグの原因究明

Input.GetKeyDown は該当のキーを押すと、1 フレームの間ずっと true となります。

コードは 1 フレームの中で実行されるので、WaitUntil の条件を満たすと、即座(フレームを跨がず)に次の WaitUntil まで到達します。

ここで、1 フレームの間 Input.GetKeyDown の値は常に true なので、2 つ目の WaitUntil に到達した時点でも true を返します。

その結果、2 つ目の WaitUntil はスペースキーを押さなくても突破されてしまうというわけでした。

ちなみに、WaitUntil は一度止まってから動くまで最低でも 1 フレームかかるので、3 つ目が一瞬で突破されることはありません。

qiita (3).png

したがって、処理を 1 フレーム分だけ中断させることができるyield return nullWaitUntil の直後に書くことで、このバグをスマートに解決することができたというわけです。

終わりに

バグと向き合ったときに既存の記事が無かったようなので自分で記事にしました。(もしあったらごめんなさい)

テンポを重視してところどころ説明を端折ってしまったため、内容は完全な初心者向けではなくなってしまったかもしれませんが、この記事で WaitUntil への理解が深まれば幸いです。

5
4
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
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?