i-school - 3Dレールガンシューティング 手順6
 引き続き、プレイヤーの攻撃処理を実装します。
最後の手順では、銃を使った攻撃用のスクリプトの作成を行います。



手順6 ープレイヤーの攻撃機能の実装ー

 銃の処理のスクリプトについては、下記のサイト様を参考にしています。FPS ゲームの製作方法が丁寧にまとめられています。
Unity でゲームを作ろう 様
弾の発射処理の作り方&エフェクトの追加方法
https://unity.moon-bear.com/zombie-slayer/shoot-an...



 ・オブジェクトプール ー生成したゲームオブジェクトを破棄せずに再利用する考え方と実装方法例ー
 ・LayerMask.GetMask メソッド



1.設計


 プレイヤー側の操作周りの実装を行います。新しく3つのクラスを作成していきます。
クラス名はこの通りである必要はありません。その場合、クラスの役割に合った名前にしてください。

 〇 PlayerController   プレイヤーの情報を管理するクラス。体力や弾数などを管理し、外部のクラスに情報提供するクラス。リロード処理、敵の攻撃の当たり判定なども管理する
             カメラのゲームオブジェクト(AR Camera、あるいは Main Camera ゲームオブジェクト)にアタッチする


〇 EffectManager エフェクトの管理を行うクラス。Create Empty で新しいゲームオブジェクトを作成し、そこにアタッチする
             シングルトンで作成し、どのクラスからでもアクセスしやすい状態にする。エフェクトなどは個別のゲームオブジェクトに持たせないようにし、管理を簡便化できる


<今回実装するクラス>
 ◇RayController     画面のタップ処理(マウスの左クリック)を判定し、Physics.Raycast メソッドを活用して、弾が発射できるかを管理制御するクラス
             PlayerController から弾数の情報を提供してもらい、PlayerController の弾数の値を更新したり、リロードさせる命令をだす
             カメラの子オブジェクトとして Create Empty で新しいゲームオブジェクトを作成し、そこにアタッチする


<完成時のヒエラルキー画像>


 これらがすべて完成することにより、プレイヤーの攻撃の処理が実装されます。

 イベント側(敵やアイテムなど)の実装がまだ済んでいないため、その手前までを Debug.Log メソッドを活用して実装し、
それらの実装が終了してから、再度修正を加えて完成に近づけていくようにします。


2.RayController クラスを作成する


 画面のタップ処理(マウスの左クリック) を判定し、Physics.Raycast メソッドを活用して、弾が発射できるかを管理制御するクラスです。
PlayerController から弾数の情報を提供してもらいつつ、こちらからも PlayerController の弾数の値を更新したり、リロードさせる命令を出します。

 Physics.Raycast メソッドの説明については割愛してあります。このメソッドには引数のオーバーロードがありますので、是非調べてみてください。

 マウスクリックを押しっぱなしで弾を連続で発射できるように制御していますが、今回は、単発で発射する処理は未実装です。
切り替え用の変数は PlayerController 側に用意してありますので、そちらを利用することで実装出来ます。


RayController.cs

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



3.RayController ゲームオブジェクトを作成して、RayController クラスをアタッチし、設定を行う


 Main Camera ゲームオブジェクトの上で右クリックをしてメニューを開き、Create Empty を選択します。
Main Camera ゲームオブジェクトの子オブジェクトとして新しいゲームオブジェクトが作成されますので、名前を RayController に変更します。

 Transfrom コンポーネントを確認し、銃のゲームオブジェクトの発射口部分になるように、位置を調整します。
これは、RayController クラスが、RayController ゲームオブジェクトの位置に発射口のエフェクトを生成するためです。



 続いて、RayController クラスをアタッチし、インスペクターからアサインして情報を登録します。

 muzzleFlashScale 変数には、生成されたエフェクトのサイズ調整用の値を登録します。
ここで、エフェクトのサイズの微調整が出来ます。サイズに問題がないときはすべて 1 に設定してください。

 layerMasks 変数には、イベント用のゲームオブジェクト(敵、アイテム、障害物)に設定した Layer を登録します。
複数の Layer を想定し、配列にしてあります。Layer の番号で1つずつ登録しておきます。

 layerMasksStr 変数はゲーム内で情報が正常に代入されているかを確認するために、一時的にインスペクターに表示しています。
ここにはゲーム実行と同時に LayerMasks 変数で登録した数値が文字列に変換されて登録されますので、何も登録しないでください。
ゲーム実行時に値の代入を確認できたら SerializeField 属性のみを削除して private の情報に戻しておきます。

 PlayerController 変数には、Main Camera ゲームオブジェクトをアサインして、アタッチされている PlayerController クラスを登録します。


<インスペクター画像>



 以上で設定は完了です。


4.<オブジェクトプール ー生成したゲームオブジェクトを破棄せずに再利用する考え方と実装方法例ー>


 同じ位置から同じエフェクトを何回も生成するのでは効率がいいとは言えません。
インスタンシエイト処理は軽い処理でもありませんので、頻発するとメモリが圧迫されて処理落ちが発生しやすくなります。

 パーティクルシステムによって作られているエフェクトの場合、Play On Awake にチェックを入れておくことで
再度、SetActive メソッドで表示を行うと、自動的にエフェクトの再生を行ってくれます

 この機能を活用し、最初に1回だけエフェクトを生成して、それを変数へと代入しておきます
エフェクトの再生が終了したら SetActive メソッドを使って非表示にしておきます

 そして次回エフェクトの再生を行う際には、生成が完了している場合には SetActive メソッドを使って表示することで
もう1度エフェクトの再生を行ってくれるようになります。

 このようなサイクルを用意しておくことで、生成したゲームオブジェクトを破棄せず、
今回であれば銃の発射口に表示されるエフェクトを再利用して活用することができています。


private GameObject muzzleFlashObj;   // 生成したエフェクトの代入用


// 発射エフェクトの表示。初回のみ生成し、2回目はオンオフで切り替える
if (muzzleFlashObj == null) {
    // 発射口の位置に RayController ゲームオブジェクトを配置する
    muzzleFlashObj = Instantiate(EffectManager.instance.muzzleFlashPrefab, transform.position, transform.rotation);
    muzzleFlashObj.transform.SetParent(gameObject.transform);
    muzzleFlashObj.transform.localScale = muzzleFlashScale;

} else {
    // 生成済のエフェクトの表示(パーティクルシステムの Play On Awake にチェックが入っていると、エフェクトが再度再生される)
    muzzleFlashObj.SetActive(true);
}

// 発射
Shoot();

yield return new WaitForSeconds(playerController.shootInterval);

// エフェクト非表示
muzzleFlashObj.SetActive(false);

こういった考え方をオブジェクトプール といいます。
よく見られるのは、弾のゲームオブジェクトをある程度だけ生成したら、あとは表示 / 非表示を繰り返して再利用させる方法です。
こうすることにより、弾を破壊するのではなく非表示にしておき、生成時に非表示の弾がある場合には、生成ではなく、非表示の弾を表示して使います。
つまり、大量のインスタンスシエイト処理を止めることができるため、処理の負荷軽減が可能になります。

 オブジェクトプールについては色々な記事がありますので、考え方も含めて調べておくといいでしょう。
最近では Unity 自体にも標準機能として実装されるようになりました。

参考サイト
Yucchiy's Note 様
Unity 2021から利用できるUnity標準のオブジェクトプールについて
https://blog.yucchiy.com/2021/04/objectpool-in-uni...


5.<LayerMask.GetMask メソッド>


 引数に指定した LayerMask の文字列を int 型の値に変換してくれるメソッドです。引数には配列を指定します。

[SerializeField, Header("Ray 用のレイヤー設定")]
private int[] layerMasks;

[SerializeField]   //Debug用。確認できたら、SerializeField 属性を削除して private にしておく
private string[] layerMasksStr;


// Layer の情報を文字列に変換し、Raycast メソッドで利用しやすい情報を変数として作成しておく
layerMasksStr = new string[layerMasks.Length];
for (int i = 0; i < layerMasks.Length; i++) {
    layerMasksStr[i] = LayerMask.LayerToName(layerMasks[i]);
}


if (Physics.Raycast(ray, out hit, playerController.shootRange, LayerMask.GetMask(layerMasksStr))) {

今回の場合、Enemy を引数に指定していますので、Enemy を登録している Layer の番号である 6 の値が戻り値として取得出来ています。
これは、Physics.Raycast メソッドの第4引数の指定値が int 型で Layer を指定するように要求されているためで、それに情報を合わせるためです。

Layer



 今後イベントの種類が増えたら、LayerMasks 配列変数に値を登録することで対応可能な設計です。


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


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


 デバッグを行いやすい環境を作って、動作を確認を行います。

 準備が整ったらゲームを実行して、イベント用のゲームオブジェクトに攻撃の判定が発生するかどうかを Debug.Log メソッドを活用して確認していきます。


 カメラの回転処理が未実装であるため、イベント用のゲームオブジェクトは画面の中央部分に置いて、Debug.DrawRay メソッドで Ray が当たっているかも確認しましょう。
エフェクトが再利用されているかも合わせて確認を行います。



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

 => 次は 手順7 ー移動経路の作成と経路の管理機能ー です。