Unityに関連する記事です

 バレット選択ボタンの BulletData の設定に基づいて、バレットの種類に応じて、発射するバレットの性能を自動的に変更する処理を実装します。

<実装動画 。格向、5方向>
動画ファイルへのリンク

<実装動画◆.廛譽ぅ筺爾剖瓩ぅ┘優漾爾鯆蛭、貫通>
動画ファイルへのリンク


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

発展28 −バレットの種類による性能変更の追加−
55.PlayerController スクリプトを修正し、バレットの種類に合わせて、1方向、3方向、5方向の弾道で生成する処理を追加する
56.EnemyController スクリプトと Bullet スクリプトを修正し、貫通するバレット、自動追尾するバレットの処理を追加する



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

 ・switch 文の実装例
 ・配列の活用方法の実装例
 ・GameObject.FindGameObjectsWithTag メソッド
 ・Vector3.MoveTowards メソッド
 ・Vector3.sqrMagnitude 変数



55.PlayerController スクリプトを修正し、バレットの種類に合わせて、1方向、3方向、5方向の弾道で生成する処理を追加する

1.設計


 バレットのデータにはバレットの種類の登録があります。この情報を活用して、生成するバレットの種類を自動的に分岐する処理を実装します。
バレットの生成処理は PlayerController スクリプトにおいて実装されていますので、こちらの処理を修正していきます。

 バレットの種類を分岐条件とし、バレットのデータに合わせて生成するバレットの内容を変化させるようにします。
分岐条件には switch 文を利用しましょう。条件式には現在使用しているバレットの情報を利用します。
この情報がどのスクリプトで管理されているか、確認してください。


2.PlayeyController スクリプトを修正して、バレットの種類に応じたバレットを生成する処理を追加する


 バレットを生成する GenerateBullet メソッドの引数と処理、、および、このメソッドで利用するプレファブ用の変数の型を修正します。
いままでは GameObject 型で生成して、その後、Bullet クラスを取得して ShotBullet メソッドを実行していましたが、
以前学習した自作クラス(ここでは Bullet クラス)で生成して、そのままメソッドを実行できる処理に変更しましょう。

 そのためには、プレファブの型を GameObject 型ではなく、Bullet 型に変更する必要がありますので、そちらも変更します。
これは Instantiate メソッドの戻り値を活用している処理になりますので、しっかりと復習しておいてください。



 またバレットを生成する前に、現在使用しているバレットのデータからバレットの種類を条件に分岐する生成準備のメソッドを用意します。
このメソッドでバレットの種類を特定し、それから、バレットを生成するメソッドを実行するように処理の流れを変更します。
このような設計にしておくことで、生成するメソッドは1つあれば、それを利用していくことが可能になります。

 switch 文による分岐内で3方向と5方向にバレットを生成する処理には for 文を活用しています。
バレットを生成する処理を繰り返すたびに発射する方向の座標を少しずつずらすことで、扇状に発射できるようにしています。
これは、ブロック崩しのゲームなどでも利用している処理です。

 残る2つのバレットの種類は1つだけバレットを生成する、いままでと同じ処理になります。
switch 文では同じ内容を処理する場合、case を続けて記述することによって同じ処理を実行できます。


PlayerController.cs

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


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


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


 ゲームを実行する前に、始めからバレットのコストをすべて支払えるようにするため GameData ゲームオブジェクトのインスペクターより、
TotalExp の値を 300 などの大きな数値に変更してください。すべてのバレット選択ボタンの動作を確認することが出来ます。
デバッグを行う際には、こういった機能を利用することでスムースなデバッグが可能になります。

 バレットの種類が、Player_NormalPlayer_Blaze については、いままでと同じように、タップした方向に1つだけバレットが生成されます。
Player_3ways_PiercingPlayer_5ways_Normal はそれぞれ、タップした方向に対して扇状に3つ、5つのバレットが生成されます。
バレットのデータを確認しながらデバッグを行ってください。


<3方向>



<5方向>



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


 続いては、Player_Blaze にエネミーの位置を自動的に設定して発射する機能と、
Player_3ways_Piercing に、エネミーを貫通していく機能を、それぞれ追加します。


56.EnemyController スクリプトと Bullet スクリプトを修正し、貫通するバレット、自動追尾するバレットの処理を追加する

1.エネミーを貫通するバレットの設計


 この手順ではエネミーを貫通するバレットと、エネミーを自動追尾するバレットの処理を、それぞれ追加します。

 貫通するバレットの実装方法は色々な方法があります。
今回はエネミー側、つまり、EnemyController スクリプトでぶつかってきたバレットの種類を判定して、
特定のバレットのみを破壊するように処理を変更します。今回は、Normal と名前についている種類のバレットのみを破壊します。
他には、バレットの Tag の設定を動的に変更して、それをエネミー側で判定して破壊するバレットの分岐を行う、という方法もあります。

 現在はすべてのバレットを破壊してしまっていますので、まずはこの部分を削除しましょう。そうすることで上記の分岐を利用できる状態になります。
具体的には、初期バレットと、5方向のバレットのみが貫通しないバレットになり、残る2つはエネミーを貫通するバレットになります。

 EnemyController スクリプトではすでに、OnTriggerEnter2D メソッドにおいて Bullet スクリプトの情報を
TryGetComponent メソッドを利用して取得し活用していますので、この情報をさらに活用していきましょう。

 Bullet スクリプトにある BulletData の情報は public 変数で管理されていますので、
外部のスクリプト(このケースであれば、EnemyController スクリプト)でも利用できる情報になっています。
BulletData の情報があれば、バレットの種類、そして、バレットの攻撃力の値も利用できます。


2.EnemyController スクリプトを修正し、貫通するバレットの処理を追加する


 設計に基づいて、処理の修正場所を特定して、処理を追加したり、削除したりしてください。
どんなロジックになっていればいいかを考えて、処理の流れをみながら実装を行うことを心がけてみてください。


EnemyController.cs


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


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


 スクリプトを修正して処理を見直したらゲームを実行して動作をします。
バレットの種類が Player_BlazePlayer_3ways_Piercing のバレットを発射すると、エネミーにぶつかっても破壊されず貫通するバレットを発射します
そのため、エネミーの後方にいる別のエネミーにもバレットがぶつかるようになります。


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


 続けて、エネミーを自動追尾するバレットを実装していきます。


4.エネミーを自動追尾するバレットの設計


 バレットがエネミーを自動追尾するための設計ですが、まずはどういった条件で自動追尾するのか考える必要があります。
今回はプレイヤーの位置から見て最も近いエネミーの位置を自動追尾の対象とすることを条件を設定します。

 考えないとならないのは、最も近いエネミーの位置情報をどのように取得するか、です。
エネミーは常に動いていますし、バレットを発射するときに、何体のエネミーが画面内にいるのか、それも毎回変化する情報になります。

 こういったケースでは、その都度、つまり、バレットを発射するたびに画面内にいるすべてのエネミーの位置情報を取得するようにしましょう。
そうすれば、その時点でゲーム内にいるエネミーの数と、そのエネミーごとの位置情報を最新の状態で取得出来ます。

 この位置情報を元に、それぞれのエネミーの位置情報を比較し、最もプレイヤーに近い座標情報を持っているエネミーを「最も近い」エネミーとして判定します。
そしてその位置に向かってバレットを発射するようにすれば、バレットを発射するたびに、「最も近い」エネミーを判定して自動的に追尾するバレットを実装出来ます。

 なお、自動追尾という名称ですが、バレットがずっとエネミーを追いかける、という挙動ではありません
今回の追尾機能は、画面のどの位置をプレイヤーがタップしても、最も近いエネミーを判定して、そちらにバレットを発射する処理のことを「自動追尾」としています。



 ゲーム内にいるエネミーをすべて取得する方法ですが、エネミーのゲームオブジェクトには Enemy という Tag の設定情報がありますので
この情報を活用する GameObject.FindGameObejctsTag メソッドを実行して取得を行うようにします。(Objects と複数形になっている部分に注目してください)

 このメソッドは戻り値として、指定した Tag を持つゲームオブジェクトの情報を、GameObject 型の配列として戻してくれる処理になります。
そのため、左辺に GameObject 型の配列変数を用意しておくことで、その時点でゲーム内に存在している Enemy の Tag を持つゲームオブジェクトの情報を配列として取得することが可能です。

<実装例>
  // ゲーム内にいる Enemy の Tag を持つゲームオブジェクトをすべて取得して配列として戻す
  GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");

 この配列に入っているエネミーの情報ですが、配列内の順番は毎回バラバラです。
Enemy の Tag がついている部分でのみ取得しているので、何かを基準に並べて取得を行っている訳ではありません

 この処理を実行すればすべてのエネミーの情報自体は取得できますので、まずは最初に、比較の基準となる位置情報を1つ、変数を用意して設定します。
あくまでも基準値なので、要素番号が 0 の情報を毎回設定するようにすればいいでしょう。

 あとは、この位置情報を1つずつ順番に取り出して、基準値となっている位置情報をより小さいかどうか(プレイヤー側に近いか)比較していけば、
処理が終了するときには、基準値として設定した情報は、プレイヤーに対して「最も近い」エネミーの位置情報になります。



 ここまで取得できれば、あとはこの位置情報を利用してバレットを発射するようにします。
ただし、バレットの発射には、「プレイヤーの位置情報」と「エネミーの位置情報」の差分を算出しないと「方向」の情報がないため、
Rigidbody2D コンポーネントの AddForce メソッドは実行できません。

 Bullet スクリプトには「プレイヤーの位置情報」は情報として存在しませんので、改めてその情報を取得するか、あるいは別の方法でバレットを移動させる必要があります。

 今回はプレイヤーの位置情報は取得せず、追尾用のバレットに限り、AddForce メソッドではなく
Vector3 型の扱える MoveTowards メソッドを Update メソッドで実行し、バレットをエネミーの位置に向かって徐々に移動させる処理で実装を行います。

 Update メソッドも追尾する対象がいる場合(追尾用のバレット)のみ実行するように処理を制御しましょう。
そうすれば追尾用のバレット以外のバレットでは Update メソッドが実行されることを停止することができます。
(制御しないと、追尾用以外のバレットも影響を受けてしまい、正常に通常の発射処理ができなくなります。)


5.Bullet スクリプトを修正して、バレットの種類に応じて自動追尾するバレットの処理を追加する


 設計に基づいて、処理を実装しましょう。

 宣言フィールドには、追尾用のバレットのためにエネミーの位置情報を代入するための変数と、
追尾用の対象がいるかどうかの判定を行うための変数をそれぞれ用意しておきます。
それぞれどのような型が適切であるか、処理のロジックを考えながら検討してみてください。


Bullet.cs

 <= クリックしたら開きます



6.<GameObject.FindGameObjectsWithTag メソッド>


 引数で指定した名前の Tag の設定されている複数のゲームオブジェクトを取得し、戻り値を利用して配列として戻してくれる処理です。

  // 発射時にゲーム内にいるエネミーの情報を取得
  GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");

参考サイト
Unity 公式スクリプトリファレンス
GameObject.FindGameObjectsWithTag
https://docs.unity3d.com/ja/current/ScriptReferenc...
丸の内テック 様
【Unity】スクリプトでオブジェクトを見つける
https://marunouchi-tech.i-studio.co.jp/2266/


7.<Vector3.MoveTowards メソッド>


 第1引数の値を起点、第2引数の値を目標点、第3引数の値を1フレームの移動速度(移動距離)として、目標点まで移動を行います。
第1引数の値をフレームごとに更新を行うことにより、目標点までの移動距離が変わっても、第3引数に指定した移動速度で直線移動を行ってくれる処理になります。
また、第2引数の値以上には進むことはありません。

<要所のみ抜き出し>
private void Update() {
 
    // 1フレームごとに座標を更新
    Vector3 currentPos = transform.position;

    // バレットの移動
    transform.position = Vector3.MoveTowards(currentPos, targetPos, Time.deltaTime / 10 * bulletData.bulletSpeed);
}


参考サイト
Unity 公式スクリプトリファレンス
Vector3.MoveTowards
https://docs.unity3d.com/jp/current/ScriptReferenc...
ロバメモ - 素人のUnity覚書と奮闘記 様
移動距離が変わっても、指定した速度で等速直線移動させたい
https://robamemo.hatenablog.com/entry/2018/02/14/1...


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


 Player_Blaze のバレットを発射すると、画面のどの位置をタップしても、自動的にプレイヤーに近いエネミーをターゲットとしてバレットを発射します


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


 なお、Player_Blaze のバレットも貫通するようになっているため、エネミーの位置に留まり、時間が来るまで破壊されなくなっています。

 この辺りは自由にスクリプトを修正して調整してください。追尾する対象をプレイヤーに近いエネミーではなく、遠いエネミーにしてもいいですし、
貫通しないようにしてもいいでしょう。あるいは、そういった種類のバレットを増やしていってもいいと思います。
 

9.<応用> 処理のリファクタリングを行う


 Bullet スクリプト内の敵を追従する弾の処理において、最も近い敵の位置情報を比較して決定する処理があります。

<現在の処理>
 // 現在のエネミーの位置と nearPos の比較をして、画面の下(プレイヤー)に近いものを 
  if (nearPos.x > pos.x && nearPos.y > pos.y) {

    // nearPos として更新する
      nearPos = pos;
  }

 if 文にて、2つの敵の位置(nearPos と pos)をそれぞれ x と y とで確認して判定を行っていますが、
この比較の方法ですが x の値のみを必ず最初に比較するため、本来であれば近くにいるはずの敵との判定が正しく行えず、処理にズレが生じることがあります。

 そのため、if 文内で2回の比較判定を行う方法から、1回の比較判定を行う方法にリファクタリングします。

 その際、2つの座標(x, y)を1つの値として計算する方法を考えます。

<修正後の処理>
 // 現在のエネミーの位置と比較して、画面の下に近いものを nearPos として更新する
 if (Vector3.Scale(nearPos, new(1, 1, 0)).normalized.sqrMagnitude > Vector3.Scale(pos, new(1, 1, 0)).normalized.sqrMagnitude) {
     nearPos = pos;
 }
}

 Vector2 型や Vector3 型には Scale メソッドが用意されているので、まずはこちらを利用して、z の値を 0 に補正し、x と y だけの値にします。
その後、normalized 変数を利用して、正規化処理を行います。
この処理は、PlayerController スクリプト内でも利用している方法ですので、復習を兼ねて見直してみてください。

 最後に同じく Vector3 型に用意されている sqrMagnitude 変数を利用してベクトルの 2 乗の長さの値を算出します。
この計算結果の値は float 型になりますので、これを比較したい敵同士で算出することで、
if 文の比較判定を2回行う方式から、float 型 > float 型を使った1回だけの比較判定文に変更することができます。

 このような比較処理に修正することで、確実性の高い比較を行うことが出来るようになります。


参考サイト
Unity 公式スクリプトリファレンス
Vector3.sqrMagnitude
https://docs.unity3d.com/ja/current/ScriptReferenc...

 
 なお、Vector2/3 型には sqrMagnitude 変数の他、magnitude 変数も用意されています。
こちらはベクトルの長さ(ベクトルの (x *x+y* y+z* z) の平方根(ルート)の長さ)を計算して返します。

 平方根の計算は計算処理が多くなり、計算までの時間がかかりますので、今回のように単純に距離の遠近を計算したい場合には
平方根の値ではなく sqrMagnitude 変数(ベクトルの 2 乗の長さ)の値を利用した比較処理を検討しましょう。

参考サイト
Unity 公式スクリプトリファレンス
Vector3.magnitude
https://docs.unity3d.com/ja/current/ScriptReferenc...


10.<チャレンジ> 敵の弾を追尾しないように修正を行う


 この部分は自分で考えてチャレンジします。

 現在、防衛拠点の当たり判定の処理の関係上、敵と「敵の弾」の両方に Enemy のタグが設定されています。
そのため、追尾する弾の処理を確認していただくとわかるように、追尾対象として「敵の弾」も敵と認識して追尾する状態になっています。

 この処理を修正し、プレイヤーの発射する追尾弾については敵のみを追尾し、「敵の弾」は追尾しないようにする処理を考えて、実装にチャレンジしてみてください。
 
 どのようにして「追尾する対象」を見つけて設定しているか、を確認し、それを修正する方法を考えてみましょう。

 例えば、敵の弾には Enemy ではないタグ(EnemyBullet) のよう設定に変えて、防衛拠点との当たり判定の基準を増やす、などです。



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

 次は 発展29 −ゲームクリア演出の追加  です。

コメントをかく


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

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

Menu



技術/知識(実装例)

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

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

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

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

レースゲーム(抜粋)

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

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

3D脱出ゲーム(抜粋)

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

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

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

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

VideoPlayer イベント連動の実装例

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

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

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

private



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

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