7.Harming Enemies
今回は、エネミーを倒す処理:エネミー側のHPや死亡判定、アニメ、プレイヤーの射撃などを実装していく。動画の時間が長めなので必定、今回の記事も長くなりました。
元動画はコチラ。
https://unity3d.com/jp/learn/tutorials/projects/survival-shooter/harming-enemies?playlist=17144
EnemyHealthの実装
いつものようにEnemyHealthスクリプトをZonBunnyにアタッチしたら中身を確認していく。
変数
startingHealth:Enemyの初期HP
currentHealth:Enemyの現在Hp
sinkSpeed:Enemy死亡時、床に沈んでいくスピード
scoreValue:Enemyを倒したときに獲得できるスコア
deathClip:Enemy死亡時に再生されるSE
anim:アニメーターを格納
enemyAudio:deathClipを格納するためのオーディオリソース
hitParticle:Enemyに攻撃が当たった時に生成するパーティクル(=ヒットエフェクト)
capsuleCollider:死亡時にEnemyにアタッチしているColliderのTriggerをOnにするため、Colliderを格納する
isDead:Enemyの死亡判定に使う
isSinking:Enemyの沈んでる判定に使う
Awakeメソッド
もはやお約束。各変数にComponentを格納していく。新しく登場するGetComponentInChildren<>()は、子オブジェクトからComponentを探して、Inspector上で一番上にあるものを返す。と、言うことは子オブジェクトに同じComponentが複数アタッチされているときは要注意である。
あとは、currentHealthに初期HPの値をぶち込んでおく。
Updateメソッド
isSinkingがTrueであれば、Enemyオブジェクトを床下のほうへ移動させる(沈んでいるように見せる)。なお、もはや物理は関係ないのでFixedUpdateに書く必要はない。
なお、isSinkingメソッドは、後述のStartSinkingメソッドの中でTrueにされる。
TakeDamageメソッド
Enemyがダメージを受けたとき、に実行するメソッド。
ここで真似したいのは、ド頭でisDeadフラグをチェックし、すでにフラグがたっていればメソッドをそこで終えているところ。これによってif文の構造をシンプルにできているんやね、スマート。
さて、もしまだisDeadが立っていない=Enemyが生きているのであれば、やられSEを再生し、Healthを減らす。減らす量は実行元であるPlayerShootingクラスのShootメソッド(後述)からintの引数amountとして渡す。さらにヒットエフェクトのパーティクルを再生する。パーティクルを生成するPositionは、これも実行元からVector3の引数hitPointを渡してある。
そして最後に、Healthが0を割っていればDeathメソッドを実行する。
Deathメソッド
前述のTakeDamageメソッドから、ヘルスが0を割った時に実行するメソッド。
まずisDeadをTrueにする。
アタッチされているCapsuleColliderのTriggerをTrueにしているのは、死亡したEnemyには当たり判定を残さないようにするため。特にTriggerを利用するわけではない。
アニメーターのDeadパラメーターをオンにして、EnemyAudioを死亡SEに差し替えたらそれを再生。複数の音源を適宜代入することで、コンポーネントを一つだけで賄っている。ちなみにコンポーネントが複数になった場合は、配列で指定する方法がある。
https://increment-log.com/unity-sound-se-play/
StartSinkingメソッド
Enemy死亡アニメーション終了後に実行されるメソッド。
StartSinkingメソッドが実行されるタイミングは後述。
NavMeshをfalseにする。これは、~|~~
RigidbodyのisKinematicをTrueにする。isKinematicによってこのオブジェクトはほかの物理の干渉を受けることがなくなる。死亡したオブジェクトに計算のリソースを割く無駄を省くためだ。
isSinkingフラグも立てておき、二秒後にこのオブジェクトを破壊する=沈み始めてから約2秒後にこのオブジェクトは消滅する。
StartSinkingメソッドはどこから実行する?
いままで見てきたコードの中に、StartSinkingメソッドを実行する記述はなかった。ではどこから実行するのか。ここで新しい概念Animation Eventが登場する。
ざっくり言えば、「アニメーション中の特定のタイミングで、特定のメソッドを呼び出す仕組み」である(たぶん)。ここでは、死亡アニメーションの途中で、このStartSinkingメソッドを呼び出している。ZonBunnyモデルのAnimationの中にすでに設定されているので、ここでは確認だけ行う。
ZonBunnyモデルのAnimationタグ、Deathアニメをクリックしてみると……
0:33のあたりにEventが設定されているのを確認、このタイミングでStartSinkingメソッドが実行されているのだ。
EnemyAttackスクリプトの修正
EnemyAttackスクリプトで、コメント化されていた部分、これはEnemyHealthにまつわる部分だ。今回改めてここのコメント化を解除していき、中身を確認していく。
EnemyHealthクラスを取得、格納する
EnemyHealth enemyHealth;
enemyHealth = GetComponent
Enemyのアタック条件に、「EnemyのHealthが0より大きいなら」を追加する
if(time >= timeBetweenAttacks && palyerInRange && enemyHealth.currentHealth > 0){
Attack();
}
currentHealthはここで呼び出されるため、Publicな変数になっていたことに注意。
PlayerAttackの実装
大まかに言って三つに分かれる
・射撃時のマズルフラッシュ(パーティクル)
・射撃時のサウンド
・射撃時の弾道表示
GunParticlesの実装
マズルフラッシュの実装。PrefabにあるGunParticlesから、ParticleSystemをCopyしてPlayer以下にあるGunBarrelEndオブジェクトにPasteする。PrefabをD&Dでアタッチしないこと。
LineRendererの実装
GunBarrelEndオブジェクトにLineRendererコンポーネントを追加。LineRendererコンポーネントは、語義の通り直線を描画するものであり、今回は弾道表示に使う。アタッチ直後はマゼンタ色の四角い箱が表示されている。
直線の色を変える
LineRendererで描画する直線の色を変更するために、Material以下のElement 0に、用意されているLineRenderMaterialを設定する。これでLineの色が黄色になる。
直線の形を変える
この箱を戦に変えるために、Parametersを調整する。StartWidth/EndWidthともに0.05に設定する。
Unity2018ではUIが変わっているので注意。
射撃時にスクリプトから適宜呼び出すため、LineRendererコンポーネント自体は非アクティブにしておき設定は終わり。
Lightの実装
マズルフラッシュが周囲を照らす表現の実装。同じくGunBarrelEndオブジェクトにLightコンポーネントを追加。
色を変える
Colorを黄色に設定。
こちらも射撃時にスクリプトから適宜呼び出すため、コンポーネント自体は非アクティブにして設定完了。
射撃時のサウンド実装
AudioSourceを追加し、PlayerGunShotを設定、PlayOnAwakeからチェックを外して設定完了。
PlayerShootingスクリプトの実装
GunBarrelEndにPlayerShootingスクリプトをアタッチ。中身を確認していく。
変数
public int damagePerShot:ショット一発のダメージ値
public float timeBetweenBullets:ショット後、次のショットを打てるまでの時間
public float range:ショットの届く範囲
float timer:各種同期のために時間を格納
Ray shootRay:ショットの当たり判定をとるためのRay
RaycastHit shootHit:上記Rayのぶつかった情報を格納
int shootableMask:ショットがぶつかるオブジェクトが存在するレイヤーを格納
ParticleSystem gunParticles:マズルフラッシュのパーティクルを格納
LineRenderer gunLine:弾道のLineRenderを格納
AudioSource gunAudio:銃声のAudioSourceを格納
Light gunLight:マズルフラッシュのlightを格納
float effectsDisplayTime:エフェクトの表示時間
Awakeメソッド
shootableMaskにShootableレイヤーをLayerMaskで取得。LayerMaskのおさらいをしておくと、
//layerMaskに存在するオブジェクトにだけ、Rayが衝突する
Physics.Raycast(ray, out hit, Mathf.Infinity, layerMask);
つまりShootableレイヤーが設定されているオブジェクト(今回のプロジェクトの場合はZonBunnyとEnvironment)にだけrayが衝突するようにするための下準備である。
それ以外は各変数にコンポーネントを格納。
Updateメソッド
Fire1に設定されたボタンがおされている間(デフォルトでは左クリック)かつ、ショット後、次のショットが打てるようになるまでの時間が経過していれば、Shootメソッドを実行する。
さらに、ショット後、十分な時間が経過していれば、DisableEffectsメソッドを実行する。(timeBetweenBulletsにeffectDisplayTimeを乗算しているのは、射撃間隔とエフェクトとの整合性をとるためで、それ以上の意味はないっぽい)
DisableEffectsメソッド
弾道表示と、マズルフラッシュによる周囲を照らすLightをfalseにする。オブジェクトでなくコンポーネントなので、setActiveではなくenabledをfalseにすることに注意。
Shootメソッド
まずtimerをリセットする。これをしないと射撃間隔の条件付けが機能しないので要注意。
銃声を再生し、マズルフラッシュのライトをTrueに。
また、gunParticlesを止めているが、これは前のショットの名残が残っていたときにそれを強制的に消すためのもの。直後にgunParticlesを再生。
弾道表示をtrueにし、さらにLineの始点PositionをGunBarrelEndと同じ位置に代入。
//indexは、0なら始点、1なら二個目の点、2なら三個目の点の位置を、Vector3に設定する。
//つまりまっすぐな一本の線だけでなく、途中で折れる線も描けるということ
LineRenderer.SetPosition(int index, Vector3 position)
rayの始点をGunBarrelEndのPosition、方向をforwardに設定する。forwardってどっちやねん!と思うが、forwardとはオブジェクトのZ軸に対してPositiveな方向、を意味するもの、みたいだ。3Dのゲームを作るときは、Z軸のプラス方向が正面になるように作る。覚えておこう。
次にRayを、先ほど設定した場所・方向から、rangeの範囲まで飛ばし、shootableMaskのレイヤーのオブジェクトだけ衝突するように飛ばす。もしヒットしたら、EnemyHealthコンポーネントを衝突対象から取得。衝突対象がEnemyであれば(壁とか障害物じゃなければ)EnemyHealthコンポーネントが取得できているはずなので、TakeDamageメソッドを、damagePerShotとshootHit.pointを渡して実行。最後に弾道を表示する直線の二個目の点(今回の場合は終点)を衝突地点に設定する。
もしRayが何にも衝突しなければ、弾道のLineの終点はRayのrange終点と等しくなる。
PlayerをPrefabにApplyしておく
ここまでいろいろ実装してきたPlayerの変更をPrefabに反映させるためApplyする
実装確認
無事に動くは動くが、Enemy死亡時にFatalErrorが発生。これは、Enemyに設定してあるNavMeshAgentが消えることで、SetDestinationメソッドが実行できなくなっているために起きているエラー。エネミー死亡時に、同時にNavMeshをFalseにしてやることで発生しなくなる。
長かったーー!!終わり!
第8回はコチラ
第6回はコチラ
「Unity公式チュートリアルSurvival Shooter日本語実践Chapter.7」への1件のフィードバック