Unityのアクションゲームで敵が一斉に攻撃をしてこないようにする

今回はUnityのアクションゲーム等で主人公が敵に囲まれた時に一斉に攻撃をされ続けるという状態を回避し、主人公を攻撃するのは一体だけにする機能を作成していきます。

敵に取り付けたスクリプトで主人公を発見次第追いかけるようにし、一定の距離になったら主人公を攻撃するようにしているとします。

同じ敵のプレハブをゲーム上に複数配置している場合は他の敵の状況にかかわらず主人公と一定の距離になったら攻撃を始めます。

このままの状態だと主人公はタコ殴りされてしまい脱出するのが不可能です。

↑のような状態で一斉に敵が攻撃をしています。

そこで他の敵が攻撃している時は攻撃をやめ、おとなしくその場で待つようにさせます!(-_-)

↑のように他の敵が攻撃している場合はおとなしく見守っています。(´Д`)

ゾンビさん礼儀正しいですね。( 一一)

スポンサーリンク

一体だけが主人公を攻撃する機能

それでは機能を作成していきましょう。

敵のAnimatorController

敵のAnimatorControllerを作成していきます。

敵一体だけが攻撃してくる敵のAnimatorController

↑のような遷移を作成します。

アニメーションパラメータにはfloat型のSpeedとTrigger型のAttackを作成します。

Idle→WalkはHas Exit Timeのチェックを外し条件にはSpeedがGreaderで0.1
Walk→IdleはHas Exit Timeのチェックを外し条件にはSpeedがLessで0.1
Any State→AttackはHas Exit Timeのチェックを外しSettingsのCan Transition To Selfのチェックを外し、条件にAttackがトリガーされた時を設定します。

敵のスクリプト

まずはタコ殴りをする敵キャラクターを作成しておきます。

敵の移動スクリプト

敵はナビゲーション機能を使って地面を移動するようにします。

ナビゲーション機能については

Unityのナビゲーション機能を使って敵キャラクターを移動させます。ナビゲーション機能はあらかじめBakeされたフィールドを移動出来る機能です。

の辺りを参照してみてください。

敵にはNavMeshAgentを取り付け、地面はNavMeshのベイクがされているとします。

敵は主人公を追いかけている状態の時で一定の距離に近づいたら攻撃状態へと移行します。

敵個人は攻撃した後に一定の期間フリーズ状態になった後、何もしていない状態へと遷移しています。

個人では一定のフリーズ時間は設けてありますが、他の敵の状況は把握していません。

主人公検知スクリプト

次に敵の子要素に空のゲームオブジェクトを作成し名前をSearchAreaとしSphere Colliderを取り付けIs Triggerのチェックを入れ主人公の検知を行います。

SearchAreaゲームオブジェクトにOnlyOneSearchCharaスクリプトを作成し取り付けます。

OnlyOneSearchCharaスクリプトでは主人公キャラクターを検知し、自身(敵)キャラクターの状態がAttack、Chase、Freeze状態でなければChase状態にして追いかけさせます。

主人公キャラクターにはPlayerタグを設定しておきます。

これで敵キャラクターが主人公を攻撃する部分が出来ました。

敵管理スクリプトの作成

他の敵が主人公を攻撃しているかどうかは別のスクリプトで他の敵が攻撃中という情報を管理しておく必要があります。

敵が攻撃をする時に他の敵が攻撃しているかを敵の管理スクリプトで調べ誰も攻撃していなければ攻撃状態へ遷移するという風にします。

ヒエラルキー上に空のゲームオブジェクトを作成し名前をEnemyManagerとします。

EnemyManagerのインスペクタのTagにEnemyManagerタグを作成し設定しておきます。

EnemyManagerにはEnemyManagerスクリプトを作成し取り付けます。

↑のように敵が一体でも攻撃していればisOtherEnemyAttackにtrueを入れ、攻撃が終わればSetAttackメソッドを呼び出してisOtherEnemyAttackをfalseにします。

敵の移動スクリプトに処理を追加

敵の移動スクリプトOnlyOneAttackEnemyスクリプトに処理を追加していきます。

新しい敵の状態OtherEnemyAttackFreeze状態を作成します。

この状態は他の敵が攻撃中だった時に自身は攻撃状態ではなくこの状態にし一定時間待ちます。

次はフィールドの追加です。

defaultOtherAttackFreezeTimeは初期の攻撃待ち時間で、他の敵が攻撃している間はずっとOtherEnemyAttackFreeze状態にするのでその時にotherAttackFreezeTimeを減らして待ち時間を減らします。

Wait状態になったら待ち時間を元に戻すのでdefaultOtherAttackFreezeTimeをotherAttackFreezeTimeに入れます。

enemyManagerは先ほど作ったEnemyManagerスクリプトをStartメソッドで探して入れます。

FindWithTagでEnemyManagerタグが設定されたゲームオブジェクトを探しEnemyManagerスクリプトを取得しています。

EnemyState.Chase状態の時で主人公と一定距離になったらEnemyManagerのIsOtherEnemyAttackメソッドで他の敵が攻撃中かどうかを調べ、EnemyManagerのSetAttackでisOtherEnemyAttackをtrueにし、自身を攻撃状態にします。

他の敵が攻撃中であればEnemyState.OtherEnemyAttackFreeze状態にします。

otherAttackFreezeTimeの待ち時間を超えたらEnemyState.Chase状態にしキャラクターを追いかける状態へと移行します。

次にSetStateメソッドに処理を追加します。

Attack状態の時はotherAttackFreezeTimeを初期値に戻します。

Chase状態の時はOtherEnemyAttackFreeze状態からChaseに移行させる際にキャラクターのTransformを渡さないので引数として受け取ったplayerがnullであれば主人公のTransformとエージェントの目的地の設定は行いません。

OtherEnemyAttackFreeze状態の時はotherAttackFreezeTimeから0.1減らし徐々に待ち時間を減らして同じ敵が何度も攻撃しないようにします。

主人公検知スクリプトに処理を追加

OtherEnemyAttackFreeze状態を追加したので、主人公検知スクリプトOnlyOneSearchCharaの条件に追加します。

EnemyManagerのisOtherEnemyAttackをfalseにしなければいけない

ここまでの処理でEnemyManagerのisOtherEnemyAttackをtrueにする処理は作りましたが、falseにする処理がないので敵のどれかが主人公を1回攻撃したら付いては来るものの攻撃を一切してこなくなります。

isOtherEnemyAttackをfalseにする処理をいれなければいけませんが、攻撃している敵が攻撃を終わった時にfalseにしなければいけないのでアニメーションイベントを使ってアニメーションの終了を検知すれば良さそうです。

ですがアニメーションイベントを追加するのは面倒ですし、その他諸々の理由もありまして今回はビヘイビアを使う事にします。

敵のAnimatorControllerのAttack状態を選択しインスペクタからAdd Behaviourボタンを押します。

名前をOnlyOneEndAttackBehaviourとします。

OnStateExitはその状態を抜ける時に呼ばれるメソッドなのでそこでEnemyManagerスクリプトのSetAttackメソッドを呼び出しisOtherEnemyAttackをfalseにします。

敵のAttack状態のインスペクタは

敵のAttack状態のインスペクタの設定

これで敵の攻撃が終われば他の敵も攻撃が可能となります。

これで機能が完成しました。

終わりに

他の敵が攻撃中はその敵は攻撃終了待ちになりますが、通常のFreeze状態にすると先ほど攻撃した敵が再度攻撃してしまうという事も起きるので多少考慮したスクリプトにしました(同一の敵が連続で攻撃する事もある)。

他のやり方としてはEnemyManagerスクリプトで敵の攻撃する順番待ちのリストを保持しておき、時間経過で攻撃させるというのも出来るかもしれません。

やり方は色々ありそうですね。

今回の機能では敵のどれか一体が攻撃を開始するとisOtherEnemyAttackをtrueにし、どれか一体が攻撃を終了するとisOtherEnemyAttackをfalseにしています。

その為、今どの敵が攻撃中でどの敵が攻撃を終了したか?といった事までは把握させていません。

今回の仕様だと敵一体しか攻撃出来ないので、isOtherEnemyAttackがtrueの時はその敵しか攻撃していないので問題はありません。

ですが攻撃の順番をEnemyManagerスクリプトで管理していたり、同時に2体までは攻撃を可能とする場合は個々の攻撃を解除するという風に作らなくてはいけないかもしれません。

スポンサーリンク

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

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