Unityでバイオハザード風ガンシューティングの機能を作ってみる

今回からUnityを使ってバイオハザード風ガンシューティングの機能を作っていきたいと思います。

あくまで風なので、バイオハザード2と同じバイオハザード4と同じ、というわけにはいきません。

キャラクターが銃を装備していたら、何か特定のキーを押している間は銃を構え、どこを狙っているかわかりやすい表示(レーザーポインタ)をして、敵にレーザーポインタが当たっていて銃を撃つキーを押せば敵にダメージを与えます。

また、次回以降に銃をかまえている間は上半身を少し動かし狙う角度の調整も出来るようにします。
なんだか盛りだくさんで嫌になりそうですが・・・(^_^;)

今回は銃を装備させ、銃を構えるようにしてみます。

スポンサーリンク

銃の3Dオブジェクトの準備とキャラクターに装備させる

銃はAsset Storeで準備しましょう。

3D モデル→小道具→武器→銃火器→[PBR] Makarov- Free Edition

をインポートさせていただきました。

バイオハザード0

キャラクターの右手の子要素にCreate Emptyで空オブジェクトを作り、名前をEquipとしています。
その子要素に銃を持たせます。

バイオ1

上のような感じに置きます。

バイオ2

Scaleを調整し、位置と角度を変更します。

Scaleは本来は銃のモデルのインスペクタのScale Factorの値を変更してサイズ調整をしたいところですが、モデル内部の部品でScale調整がされており、Scale Factorを使うと銃がバラバラになってしまう為、今回はScaleを調整しました。

銃の親要素であるEquipのTransformのScaleのX、Y、Zが1である事を必ず確認してください。

ここがそれ以外の値であると子要素である銃のTransformはそこからの相対値になるので、位置や角度を変更するとオブジェクトがゆがみます。

また銃をもっと正しく手の位置、角度と合わせたい時は

Unityでキャラクターの手の位置に正確に武器を持たせるように調整します。なんとなく持たせる位置を設定していた人には朗報かも!?

も参考にしてください。

武器のステータス記憶用のスクリプトWeaponStatusを追加し、銃で殴るという場合も考えてCapsuleColliderを追加しチェックを外しておきます。

主人公の打撃攻撃の作成は

Unityで主人公キャラが剣を振って敵キャラを攻撃出来るようにする機能を作成します

を参考にしてください。

バイオ3

回転をさせる場合は上の部分をクリックして操作すると視覚的にわかりやすいです。

これで銃のインスタンスのチェックを外せばとりあえず装備の切り替えで銃まで切り替わるようになります。
(今まで全部の機能を作って頂いた方限定ですが・・・・)

銃を構えるアニメーションの準備と設定

次に銃を撃つ時のアニメーションを用意してください。

AssetStoreでは見つけられませんでした(無料で)。ちゃんと探せばあると思いますが・・・。

アニメーションの作り方は

Unityで使用するキャラクター用のアニメーションをBlenderを使って作成していきます。作成方法を学べば自分のゲームに自前のアニメーションを使うことが出来ます。

を参考にして作成してみてください。

それではアニメーターコントローラーに構える状態を作ります。

主人公のアニメーターコントローラーにWaitShotを作成する

まずBool型のWaitShotというアニメーションパラメータを作成します。

上のようにWaitShotというステートを作り、Idle、Walk、Damageから遷移を繋げ、WaitShotがtrueになったらWaitShotステートに遷移するように条件を加えます。

WaitShotステートからはIdleに遷移を繋げ、条件にWaitShotがfalseになった時に設定します。

それぞれの条件ではHas Exit Timeのチェックを外しておきます。

これでアニメーションの流れが出来たので、スクリプトでアニメーション操作の処理を足していきます。

銃を構えるアニメーションに遷移させるスクリプトを作成する

構えるアニメーションにする為に押すボタンは「Fire2」に設定されているボタンにします。

Windowsパソコンの場合はデフォルトで右クリックになっています。

右クリックを離した時点で構えをやめるようにします。

これらのボタンの動作の処理はMoveスクリプトでやっている(ご自分のキャラクター操作スクリプト内に銃を構える処理を加えてください)のでそちらに処理を加えていきます。

MyState.Normal状態の時は移動を行い、その中でFire1ボタンを押したら通常の打撃攻撃、銃を装備している時でFire2ボタンを押したら銃を構えるようにします。

MyState.WaitShot状態の時は銃を構えた状態なのでその中で、マウスの右ボタンを離した時はノーマル状態へと遷移させます。

マウスの右ボタンを離していない時でFire1ボタンを押したら銃を撃った時にする為、銃を撃つ処理をします。

shotに保存しているShotスクリプトの関数処理を多く記述していますが、これらは後で作成します。

Shotスクリプトのメソッドは、

SetAbleLightは銃を発砲した時の光を点け、
SetDisableLightは銃を発砲した時の光を消し、
SetAbleRaserPointerは銃のレーザーポインターを表示し、
SetDisableRaserPointerは銃のレーザーポインターを消し、
PlaySEは銃の発砲音を鳴らし、
JudgeShotは弾が敵に当たったかどうかを判断するメソッドです。

さきほど紹介したスクリプトは以前の記事で作成したスクリプトを元にしているので、この記事単体ではわからない部分もあると思いますが、
キャラクター移動の条件だったり、キャラクターが銃を構える時の条件を記述しているだけです。

MoveのSetStateメソッドを変更します。

銃のレーザーポインタの作成と銃を発砲した時の光

とりあえずShotスクリプトは後回しにして、次はレーザーポインタと銃を撃った時の光の準備をします。

バイオ5

銃の子要素にMuzzle(銃口)をCubeで作りサイズを調整して合わせます。Z方向青矢印が銃弾の飛んでいく先とします。

見た目はいらないのでMeshRendererのチェックを外し、BoxColliderもチェックを外しておきます。
空オブジェクトにしなかったのは位置を合わせる時にやりやすいからです。

バイオハザード20

MuzzleのZ方向の青矢印を銃弾の飛ばす位置として合わす時に、上の画像のように赤い四角の部分をクリックし、Local表示にして合わせてください。

Local表示にするとそのゲームオブジェクトの向きでX、Y、Zの軸の矢印が表示されるようになります。

MuzzleにAdd ComponetでLineRendererとLight、AudioSourceを追加します。
AudioSourceには発砲時に鳴らす銃声を設定し、PlayOnAwakeのチェックは外してください。

AudioSourceについては

Unityで敵キャラクターを出現させた時に効果音を鳴らして、出現した事をわかるようにしていきます。

を参照してください。

LineRendererは設定した点に線を引く為のコンポーネントです。

Muzzleの部分から銃口が向いている先の部分まで軌跡を表示することでレーザーポインタの表現が出来ます。

Lightは発砲した時の光を表現します。

バイオ6

設定は上のような感じにしました。
Lightは光の発生点から広がるPointにしました。

LineRendererに設定しているGunsRayというマテリアルを作成します。

ProjectsタブのAssetsフォルダで右クリックしCreateからMaterialを選びます。

GunsRayのインスペクタでシェーダ―をParticle/Additiveに変更します。

Tint Colorを変更します。

レーザーポインタ用のシェーダ―を変更する

LineRendererにマテリアルを設定し、コンポーネントのチェックを外したらレーザーポインタの準備が完了です。
Lightもチェックを外してください。

敵にレーザーポインタが当たった時にわかりやすくする為、シューティングポイントを表示します。
それをシーン上にSphereとして作成します。

シューティングポイント用のゲームオブジェクトを作成

Sphereの名前をShotPointという名前に変更し、マテリアルを設定します。

マテリアルはレーザーポインタのマテリアルを作った時と同様の方法で作成します。

出来たらShotPointにマテリアルを設定します。
ShotPointは最初は見えなくしておきたいので、MeshRendererのチェックを外しておきます。

このShotPointは常にシーン上にあり、敵にレーザーポインタが当たるとそこに移動し、
MeshRendererのチェックをいれるという感じで使用します。

ShotPointはプレハブ化し敵にレーザーポインタが当たった時にインスタンス化するという方法もありますが、
毎回インスタンス化するのは処理に時間がかかりそうなので表示のオン・オフで対応しました。

銃を構えた時の処理スクリプトShotの作成

それでは銃を構えた時の処理を実行するスクリプトShotを作成していきます。

Shotスクリプトは銃自体に設定する事も出来ますが、今回は主人公キャラクター自身に設定する事にします。

フィールド宣言部とStartメソッド

まずはフィールド宣言

いきなり盛りだくさん・・・・(+_+)

rayRangeはレイを飛ばす距離。
equipはインスペクタで武器の親のゲームオブジェクトのTransformを指定します。
raserPointer、audioSource、shotLightは武器が銃に変わった時に銃に設定しているコンポーネントを設定します。
lightLimitは発砲の光を表示している時間。
lightElapsedTimeは銃を発砲してからの経過時間。
moveは主人公キャラ操作スクリプト。
shotPointは銃のシューティングポイント。
myStatusは主人公のステータススクリプト。
hitFlagはレーザーポインターが何かに当たっているかどうか。
nearPointはレーザーポインターが当たった一番近いゲームオブジェクトまでの距離。
distanceはレーザーポインターが当たった距離を入れるフィールド。
muzzleは銃の子要素のMuzzleを設定するフィールド。

になります。

Startメソッド内コンポーネントの取得と初期設定です。

コンポーネントの有効・無効と設定処理

Shotスクリプトの簡単な部分から記述していきます。

これらはレーザーポインタの表示・非表示
発砲時の光の表示・非表示
音を鳴らす

という処理です。

銃を撃ったタイミングでこれらの関数を呼び出します。

これらは主人公操作スクリプトMoveで呼び出していたメソッドですね。

SetComponentメソッドは武器を切り替えるスクリプトChangeEquipスクリプトから呼び出すメソッドで、武器の子要素のコンポーネントを取得し、発射口であるMuzzleの設定もしています。

レーザーポインタを表示させる処理

次にレーザーポインタの表示処理を加えます。

銃を構えた状態の時にレーザーポインターの処理を行います。

LineRendererでは複数点を指定して線を引きますので、まずは最初の地点を指定します。

で最初の地点を指定します。

第1引数が0で最初の地点、1で次の地点となります。
この数を増やしていくと点を繋ぐ線が出来ます。

Rayはレーザー光線のようなもので位置と向きを指定します。

Muzzleの位置からMuzzleの前方に向かってレイを作成します。

MuzzleのZの位置を発射させる向きにしたのはmuzzle.forwardを使う為です。

次はレイが指定したレイヤーを持つゲームオブジェクトと接触したかどうか調べる部分を見ていきましょう。

Physics.RaycastはレイをrayRangeの長さ飛ばし、Fieldレイヤーが設定されたゲームオブジェクトに接触したら条件がtrueになります。

さらにhitに接触したゲームオブジェクトの情報が入り、hit.pointで接触した位置を取得する事が出来ます。

Fieldレイヤーとの接触の後にEnemyレイヤーを指定したものとの判定を行いますが、この場合のレイの距離は武器の射程距離にします。

こうすることで、武器の射程距離に満たない場合は敵にレイが届きません。

敵が死んだ状態でなければShotPoint(シューティングポイント)を表示し、hitFlagをtrueにし、敵に当たった事にします。

distanceには何も接触していない時はfloat.maxValueでfloat値の最大値が入り、Fieldレイヤーと接触していればそのゲームオブジェクトの距離が入っています。

Enemyレイヤーと接触した距離とdistanceの距離を比べ、Enemyレイヤーのゲームオブジェクトとの距離の方が短かった時はそちらにシューティングポイントを表示します。

シューティングポイントをhit.pointにするとコライダの位置にシューティングポイントが表示されますが、敵にかぶって見えづらい事もあります。

今後カメラをキャラクターの子要素に配置し、ラジコン操作で動かせるようにした場合はシューティングポイントを少し主人公キャラクターよりに表示した方が見やすいかもしれません。

シューティングポイントを主人公側に表示するには

↑のようにhit.pointからtransform.forward(主人公キャラクターの前方)の反対側に少し補正値を加えます。

この場合カメラがキャラクターの後ろにあればいいですが、ない場合はtransform.forwardの代わりにCamera.main.transform.forward(この場合はメインカメラ)を使ってカメラの方向を使うといいかもしれません。

hitFlagがtrueならば何らかのゲームオブジェクトにレイが当たっていたので、レーザーポインターの到達点を設定します。

ゲームオブジェクトに当たっていなければシューティングポイントをオフにし、到達点をrayRange先の位置に設定します。

発砲した時の光が一定時間経過したらSetDisableLightメソッドを呼んで光を無効にしています。

弾が当たったかどうかの処理

弾が当たったかどうかを判断するJudgeShotメソッドを作成します。

JudgeShotメソッドでもFieldレイヤーとEnemyレイヤーでEnemyレイヤーが近かったらダメージを与えるようにしてます。

TakeDamageで渡すダメージ値はMyStatusのGetShotPowerメソッドを呼んで得られた値にします。

MyStatusにGetShotPowerメソッドを定義します。

敵キャラクターにレイヤーを設定する

ここでレイヤーについて見ていきます。

LayerMask.GetMask(“レイヤーの名前”)

でレイヤーの取得が出来ます。レイヤーはオブジェクトのインスペクタで設定が出来ます。

敵キャラクターのレイヤーをEnemyに設定します。
(レイヤーの設定はご自由に)

レイヤーがない場合は作成して追加してください。

バイオ10

レイヤーを変更する時に上のようなウインドウが出る事があります。

これは子要素のレイヤーも変更するかどうか聞かれています。
子要素のレイヤーを変更してしまうと不具合になる場合はNo, This Object Onlyをクリックしてください。

例えば敵キャラクターの子要素に主人公キャラを検知するサーチエリアを設定していた場合、
Yes, Change Childreをクリックして子要素のレイヤーもEnemyにしてしまった場合、銃を撃って当たり判定をする時にサーチエリアの範囲も含めてしまうことになります。

なので、敵キャラクターの場合は子要素のレイヤーは変更しないようNo, This Object Onlyを選んでください。

万が一間違えて変更してしまっても子要素のゲームオブジェクトを選択しレイヤーを変更してあげれば大丈夫です。
ゲームオブジェクトを作成した時はDefaultレイヤーに設定されているので、特にレイヤーを設定する必要がないものはDefaultレイヤーに戻すといいと思います。

Enemyスクリプトの修正

敵のHPが0以下になった時にDead状態へと遷移させていますが、Dead状態になったら他の処理をしないように変更します。

まずはUpdateメソッドの最初でEnemyState.Dead状態であればreturnで処理をしないようにします。

次はSetStateメソッドです。

こちらも死んだ状態であればreturnで処理をさせません。

最後のDeadメソッドです。

Damage状態からDeadへ行く時に剣が2度ぶつかりDeadアニメーションからDamageアニメーションへと遷移してしまう事があるので、ResetTriggerでDamageトリガーをキャンセルします。

武器の位置、角度、サイズをWeaponStatusスクリプトに移す

武器の切り替えはChangeEquipスクリプトで行っていましたが、そこでは直にそれぞれの武器の位置や角度、サイズを設定していましたが、これをWeaponStatusに保存し、そこからデータを取って来るようにします。

WeaponStatusに

というフィールドを宣言し、武器のプレハブでそれぞれの値を設定します。

WeaponStatusにインスタンス化した時の位置や角度を保持する

ChangeEquipでは

↑のようにweaponStatusのゲッターでインスタンス化した時の位置、角度、サイズを取得し、それを設定します。

レーザーポインタの移動が少し遅れる!?

次の記事の

Unityのゲームで初期のバイオハザード風に上半身の角度を変え、照準を合わせるような機能を作成していきます。

の機能を搭載すると、
主人公が体を動かして照準を定める時にレーザーポインタが銃より少し遅れて移動しているように見えます。

バイオ18

上の画像のように銃の向きとレーザーポインタの出ている位置が少しズレています。
これは体の向きを変更するUpdateとレーザーポインタの点の位置を指定するUpdateのタイミングのせいで起きてると思われます。

そんなに問題があるわけではないですが、見た目的におかしいので修正します。
体の向きを変更するUpdateが終わった後にレーザーポインタの点の位置を指定すればいいので、Updateの部分をLateUpdateに変更します。

LateUpdateはUpdateが終了した後に実行されるので、体の向きが確定した後にレーザーポインタの位置を指定するようになり、不具合がなくなります。

バイオ19

これで銃を構えたらレーザーポインタを表示した時の処理、敵にレーザーポインタが当たった時の処理、発砲した時の処理が完成しました。

シューティング機能が出来たが、当たり判定個所が大雑把

細かい所を作りこめばなかなかいい感じに出来上がると思います。結構感動ものです!!

ですが・・・、現状では銃を構えた状態でその場で銃が向いている方向にしか打てません。

銃を構えた状態でも歩けるようにする事も出来ますので(そういう風に作れば)そういう作りにしていろいろな所を狙えるようにするのもいいかもしれません。

また当たっている部分が敵キャラのコライダの接触位置なので空間上になっています。
CharacterColliderの大雑把なコライダで判定している為、そうなってしまいます。

それは次回以降、部位に当たり判定個所(コライダ)を作成し、より細かく当たり判定が出来るようにしたいと思います。

銃を構えた状態で歩いて狙いを定めるのも魅力ですが、バイオハザード4やバイオハザード5あたりの構えた状態で狙いを定めるというのを次回から作ってみようと思います。

スポンサーリンク

記事をシェアして頂ける方はこちら

フォローして頂くとやる気が出ます

コメント

  1. 緑虫 より:

    かめさん
    ゾンビのAIを改良したいのですが 今のままの場合プレイヤーが壁の奥にいる場合
    ゾンビはその壁に向かってずっと歩く形になってるのですが、回り込んだり 壁をジャンプしたり、壁を壊したり したいのですが 何かいい案とかありますか?

    • 主人公を単純に追いかけさせる場合は、間に障害物があると壁に衝突して追いかけてしまうので、そういうのを避けて追いかけさせるのはなかなか難しそうですね。

      ナビゲーションを使うとあらかじめベイクした道を移動してくれるので、この問題は簡単に解決します(NavMeshAgentを大量の敵に適用すると処理速度の問題が出る可能性はあり)。

      https://gametukurikata.com/category/navigation

      壁をジャンプするのも以前記事に書いたオフメッシュリンクを使った処理で出来るかもしれません。

      壁を壊わすのは単純にゲームオブジェクトに敵がぶつかったら消したり、倒したりさせればいいような気がします。

      ナビゲーションを使わない場合に障害物を回り込ませるのは結構大変そうです。

      地面にいくつか移動ポイントを作っておきます(障害物の横の道等に)。

      敵から前方にレイを飛ばして、障害物にBlockなどのタグを設定して、それを検知し障害物があるのがわかったらその近くにある移動ポイントを目的地にして移動させる感じで出来るかもしれませんが、

      主人公が障害物の左側から後ろに回り込み右側に進んだとしたら、敵は障害物ごしの主人公の位置に進もうとすると思うので、そこでレイで障害物を検知し近くの移動ポイントに移動させます。

      この近くの移動ポイントをどうやって計算するのかも難しそうですね、主人公が障害物の左側から入って右側のあたりまで移動してしまった場合、敵は左側から入ったのを知っているので左側から追いかけさせるのが普通だと思いますが、敵と移動ポイントで主人公との距離が右側の方が近かった場合に右側付近の移動ポイントに移動してしまう可能性があります。

      これは難しそうなので・・・、別の方法を考えました。

      コルーチン等を使ってキャラクターの移動位置を一定時間毎に記録しておき(List等を使うといいかも)、敵の目的地にその記録してある最初の位置を指定します。

      その位置にきたらListから記録をPopして捨て、また最初の位置を目的地に移動します。

      このようにすれば主人公が進んだ位置を追いかけるようには出来るかもしれません。

      なにぶん実装してないのでどうなるかはわかりませんが・・・・(^_^;)

      壁をジャンプするというのは障害物の辺りにコライダで敵を検知し、その位置にきたらジャンプさせるようにすれば出来るかもしれません。

      ジャンプの位置を合わせるのはターゲットマッチングを使うといいかもしれません。

      https://gametukurikata.com/animationanimator/applyrootmotion

      とか一人用フリーフォールの記事でも使ってます。

      https://gametukurikata.com/program/freefall

      なんだかんだでやっぱりナビゲーションを使うのが一番簡単だと思います。

  2. 緑虫 より:

    かめさん ありがとう!
    アニメーションをprivateに変更したらすべてがうまくいきました
    ありがとうございます

  3. 緑虫 より:

    メールをした者です
    銃のスクリプトです

    using UnityEngine;

    public class Weapon : MonoBehaviour {

    [Header(“銃ダメージ”)]
    public float Damage = 100f;

    [Header(“サウンド”)]
    public AudioSource shotsound;
    public AudioSource aimsound;
    public AudioSource outofammo;

    [Header(“アニメーション”)]
    public Animation am;
    public AnimationClip undrawA;
    public AnimationClip fireA;
    public AnimationClip reloadA;

    [Header(“銃弾”)]
    public int totalAmmo = 120;
    public int clipSize = 30;
    int ammo;
    public int cooldown = 11;

    public Weapon.fireMode mode;
    int _cooldown = 0;

    [Header(“アタッチメント”)]
    public Camera cam;
    public ParticleSystem muzzleFlash;
    public GameObject hit;

    [Header(“クロスヘアー”)]
    public float crossHairSize;

    [Header(“狙撃”)]
    public Vector3[] aimPoints;

    public static Weapon instance;

    public enum fireMode
    {
    AUTOMATIC,
    SEMI_AUTOMATIC
    }

    void OnEnable()
    {
    instance = this;

    UIManager.instance.UpdateAmmo (ammo);
    UIManager.instance.UpdateTotalAmmo (totalAmmo);
    }

    void Start()
    {
    ammo = clipSize;
    InvokeRepeating (“UpdateCooldown”, 0.01f, 0.01f);
    AudioSource[] audioSources = GetComponents();
    shotsound = audioSources[0];
    aimsound = audioSources[1];
    outofammo = audioSources[2];
    }

    void UpdateCooldown()
    {
    if (_cooldown > 0)
    _cooldown–;
    }

    void reload()
    {
    if (ammo == clipSize || totalAmmo == 0)
    return;

    am.CrossFade (reloadA.name);

    if(totalAmmo >= (clipSize – ammo))
    {
    totalAmmo -= (clipSize – ammo);
    ammo += (clipSize – ammo);
    }
    else
    {
    ammo += totalAmmo;
    totalAmmo = 0;
    }
    //画面上の表記銃弾数を変更する
    UIManager.instance.UpdateAmmo (ammo);
    UIManager.instance.UpdateTotalAmmo (totalAmmo);
    }

    void fire()
    {
    if (ammo <= 0 || Input.GetKey (KeyCode.LeftShift))
    return;

    am.Stop ();
    am.Play (fireA.name);

    RaycastHit _hit;
    Ray ray = cam.ScreenPointToRay (new Vector3 (Screen.width / 2 + Random.Range (-UIManager.instance.calculateCrossHair () / 1.4f, UIManager.instance.calculateCrossHair () / 1.4f), Screen.height / 2 + Random.Range (-UIManager.instance.calculateCrossHair () / 1.4f, UIManager.instance.calculateCrossHair () / 1.7f), 0f));

    if(Physics.Raycast (ray, out _hit, 5000))
    {
    _hit.transform.SendMessage("ApplyDamage",Damage,SendMessageOptions.DontRequireReceiver);
    Destroy(GameObject.Instantiate (hit, _hit.point, Quaternion.Euler (_hit.normal)), 5f);
    muzzleFlash.Play ();
    }

    _cooldown = cooldown;

    ammo -= 1;
    UIManager.instance.UpdateAmmo (ammo);
    }

    void Update()
    {
    if (Input.GetKey (KeyCode.LeftShift))
    return;
    Debug.Log (ammo + " " + clipSize + " " + totalAmmo);

    if (Input.GetMouseButtonDown(1))
    Aiming.instance.Aim(aimPoints);

    if (mode == Weapon.fireMode.AUTOMATIC && Input.GetMouseButton (0) && _cooldown = 1) {
    fire ();
    shotsound.PlayOneShot (shotsound.clip);
    }
    }
    else if(mode == Weapon.fireMode.SEMI_AUTOMATIC && Input.GetMouseButtonDown (0) && _cooldown = 1) {
    fire ();
    shotsound.PlayOneShot (shotsound.clip);
    }
    }

    if(!am.IsPlaying (fireA.name) && ((undrawA != null && !am.IsPlaying (undrawA.name)) || undrawA == null) && !am.IsPlaying (reloadA.name) && Input.GetKeyDown (KeyCode.R))
    {
    reload ();
    }
    }
    }

    • メールでお伝えしたとおり、ある敵を撃った時の他の敵のHPを確認し、他の敵のApplyDamageが実行されているかどうかを確認してください。

      全部の敵にEntityAiスクリプトが設定されている場合は、スクリプト中にDebug.Logを使ってなんらかの文字列をコンソールに表示して、攻撃を当てた時に他の敵のApplyDamageが実行されているかを確認してもいいと思います。

      とりあえず関係なさそうな部分を排除して実行してみましたが、問題は出ませんでした。

      Weaponにはキャラクターの移動スクリプトを追加し、マウスの左ボタンで攻撃にしました(ランダム値の処理はとりあえず省きました)。

      Updateメソッドでレイを飛ばし視覚的にわかるようにしてます。

      当たり判定のレイヤー部分は主人公とカメラを親子関係にしなかった為、主人公にPlayerタグを設定し~でマスクを反転し排除してます。

      EntityAiの方ではApplyDamageのifでhp < 0となっていた部分をhp <= 0にしました。

      確認の為の簡略化の為、Dieメソッドで敵のゲームオブジェクトを削除してます。

      またplayerの設定の確認の為の簡略化の為、キャラクターにPlayerタグを設定しFindWithTagで取得しています。

      SendMessageメソッドはそのゲームオブジェクトに設定されているスクリプト全部のApplyDamageメソッドを呼び出すので、他のスクリプトでもApplyDamageメソッドがあるとそれも呼ばれてしまいます。

      なのでSendMessageを使うよりも

      とした方が意味不明なバグを発生させないかもしれません。

      と思ったんですが・・・、よくみたらAnimatorのアクセス修飾子がstaticで宣言されてますね・・・・。

      staticで宣言すると全シーン内でAnimatorを共有するので、Startメソッドで自身のAnimatorを設定しても、敵それぞれのEntityAiのStartで再度それを設定し直してます。

      Animatorを共有しちゃってるので、他の敵のAnimatorControllerを操作した場合、それ以外の敵も敵が倒れるアニメーションになったり、歩いたりと共有してる可能性があります。

      static Animator animの部分を

      に書き換えてみてはどうでしょう。