i-school - 【2D】プレイヤーに最も近い敵を見つけて、その方向に弾を発射する機能
 プレイヤーに最も近い敵を見つけて、その方向に弾を発射する機能の実装例です。
複数の敵を見つけたり、追尾する機能も付いています。



前提


 GameData クラスがあり、そこでは敵の情報と追尾対象を List で管理しています。
敵の情報の List には、敵の生成時に敵の情報が追加される仕組みです。
追尾対象の List には、プレイヤーに近い敵の情報がその都度追加され、弾がなくなると同時に List からも削除される仕組みです。

 List の型は敵のゲームオブジェクトにアタッチされている EnemyController クラスです。
GameObject 型よりも命令を出す際に GetComponent が不要となり、融通が利くためです。

 下記にサンプルを提示します。




EnemyController.cs

 <= クリックすると開きます





GameData.cs

 <= クリックすると開きます



サンプルコード


 LINQ を利用して記述しています。

 弾役のゲームオブジェクトにアタッチして利用します。
1つの弾の機能になりますので、例えば、複数の弾を生成すれば、それらが異なる敵を見つけて発射されます。


HomingBullet.cs



<Vector2.MoveTowards メソッドを利用した追尾処理>


 Update メソッド内にある下記の処理によって敵を追尾しているため、削除するか、コメントアウトすれば追尾はしなくなります。

// 追尾対象を追いかける
transform.position = Vector2.MoveTowards(transform.position, target.transform.position, bulletSpeed * Time.deltaTime);

 transform.positionは、このコードを含むゲームオブジェクトの現在の位置を表します。つまり、弾の位置です。

 Vector2.MoveTowards メソッドは、現在の位置から指定された位置に向かって移動するためのメソッドです。
第1引数に指定した対象(弾)を、第2引数に指定された位置(target.transform.position)に向かって、弾が進むときの新しい位置を計算します。

 target.transform.position は、弾が追いかける対象の位置を表します。つまり、弾はこの位置に向かって移動しようとしています。

 bulletSpeed * Time.deltaTimeは、弾の進む速さを表します。
bulletSpeed は弾の移動速度を表し、Time.deltaTime はフレーム間の時間差を考慮して移動距離を調整します。
これにより、ゲームがどれだけ早く実行されているかに関係なく、一定の速さで移動します。

 以上、この処理は弾が毎フレームごとに対象の位置に向かって一歩進むことを表しています。
弾は bulletSpeed で指定された速度で対象を追いかけます。これにより、弾は目標を追跡し、その位置に近づいていきます。


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


<LINQ による最も近い敵を見つける処理>


var sortedEnemies = GameData.instance.enemiesList.OrderBy(enemy => Vector2.Distance(transform.position, enemy.transform.position));

 この部分では、enemiesList と呼ばれるリスト内の敵キャラクターを、プレイヤーの位置からの距離が近い順に並べ替えています。

 GameData.instance.enemiesList は GameData クラスから取得した敵キャラクターのリストです。

 .OrderBy(enemy => Vector2.Distance(transform.position, enemy.transform.position)) のうち、
OrderBy メソッドは、指定された条件に従って要素を並べ替えるためのLINQメソッドです。
この場合、enemy という各敵キャラクターに対して、Vector2.Distance(transform.position, enemy.transform.position) を行います。
enemy の位置からプレイヤーの位置までの距離を計算して、その距離が小さい順に並べ替えています。

 これにより、一番近い敵が最初に来るようにソートされた sortedEnemies という新しい List が完成します。


参考サイト
MicroSoft
Enumerable.OrderBy メソッド



target = sortedEnemies.FirstOrDefault(enemy => !GameData.instance.targetList.Contains(enemy));

 この部分では、sortedEnemies リスト内から最も近い未選択の敵(target)を見つけています。

 sortedEnemies は一番近い敵が要素の最初に来るようにソートされた敵キャラクターのリストです。
.FirstOrDefault(enemy => !GameData.instance.targetList.Contains(enemy)) のうち FirstOrDefault メソッドは、
指定された条件に合致する最初の要素を返すLINQメソッドです。
ここでは、enemy という各敵キャラクターに対して、その敵がまだ選択されていない(targetList に含まれていない)場合、その敵を target に設定します。
targetList に含まれていないかどうかは、Contains メソッドを利用して判定しています。

 以上のことから、この処理の目的は、プレイヤーから最も近い未選択の敵を見つけて target に設定することです。


参考サイト
MicroSoft
Enumerable.FirstOrDefault メソッド



 上記の処理により、プレイヤーに近い敵から順番に対象先の List に入ります。

// ターゲットをリストに追加
GameData.instance.targetList.Add(target);

 そのため、2つ目の弾を発射した場合、最初の対象ではなく、次に近い敵を対象として取ります。

 もしも、複数の弾を最も近い敵に向けてだけ発射したい場合には、この List に入れる処理をなくすことで実現できます。


LINQ を利用しない場合のサンプルコード


 処理の内容はまったく同じですが、LINQ の部分を foreach を利用して実装しています。
LINQ の機能が未学習である場合には、こちらを読み解く練習から始めましょう。



HomingBullet.cs



<Vector2.Distance と Vector2.SqrMagnitude の違いについて>


 2点間の対象の距離については、上記の2つの処理を利用するケースが多いです。



参考サイト
Unity 公式スクリプト・リファレンス
Vector2.Distance
Unity 公式スクリプト・リファレンス
Vector2.sqrMagnitude



 SqrMagnitude は距離の2乗を計算しますが、Distance は距離の平方根を計算します。
平方根の計算は計算量が多いため、非常に大量のオブジェクトをソートする場合には SqrMagnitude の方がパフォーマンスが向上します。
しかし、通常のゲーム内で数個の敵をソートする場合には、パフォーマンスの差はほとんど感じられないでしょう。

 Distance は正確な距離を提供しますが、SqrMagnitude は距離の2乗を提供します。
正確さが重要な場合には Distance を使用します。たとえば今回のような、敵の間隔を厳密に比較する必要がある場合です。

 Distance は距離を直接表すため、コードが読みやすいことがあります。
SqrMagnitude の場合、距離の2乗を比較するため、コードの意図が少し分かりにくくなることがあります。

 したがって、どちらを使用するかはゲームの要件とコードのパフォーマンス要件に依存します。
通常、パフォーマンスに対する大きな懸念がない場合は、Distance を使用することが一般的です。
SqrMagnitude は計算効率を最適化したい場合(大量の計算が必要な場合)に役立つツールとして使われます。
必要に応じて、パフォーマンスが問題になる場合に Distance ではなく SqrMagnitude に切り替えることを検討してください。

 ただし、正確な距離が必要な場合や、読みやすいコードを書きたい場合には Distance を選ぶようにしましょう。