Unityに関連する記事です

 この手順では、防衛用の味方キャラに攻撃範囲内にいる敵に対して一定時間ごとにダメージを与えるようにし、ダメージによって破壊される、というように順番に処理を実装します。
また攻撃範囲より敵キャラがいなくなってしまった場合には、攻撃の処理を停止する処理も合わせて実装します。

 以下の内容で順番に実装を進めていきます。


<実装動画 々況眸楼脇發謀┘ャラが侵入したら一定時間ごとに攻撃してダメージを与える>
動画ファイルへのリンク


<実装動画◆‥┘ャラが攻撃範囲外になったら攻撃を停止する>
動画ファイルへのリンク


手順12 ー味方キャラの攻撃処理と敵キャラのダメージ処理の実装ー
20.CharaController スクリプトの修正を行い、攻撃範囲内に侵入した敵キャラに一定時間ごとに攻撃する処理と、敵キャラがいなくなった場合に攻撃を停止する処理を実装する
21.EnemyController スクリプトの修正を行い、攻撃範囲内に侵入した敵キャラにダメージを与えて破壊する処理を実装する



 新しい学習内容は、以下の通りです。

 ・while 文とコルーチン・メソッドを組み合わせた処理の実装例
 ・OnTriggerExit2D メソッド
 ・Mathf.Clamp メソッド
 ・Tween.Kill メソッド



20.CharaController スクリプトの修正を行い、攻撃範囲内に侵入した敵キャラに一定時間ごとに攻撃する処理と、敵キャラがいなくなった場合に攻撃を停止する処理を実装する

1.設計


 一度に処理を実装するのではなく、防衛用のキャラ側の処理を実装し、その後、敵キャラ側の処理を実装します。
1つずつ順番に機能を追加していくことで不具合を見つけやすくします。また、前提となる処理を理解した上で先に進めることが出来ます。

 まずは防衛側の味方キャラにどのような処理が必要になるか、その場合には、どんな変数が必要で、どのように利用していけばよいかといった部分を考えて書きだしてみてください。
頭の中でのイメージを書き出すことが出来れば、それを元にプログラムのロジックを考えていくことが容易になります。
現在の CharaController スクリプトをみながら処理をイメージしてみましょう。
 
 攻撃範囲内については OnTriggerEnter2D メソッドにおいて実装済です。この処理の中で敵キャラをすぐに破壊してしまっていたため、
その部分を「一定時間ごと」に「敵を攻撃する」というロジックを考えていきます。

 一定時間ごと、というのは、簡単に考えても計測用の値と目標用の値の2つが必要になります。
値同士を比較することによって、一定時間を表現することができるようになるためです。
また一定時間を判定し、それを繰り返すことになりますので、繰り返し用の処理を考える必要もあります。

 敵を攻撃する、というのは、敵の体力を減らす、という状態になります。
現時点では敵にも体力となる値はありませんでの、まずは防衛用の味方キャラに攻撃力用の値を準備するようにします。
減らす処理自体は敵の体力が追加されてから実装することになりますので、まずは Debug.Log メソッドを活用して、攻撃を行っていることを確認しておくようにします。
この部分にあとで処理を実装するイメージです。

 まずはここまでを実装できるように、ロジックを考えてみましょう。


2.CharaController スクリプトを修正する


 設計の内容を元にロジックを考えて、それからスクリプトを修正します。
スクリプトを修正するのは、しっかりと処理のイメージが固まり、ロジックが完成してからになります。
いきなり処理を書いたり、教材通りに書けばいい、という形で進めてしまうと、後々に自分でロジックを考えることが出来なくなってしまいます。

 常に自分で処理を書く、という意識を持ちながら、処理の内容を理解しながら書いていくようにしてください。


CharaController.cs


 スクリプトを修正したらセーブを行います。


3.while 文とコルーチン・メソッドを組み合わせた処理の実装例


 while(ホワイル) 文は反復処理と呼ばれる処理です。条件を満たしている限り、繰り返し処理を行います。

 今回のケースでは条件が「isAttack の値が true の間」です。
よって、isAttack の値が false になるまではずっと繰り返されて終わることのない処理になります。


    public IEnumerator PrepareteAttack() {

        Debug.Log("攻撃準備開始");

        int timer = 0;

    // 攻撃中の間だけループ処理を繰り返す
        while (isAttack) {

            // TODO ゲームプレイ中のみ攻撃する

            timer++;
        
      // 攻撃のための待機時間が経過したら    
        if (timer > intervalAttackTime) {

        // 次の攻撃に備えて、待機時間のタイマーをリセット
                timer = 0;

        // 攻撃
                Attack();
                   
                // TODO 攻撃回数関連の処理をここに記述する

                
            }

      // 1フレーム処理を中断する(この処理を書き忘れると無限ループになり、Unity エディターが動かなくなって再起動することになります。注意!)
            yield return null;
        }
    }

 while 文内の処理は3つあり、1つは timer 変数の加算処理、もう1つは攻撃メソッドの実行処理です。
指定された値になるたび、攻撃メソッドを実行する制御を行っています。

 もう1つは yield による中断(待機・遅延)の処理です。 yield return null 1フレームだけ処理を中断します。

 そのため、この while 文による繰り返しの処理は次のような挙動になります。

 1.while 文の条件を満たしているか、確認する。満たしていない場合には、処理を終了する
 2.条件を満たしている場合には、timer 変数の値を加算する。
   その後、timer 変数の値と intervalAttackTime の値を if 文にて比較して、条件を満たしている場合には if 文内の処理(timer 変数のリセットと攻撃メソッドの呼び出し)を行う
 3.1フレーム待つ
 4.【1】の処理に戻る

 Update メソッドを利用していませんが、しっかりと一定の間隔で繰り返し攻撃を処理を実装することが出来ました。

 なお、while 文では処理を中断する処理を挟まないと繰り返しの処理が止まらなくなって、Unity のエディターが動かなくなります
こうなってしまうと再起動するしかなくなりますので、while 文は特に気を付けて作業してください


参考サイト
未確認飛行 C 様
反復処理
https://ufcpp.net/study/csharp/st_loop.html


 他にも自動的に攻撃を行うようにする処理は考えられます。色々な処理を試しておくことで処理の引き出しが広がり、
より多くの処理を実装できるようになりますので、いつも使っている処理だけではなく、新しい技術も取り入れていきましょう。


4.Chara ゲームオブジェクトの設定を行う


 Chara ゲームオブジェクトのインスペクターを確認し、新しく追加した変数に情報に設定が反映しているか確認してください。
初期値が入りますので新しく設定を変更する部分はありませんので、確認だけで問題ありません。

 isAttack 変数はスクリプト内で制御が入りますので、チェックなし(false)の状態で構いません。
また enemy 変数も同様で、OnTriggerEnter2D メソッドが実行されたタイミングで代入されますので、こちらも None のままで問題ありません。


インスペクター画像



 以上で設定は完了です。


5.ゲームを実行して動作を確認する


 いままでと同じように処理の内容を把握してから、ゲームを実行して制御できているかを確認していきます。


<実装動画>
動画ファイルへのリンク


 一定時間(約1秒前後)ごとに Debug.Log メソッドが表示されれば、繰り返し処理と攻撃処理の制御成功です。


<ケーススタディ ーAttack メソッド内の Debug.Log(“攻撃”) のログが大量に表示される場合ー>

 
 攻撃の処理が繰り返し発生し、大量の「攻撃」ログが表示される場合があります。

 この場合、スクリプト側のエラーの場合と、PC のスペックに依存している場合の2つが考えられます。

 まずはどちらに該当するのかを、すでに記述済の Debug.Log メソッドで確認し、問題の特定を行います。
 
    public IEnumerator PrepareteAttack() {

        Debug.Log("攻撃準備開始");  // ← このログ

   (省略)


 この PrepareteAttack コルーチンメソッド内にある Debug.Log("攻撃準備開始") が何回実行されているかを確認してください。

 「攻撃準備開始」というログが大量に出ている場合には、スクリプト側のエラーの可能性が高いです。
処理を書き間違えていないか、再度、教材の内容と、自分の書いたスクリプトの内容の見直しを行ってください。



 「攻撃準備開始」のログは1回しか出ていないにもかかわらず、大量の Debug.Log(“攻撃”) が表示される場合、
お使いの PC の Update メソッドを実行するフレームレート(FPS)の値が高いことが原因です。

 while 文は yield return null により、1フレームごとのループ処理を行っていますが、
この1秒間に実行するフレームレートを 60 と想定してプログラムを作成しています。
そのため、intervalAttackTime 変数には 60 という値を代入して if 文の条件式とし、
timer 変数は1秒ごとに 60 になる想定でそのタイミングで Attack メソッドを実行しています。

int timer = 0;

    // 攻撃中の間だけループ処理を繰り返す
        while (isAttack) {

            timer++;
        
      // 攻撃のための待機時間が経過したら    
        if (timer > intervalAttackTime) {  // ← ここ

        // 次の攻撃に備えて、待機時間のタイマーをリセット
                timer = 0;

        // 攻撃
                Attack();
                   
                // TODO 攻撃回数関連の処理をここに記述する

                
            }

      // 1フレーム処理を中断する(この処理を書き忘れると無限ループになり、Unity エディターが動かなくなって再起動することになります。注意!)
            yield return null;

 そのため、1秒間のフレームレートの値が高いと、timer 変数の値がすぐに intervalAttackTime(60) に到達するため、
1秒の間に何回も繰り返して if (timer > intervalAttackTime) の条件を満たすことになり、Attack メソッドが実行されているため、
Debug.Log("攻撃") のログが大量に出てしまっています。

 改善方法としては、intervalAttackTime 変数の値を変更するか、フレームレートの値を規定値に設定する方法です。
ただし、intervalAttackTime 変数の値については、相当に高い値に設定しても、変化が見られない可能性が高い(PC のスペックが高い)のでその方法はお勧めしません。
ここではフレームレートの値を規定値に設定する方法を採用してください。

 下記の記事を参考にしてスクリプトを作成し、いずれのゲームオブジェクトでも構いませんのでアタッチしてください。


   → フレームレート(FPS)の固定化設定の方法


 こうすることでフレームレートの値が固定化されるため PC の性能差に依存しない値になり、問題を解消できます。


6.問題点を修正する ーCharaController スクリプトを修正するー


 デバッグを行って処理を確認していくとわかりますが、OnTriggerEnter2D メソッドに置いて EnemyController スクリプトの情報を取得すると、
その情報をずっと保持したままになりますので、敵キャラが攻撃範囲外に出ていっても、ずっと攻撃をし続けるようになっています。

 こちらを修正することを考えます。

 OnTriggerExit2D メソッドという処理がありますので、こちらを活用しましょう。
このメソッドは侵入しているコライダーが侵入範囲外に出たときに1回だけ自動的に処理が実行されるメソッドです。
つまり、敵キャラが攻撃範囲外に出たタイミングで呼び出されますので、このメソッド内で繰り返しの処理を停止し、 EnemyController の情報を削除することが出来れば
結果として攻撃処理も停止するようになります。

 繰り返しの処理はどのようにして動いているか? 

 停止するにはどうすればいいのか?

このロジックをイメージして、処理を書いてみましょう。


CharaController.cs


 スクリプトを修正したらセーブを行います。


7.ゲームを実行して動作を確認する


 処理を修正しましたので、Chara ゲームオブジェクトのインスペクターを確認しながら処理を実行してみましょう。
どの処理が、どの部分を動かしているのかを理解しておくことがスクリプトの理解を深めることにもなります。


<実装動画>
動画ファイルへのリンク


 無事に制御できているか確認しておきましょう。


21.EnemyController スクリプトの修正を行い、攻撃範囲内に侵入した敵キャラにダメージを与えて破壊する処理を実装する

1.設計


 プログラムのロジックを考えるときは、実行したい処理をメソッドにまとめて用意し、それを必要なタイミングで呼び出すように考えます。
今回のケースであれば、呼び出したいタイミングは CharaController スクリプト側に TODO で記述してありますので、
あとはその処理を実装すればロジックが完成します。

 EnemyController スクリプト側に、CharaController スクリプト側から呼び出してもらうメソッドを実装しましょう。
メソッドには引数を設定できますので、CharaController スクリプト側に設定している attackPower の値を引数を通じて受け取ることで
EnemyController スクリプト側のメソッドに、attackPower の値が情報として届き、利用出来るようになります。

 敵の破壊処理に際してですが、敵の移動を実行している DOTween の DOPath メソッドは非同期処理で動いています。
そのため、DOPath メソッドを実行している敵キャラ自体が破壊されてしまっても、この処理自体が自動的に終了することはありません。
通常の同期処理によるプログラムと非同期処理によるプログラムにはこのような違いもあります。

 よって、敵キャラが破壊されたタイミングで適切に DOPath メソッドの処理を終了しておく必要があります。

 DOTween の処理は Tween という型によって動いています。処理の実行途中で処理を終了したい場合(あるいは一時停止したい場合など)は、
Tween 型の変数を用意しておき、その中に実行するメソッドを代入することによって、処理の制御が可能になりますので
敵の破壊処理を実装する場合、DOPath メソッドの実行処理にも修正が必要になります。


2.EnemyController スクリプトを修正する


 新しくダメージ計算用のメソッドと破壊処理用のメソッドを実装します。
 
これに伴い、Start メソッド内に処理の追加と修正があります。

 変数の宣言も新しく3つ準備しています。同じ項目同士(SerializeField属性 の変数)で並べているため、途中に差し込んでいます。

 今後の実装を見通し、TODO を残しておきます。こちらも順次、必要に応じて追記していきましょう。
自分で処理の流れを考えてみて、ここにはこんな処理が欲しいな、という部分には積極的に TODO を残しておくようにするとプログラムのロジックを考えやすくなります。


EnemyController.cs


 スクリプトを修正したらセーブをします。


3.<Mathf.Clampメソッド>


 Mathf 構造体は、Unity が用意している、数学関数の変数やメソッドをまとめてある構造体です。
通常の Math クラスと異なり、戻り値は float 型で用意されています。


参考サイト
Unity 公式スクリプト・リファレンス
Mathf



 このうち、Clamp メソッドは、「制御したい指定値を、指定した範囲内の最小値、最大値に収めてくれる(置き換えてくれる)」処理になります。

<メソッドの記法>
  制御したい指定値 = Mathf.Clamp(制御したい指定値, 最小値, 最大値);
 
 このメソッドを利用して、計算処理後の hp 変数の値を制限することが出来ます。
上記のメソッドの書式に、制御したい値を当てはめて処理を組み立ててみましょう。

  // Hp の値を減算した結果値を、最低値と最大値の範囲内に収まるようにして更新
  hp = Mathf.Clamp(hp -= amount, 0, maxHp);

 なおMathf.Clampメソッドにはオーバーロードがあり、引数の型は、float型とint型でそれぞれ利用が出来るようになっています。
hp 変数の型は int 型ですので、今回は自動的に int 型を利用しています。


参考サイト
Unity 公式スクリプトリファレンス
Mathf.Clamp


4.<Tween.Kill メソッド>


 DOTween に関連する処理になります。
Tween 型の変数を宣言することで、DOTweenの処理内容を変数に代入することが出来ます。

 DOTweenの処理は変数に代入せずとも実行できます。本来はそれだけで問題はないのですが、
Tween 型の変数に DOTween の処理を代入していることで、DOTweenの処理を一時停止させたり、再開したり、強制的に終了させることが出来るようになります。

 どのような場面で使用するかというと、無限ループの処理を終了させたり、一時停止して再開したりできます。
また、今回のように経路移動の処理の途中で敵キャラが破壊されてしまって、DOPath メソッドがすべて実行できなくなってしまうようなケースです。

 変数にDOTweenの処理を代入していない場合、動いている DOTween の処理を一時停止したり、再開したり、強制的に終了させる処理が実行できません

 DOTweenの処理を中断するには、Killメソッドを実行します。この処理を実行することで、今回であれば DOPath メソッドによる経路移動処理を終了させることが出来ます。

 private Tween tween;  // Tween 型の変数

  // Tween 型の変数には DOTween の処理を代入できる
     ↓
  // 各地点に向けて移動。今後この処理を制御するため、Tween 型の変数に DOPath メソッドの処理を代入しておく
  tween = transform.DOPath(paths, 1000 / moveSpeed).SetEase(Ease.Linear).OnWaypointChange(ChangeAnimeDirection);

 // DOTween の処理を破棄(終了)する
  tween.Kill();


5.Enemy ゲームオブジェクトの設定を行う


 EnemyController スクリプトの修正を行いましたので、Enemy ゲームオブジェクトのインスペクターを確認して設定を行います。

 maxHp 変数に最大HP の値を設定してください。デバッグ用に数値を大きくしたり、小さくしたりして確認しますので、
まずは最初は 1 にしておいて、攻撃処理→ダメージ計算処理→破壊処理が実行されるかを確認しましょう。


インスペクター画像


 
 以上で設定は完了です。


6.CharaController スクリプトを修正する


 EnemyController スクリプト側にダメージ計算用のメソッドと破壊処理のメソッドが実装できましたので、
最後に、このうちのダメージ計算用のメソッドの呼び出し処理を実装します。破壊処理はダメージ計算用のメソッド内で呼び出しがあるので、こちらからの呼び出しは不要です。

 TODO で記述されている部分になりますので、まずは自分で実装に挑戦してみてください。


CharaController.cs


 スクリプトを修正したらセーブを行います。


7.ゲームを実行して動作を確認する


 処理の流れを把握した上でゲームを実行して動作を確認していきます。

 敵の mapHp の値や防衛キャラの attackPower の値や intervalAttackTime の値を調整して
どのように処理が変わるのか、しっかりと内容を理解しておいてください。


<実装動画 々況眸楼脇發謀┘ャラが侵入したら一定時間ごとに攻撃してダメージを与える>
動画ファイルへのリンク


<実装動画◆‥┘ャラが攻撃範囲外になったら攻撃を停止する>
動画ファイルへのリンク


8.Enemy ゲームオブジェクトと PathTranSet ゲームオブジェクトをヒエラルキーから削除する


 デバッグ用の役割が終了しましたので、これらのゲームオブジェクトをヒエラルキーより削除してください。
次の手順では、Enemy ゲームオブジェクトのプレファブを利用していきます。



 以上でこの手順は終了ですが、続けて、コルーチンメソッドと while 文の組み合せた処理について解説します。


<コルーチンメソッドと while 文を組み合せた処理の詳細説明>


 今回利用している、コルーチンメソッドと while 文を組み合わせた処理について詳しく解説します。

        while (isAttack)
        {
            timer++;

            if (timer > intervalAttackTime)
            {
                timer = 0;
                Attack();

            }

            //1フレーム処理を中断する(これを書き忘れると無限ループになり、Unityエディターが動かなくなって再起動することになる。注意!)
            yield return null;
        }



 最初はコルーチンメソッドの機能から再確認をしていくと良いでしょう。

 コルーチンですが、これは通常のメソッドとは異なり、途中で一時停止し、後で再開できるメソッドです。
この一時停止と再開の機能を活用することで、時間がかかる処理を分割して実行したり、特定のタイミングを狙って処理を実行することができます。

 これを実現しているのが、yield キーワードです。これは、コルーチンメソッド内部でのみ利用できるキーワードです。
このキーワードを書くことで、処理が一時中断します。逆にいうと、コルーチンメソッド以外ではこのキーワードは書くことが出来ません



 yield return null; の部分は、コルーチンの一時停止と再開を制御するためのキーコードです。

 yield の書いてある行が実行されると、コルーチンは一時停止され、指定された時間や指定された状態になるまで待機します

 yield return null の場合の待機時間は1フレームです。これはルールとして、そのように決まっています。

 そのため、この処理を書くと、1フレームの間だけ処理を一時停止し、次のフレームが始まると、一時停止した箇所から処理が再開されます。

 また、コルーチンメソッド内では、必ず1回は yield を利用した処理を書かなくてはなりません。これは強制です。
コルーチンメソッド内に1回も yield で始まる処理がないと、「yield 使わないなら、そもそもコルーチンじゃなくてよくない?」ということで、エラーが出ます。

 このような機能を持っているのが、コルーチンメソッドです。
そして yield return null という処理自体は、コルーチンメソッド内で普段からよく利用される処理の1つです。

 while 文は while 文、コルーチンメソッドはルーチンメソッド、それぞれの機能自体をしっかりと理解しておくことが大切です。



 次に、while 文の処理を見直してみましょう。

 while 文は、条件が成立している間ずっとループを繰り返す処理です。

  while (条件)
    {
        // 何らかの処理を行う
    }

 上記のような while があった場合、条件を満たす限り、処理を繰り返します。
繰り返す、というのは、{ } ブロック内の部分を最初から最後まで1回ずつ行う、という意味です。
 
 while 文の挙動も、for 文と同じように、動きに番号を振ることで、可視化しやすくなります。

  while (  ‐魴) // ← 満たしているか確認
  {

   ◆ 抬,両魴錣鯔たしていたら】ここに来る

         // 何らかの処理を行う

    { ブロックの中の処理が終了したら、} ブロックの外には出ずに、,粒稜Г北瓩

  }

 ぁ 抬,両魴錣鯔たさなくなってから】ここに来る


 このような仕組みで動いています。が重要で、これにより、while 文は,良分に自動的に戻ります

 また while 文は条件を満たす限りは、while { } ブロックより、下の行に次に書いてある処理は動きません。

  while (条件)
    {
        // 何らかの処理を行う
    }
 
  Debug.Log("while 終了")  (上記い砲△燭詆分)

 このように while 文の下に Debug.Log メソッドを書いておくと、どのタイミングで while 文が終了したのか確認できます
なぜなら、Debug.Log("while 終了") の処理は、while 文が終了するまで動くことがないため、このログが出る = while 文が終わったと判断できるためです。

 このように、情報を整理し、適切な前提と仮説を立て、論理的な推論を行い、結論を導くプロセスを
論理的思考、ロジカルな考え方(ロジカルシンキング)といいます。

 プログラムはこの考え方がとても大切です。

 ちょっと難しいので、これを要約して置き換えると、「〜だから、〜になる」ということは、「こうなるなら、こっちはこうだな」という風にも考えられるな、
という感じで、1つずつ問題を分析・推論し、そこから結論を考えていくようにします。

 このような考えを持ちながら、処理を考えていくようにすると、結論(処理の結果・流れ)までがみえやすくなり、理解も深まります。

 例えば、この while 文と Debug であれば

「 while 文の下に Debug を書いておけばいいな。
 何故なら、ここの Debug は while 文の { } の外側にあるので、この Debug が実行されるということは、
 絶対に while 文が終了したときにしか実行されない。
 つまり、Debug のログが出るということは、while 文が正常に終了したことの証明になるし、終わったタイミングもわかることにもなる。」

 論理的に順序立てていくと、このようにして考えていくことが出来るようになります。


<yield return null; は、無限ループにならないようにするためだけに記述しているのですか?>


 コルーチンメソッド内での while 文の挙動ですが、while 文は条件が成立している間、ずっとループを続けます。
ただし while 文をコルーチン内で実行すると、コルーチンが一時停止することがなくなります
このため、コルーチンメソッド内の処理が正常に処理されなくなり、while 文のループが終了するまでゲームの処理が止まってしまい、
while 文のループ処理は無限ループ状態になるという仕組みです。

 処理の動きはこのようになります。

 1.while 文の条件が成立している間、ループを続けます。(isAttack == true)
 2.ループ内で何らかの処理を行います。
 3.yield return null; が実行されると、コルーチンが一時停止します。
 4.次のフレームが始まると、一時停止した箇所(yield return null;)から処理が再開されます。
 5.while 文の最後まで来たので、1 に戻り、再び while 文の条件をチェックし、成立していれば続けます。

 3の部分がないと、4と5の処理が動きません

 実はループの処理というのは、フレームをまたぎません。1フレーム内で、すべての繰り返しの処理を行おうとします。

 そのため、while 文もフレームをまたぐための処理(yield return null;)がないと、1フレーム内にずっと同じ処理を繰り返そうとします。
それこそ、何千、何万回と、です。そして処理負荷が高くなり、プログラムが耐え切れなくなって止まります。
これが無限ループの理由です。

 ただし、コルーチンメソッド内では yield return null のように、処理を止めてから再度動かす機能があります。
これにより、while 文のループ処理自体も、複数のフレームにまたがって処理を実行することが出来るようになりますので
1フレーム内で無理なループ処理を繰り返すことがなくなります



 以上のことから、コルーチンメソッド内で while 文を使う場合には、必ず while { } 内に、yield return null; などの、yiled の処理を含める必要があります。
この yield return null により、ループ内の処理がフレームごとに実行され、コルーチンメソッドでの正常なループ処理が動くようになります。
イメージとしては、コルーチン内では、while 文の条件チェックのために yield return null を挟まないとループから戻ってこれなくなる感じです。

 最初にお答えしたように、「無限ループにならないようにするためだけに記述している」という質問は正しい、というのは、このためです。
今回の場合も、コルーチンメソッド内で while 文を使っているので while { } 内に、yield return null; と書いています。

 コルーチンメソッド内の while 文は無限ループになってしまうので、このルールを守らないと正常に動かないよ、ということですね。

 なお、yield キーワードを利用し、中断できる処理であれば yield return null; 以外でも正常に動作します。
例えば、yield return new WaitForSecounds(1.0f); と記述すれば、1秒待機後に while 文の処理が再開されますので、正常に動作します。


<yield return null;のあと、なぜwhile文の最初の処理に飛ぶのですか?>


 while 文のループ処理を読み解くことで理解できてきます。

yield return null の処理に限らず、while 文は、{ } ブロックの最後の行までいったら、最初の条件の確認に戻ります
 
 今回の場合はたまたま yield return null;が最後になるので、そのタイミングで条件の確認に戻りましたが、
これは yield return null; があるからではなくて、while 文のループ処理自体がそのような機能になっているためです。

 仮に

while (isAttack)
{
  // TODO ゲームプレイ中のみ攻撃する
    timer++;

    if (timer > intervalAttackTime)
    {
         timer = 0;
         Attack();
         // TODO 攻撃回数関連の処理をここに記述する
    }

    //1フレーム処理を中断する(これを書き忘れると無限ループになり、Unityエディターが動かなくなって再起動することになる。注意!)
    yield return null;

    Debug.Log("while 最後"); 
}
 
 このような処理だとしたら、Debug.Log("while 最後"); 実行後、while の最初に戻り、条件のチェックを行います。



 補足として、while 文内で yield return null を書く位置は、常に while 文が実行する位置に書き
if 文などの条件分岐内には書かないようにしてください。

 条件分岐ということは、その { } には入るか入らないかが発生します。
よって、分岐内に yield キーワードの処理を書いてしまうと、yield キーワードが処理されない状況が発生してしまうことになり、その場合も無限ループに入ります。

 これはダメな例です。

while (isAttack)
{
    timer++;

    if (timer > intervalAttackTime)
    {
         timer = 0;
         Attack();
         // TODO 攻撃回数関連の処理をここに記述する

        //1フレーム処理を中断する(これを書き忘れると無限ループになり、Unityエディターが動かなくなって再起動することになる。注意!)
        yield return null;
    }


  // while 文内で必ず実行される部分に、yield return null; がない


}

 このように if 文でしか yield return null; が実行されない状態にしてしまうと、
while 文全体で見た時、while 文の常に動く(分岐に限らず必ず実行される)部分に yield return null; がないことがわかります。
これだと無限ループします。



 これからは複数の処理が合わさって処理が動いていることが多くなります。
いずれも学習されてきている基礎的な処理ではありますが、それぞれの処理の内容をしっかりと理解しておくことで
こういった複数の処理が合わさっている場合でも、同じように読み解いていくことが出来ます
 
 プログラムは基礎的な処理の組み合わせで出来ているので、基礎をしっかりと身につけていくことで
一見すると複雑に見える処理も読み解けるようになります。あせらずにいきましょう。



 以上でこの手順は終了です。

 次は 手順13 −敵キャラの自動生成処理の実装− です。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Menu



技術/知識(実装例)

2Dおはじきゲーム(発展編)

2D強制横スクロールアクション(発展編)

3Dダイビングアクション(発展編)

2Dタップシューティング(拡張編)

レースゲーム(抜粋)

2D放置ゲーム(発展編)

3Dレールガンシューティング(応用編)

3D脱出ゲーム(抜粋)

2Dリアルタイムストラテジー

2Dトップビューアドベンチャー(宴アセット使用)

3Dタップアクション(NavMeshAgent 使用)

2Dトップビューアクション(カエルの為に〜、ボコスカウォーズ風)

VideoPlayer イベント連動の実装例

VideoPlayer リスト内からムービー再生の実装例(発展)

AR 画像付きオブジェクト生成の実装例

AR リスト内から生成の実装例(発展)

private



このサイト内の作品はユニティちゃんライセンス条項の元に提供されています。

管理人/副管理人のみ編集できます