Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9337

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

$
0
0

はじめに

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

環境

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

WaitUntil とは

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

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

WaitUntilSample.cs
usingSystem.Collections;usingUnityEngine;publicclassWaitUntilSample:MonoBehaviour{voidStart(){StartCoroutine("Sample");}IEnumeratorSample(){Debug.Log("Waiting...");yieldreturnnewWaitUntil(()=>Input.GetKeyDown("space"));Debug.Log("Let's play!!!");// 以下ゲーム開始の処理}}

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

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

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

詰まったところ

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

WaitUntilTest.cs
usingSystem.Collections;usingUnityEngine;publicclassWaitUntilTest:MonoBehaviour{voidStart(){StartCoroutine("Sample");}IEnumeratorSample(){Log("Waiting...");yieldreturnnewWaitUntil(()=>Input.GetKeyDown("space"));Log("first");yieldreturnnewWaitUntil(()=>Input.GetKeyDown("space"));Log("second");yieldreturnnewWaitUntil(()=>Input.GetKeyDown("space"));Log("third");yieldreturnnewWaitUntil(()=>Input.GetKeyDown("space"));Log("fourth");yieldreturnnewWaitUntil(()=>Input.GetKeyDown("space"));Log("fifth");yieldreturnnewWaitUntil(()=>Input.GetKeyDown("space"));Log("sixth");}voidLog(stringmessage){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
usingSystem.Collections;usingUnityEngine;publicclassWaitUntilTest:MonoBehaviour{voidStart(){StartCoroutine("Sample");}IEnumeratorSample(){Log("Waiting...");yieldreturnnewWaitUntil(()=>Input.GetKeyDown("space"));yieldreturnnull;Log("first");yieldreturnnewWaitUntil(()=>Input.GetKeyDown("space"));yieldreturnnull;Log("second");yieldreturnnewWaitUntil(()=>Input.GetKeyDown("space"));yieldreturnnull;Log("third");yieldreturnnewWaitUntil(()=>Input.GetKeyDown("space"));yieldreturnnull;Log("fourth");yieldreturnnewWaitUntil(()=>Input.GetKeyDown("space"));yieldreturnnull;Log("fifth");yieldreturnnewWaitUntil(()=>Input.GetKeyDown("space"));yieldreturnnull;Log("sixth");}voidLog(stringmessage){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への理解が深まれば幸いです。


Viewing all articles
Browse latest Browse all 9337

Latest Images

Trending Articles