Unityで速い攻撃モーションの時に当たり判定がされない問題の対応をする

今回やる事は、Unityのアクションゲーム等で攻撃のアニメーションスピードが速すぎて剣と敵との当たり判定がスルーされてしまう問題の対応です。

この問題は攻撃アニメーションのスピードを落とせば解消されますが、実現したかった速い攻撃を捨てることになります。

そこで、なるべく当たり判定がされるように自身でメッシュを生成し、それをコライダとして使って対応してみたいと思います。

メッシュの生成については

Unityではスクリプトからメッシュを生成する事が出来ます。そこでメッシュを作成する時に必要な基本的な勉強をしてから実際にメッシュの生成を行ってみます。

を参照してください。

なにも対応していない場合は

速いモーションで当たり判定がスル―される

↑のように武器が当たっているように見えて当たり判定がスルーされています。

これを今回作成する機能を適用すれば

当たり判定の対応をしたサンプル

↑のように攻撃モーションが速くても当たり判定がされます。

スポンサーリンク

当たり判定がスル―される理由と対応

武器と敵との当たり判定はコライダが他のコライダと接触した時にOnTriggerEnterで判定していますが、フレーム毎の武器の位置で武器のコライダが敵のコライダと接触しているかを判定する為、攻撃のモーションが速いと前フレームと現フレームでの位置が大きく変化する為、当たり判定がされない事になります。

そこで、前フレームでの剣の位置と現フレームでの剣の位置の間のメッシュを生成し、それをMesh Colliderに設定して当たり判定に使用する事にします。

その為、攻撃モーションが速くても前回の武器の位置と現在の武器の位置の間に当たり判定を作る為、当たり判定の範囲が広くなります。

作成するメッシュコライダのイメージ図

作成するメッシュコライダのイメージとしては↑のような感じで攻撃のモーションの間に作成します。

前フレームでの剣の位置や現フレームでの剣の位置を使ってメッシュを生成する方法は

Unityでスクリプトから剣の軌跡のメッシュを作成し表示してみたいと思います。フレーム間の線を分割し曲線を作って滑らかな軌跡が表示されるようにします。

でやっていますが、今回はそこで作ったメッシュ生成より簡単です。(^^)/

こちらの記事を先に公開するべきだったかもしれませんね・・・・(^_^;)

敵の設定

まずは敵キャラクターをヒエラルキー上に配置し、ボーンの子要素に空のゲームオブジェクトを作成し当たり判定用のコライダを設定します。

ボーンの子要素に当たり判定を個別に作成

↑のように対応するボーンの子要素に空のゲームオブジェクトを作成し、それぞれコライダを設定します。

ここら辺は

Unityゲームの敵キャラクターに取りつけるコライダを細かく設定し、当たり判定を細かくします。

を参照してください。

当たり判定を設定した敵は、

敵に作った当たり判定の箇所

↑のようになりました。

敵キャラクター移動用のCharacterControllerも取り付けてますが、今回は使用しません。

またそれぞれの当たり判定用のゲームオブジェクトのTagにEnemeyを新しく作り設定し、コライダを取り付けIs Triggerにチェックを入れて物理的に当たらないようにします。

敵の当たり判定毎にタグとコライダ、スクリプトを設定

DamageScriptスクリプトを新しく作成しそれぞれの当たり判定用ゲームオブジェクトに設定しています。

DamageScriptスクリプトの解説

ダメージを受けた時の処理を行うDamageScriptスクリプトの解説をします。

damageUIはダメージを表示するUIプレハブを設定し、攻撃を受けた時にそのUIをインスタンス化し表示します。

ここで使用するダメージUIは

Unityのアクションゲームで敵にダメージを与えた時に与えた敵の体の位置に与えたダメージ値を表示する機能を作成します

で作成したダメージ用UIプレハブです。

invincibleTimeはダメージを受けた時に次の攻撃を無視する時間を設定します。

isDamagedはダメージを受けた状態かどうか、elapsedTimeはダメージを受けてからの時間経過です。

これらのフィールドは連続でダメージを計算するのを回避する為に作成しました。

前の剣の位置と現在の剣の位置の間に当たり判定を作成するので、剣が移動するたびその当たり判定メッシュを生成します。

その為、その新しく生成したメッシュが敵のコライダと接触するたびにダメージを与えてしまいます。

そこで一旦ダメージを受けたらinvincibleTimeを超えない限りはその場所にダメージを与えないようにします。

Updateメソッドではダメージ状態の時に経過時間を計算し、invincibleTimeを超えたらダメージを受ける状態に変更します。

Damageメソッドではダメージを受けていない状態の時だけダメージ用UIの表示を行っています(実際に敵のHPを減らす処理等は今回の記事とは関係ない為記述していません)。

Resetメソッドはダメージのリセット処理です。

引数でbool値が渡ってこなかった時はfalseに初期化しています。

DamageScriptスクリプトで一旦ダメージを受けてから次に攻撃を受けるまでの無敵時間を作りましたが、主人公が連続攻撃を可能とする場合、次の攻撃モーションの時にも最初のモーション時の無敵時間が残っています。

その為、無敵時間が長いと次のモーションでの攻撃も無視されてしまう事になります。

無敵時間があるのは同じ攻撃時の連続判定を避ける為に取り付けたので、次のモーション時にはリセットするのが自然です。

その対処は主人公キャラクターの処理でやりますので一旦置いておきます。

剣の設定

次に主人公キャラクターが持つ剣の設定をしていきます。

剣自体のインスペクタの設定

剣にはCapsule Colliderを取り付けIs Triggerにチェックを入れます。

Rigidbodyを取り付けIs Kinematicにチェックを入れます(RigidbodyはOnTriggerEnterを発生させる為設定)。

Attackスクリプトは剣が敵のコライダと接触した時に、敵に設定しているDamageScriptスクリプトのDamageメソッドを呼び出す為のものです。

剣のコライダが敵の当たり判定のゲームオブジェクトのコライダと接触した時にDamageScriptのDamageメソッドを呼び出しています。

前の剣の位置と現在の剣の位置でメッシュを生成しますが、平面のメッシュだとちょっと問題が出てくるので厚みを出す為に剣元に2点、剣先に2点の空のゲームオブジェクトを作成し、前の剣の位置とで厚みのあるメッシュを生成出来るようにします。

剣元と剣先に2点ずつポイントを作成

↑のように剣の子要素に空のゲームオブジェクトを作成し名前をStartPosition、StartPosition2、EndPosition、EndPosition2とします(名前はわかりやすいように変更してください)。

それぞれの位置は

剣の子要素にポイントを作る

↑のような位置に移動させます。

とりあえずここまでの機能で剣を振った時に剣と敵との当たり判定をする事が出来ます。

攻撃モーションのビヘイビア

先ほど敵がダメージを受けた時に無敵時間を設けましたが、次のモーションの時には無敵状態を解除したいところです。

そこでAnimator Controllerの攻撃状態にビヘイビアを設定し、そこで無敵状態をリセット出来るようにします。

ビヘイビアに関しては

Unityアニメーターコントローラを通常のスクリプトで制御するのではなくBehaviourを使って制御するようにします

を参照してください。

今回の主人公キャラクターは連続攻撃が出来るものとし、最初の攻撃中にマウスの左ボタンを押すと次の攻撃モーションへと遷移するようにしています。

攻撃モーションの最初を選択

↑のような感じの状態と遷移を作成してます。

最初の攻撃モーションの状態を選択し、インスペクタのAdd Behaviourを押して名前をAttackBehaviourとします。

攻撃モーションの最初にビヘイビアを追加

AttackBehaviourでは無敵状態をリセットする為の処理を記述します。

OnStateEnterはその状態に入った時に実行されるので、このタイミングでFindObjectsTypeでDamageScriptスクリプトを全て取り出して、Resetメソッドを呼び出し初期化します。

OnStateUpdateはその状態でアニメーションが再生中にずっと呼ばれるので、その中でマウスの左ボタンが押されたかどうかを判定し、次の攻撃アニメーションに遷移するかどうかを判定しています。

左ボタンが押されたら次のモーションに移動するので、その時に無敵状態をリセットします。

FindObjectsTypeで全部のDamageScriptを探して処理するので動作が遅くなる可能性もあるかもしれません。

前の剣の位置と現在の剣の位置の間にメッシュを作成する

これでやっとメインの処理である前の剣の位置と現在の剣の位置との間からメッシュを作成する処理に移れます・・・・。

ヒエラルキー上に空のゲームオブジェクトを作成し、名前をTrailColliderとし、Transformの歯車からResetを選択し位置や角度をリセットします。

TrailColliderゲームオブジェクトにはRigidbodyを取り付けIs Kinematicにチェックを入れ、先ほど作成したAttackスクリプトを取り付けます。

さらにTrailColliderスクリプトを新しく作り設定します。

スクリプトが長いので少しづつ見ていきます。

フィールド宣言部分

startPosition、startPosition2、endPosition、endPosition2は剣の子要素に作成したゲームオブジェクトをそれぞれ設定し、位置を取得する為に使用します。

meshは生成したメッシュを入れるフィールド、頂点リストと三角形リストはメッシュを生成する時に使用する情報を入れます。

当たり判定のメッシュのコライダは攻撃モーションの位置によってオン・オフをするので、isSwordColliderフラグを使います。

このフラグはアニメーションイベントから送られてきたイベントでオン・オフをします。

アニメーションイベントに関しては

Unityのアニメーションイベントを使い、歩くアニメーションで地面に足を着いた時に足音を鳴らしたり剣を振っている途中で剣を振った時の音を鳴らしたりしたいと思います。

を参照してください。

meshColliderはコライダコンポーネント、meshFilterはメッシュフィルターコンポーネントを設定します。

Startメソッド

Startメソッドでは初期処理を行っています。

自身のMeshFilterコンポーネントのMeshクラスをmeshに入れます。

自身のMeshColliderコンポーネントをmeshColliderに入れます。

MeshFilterとMeshColliderコンポーネントはクラス定義の前のアトリビュートで指定している為、TrailColliderスクリプトをゲームオブジェクトに設定すると自動で追加されます。

meshColliderのConvexのチェックをオンにします。

MeshColliderコンポーネントのConvexにチェックを入れると、他のメッシュコライダを持つゲームオブジェクトとの当たり判定をする事が出来るようになります。

このチェックを入れないと他のメッシュコライダを持つゲームオブジェクトだけでなく、Cube等のプリミティブな図形(メッシュコライダと判定される物)との当たり判定が出来ません。

Convexにチェックを入れると凸面を勝手に作成してくれるので、メッシュの三角形を作る手間が省かれます。

詳しい事はメッシュの生成処理で見ていくので置いておきます。

meshColliderのIs Triggerにチェックを入れ物理的に当たらないようにし、sharedMeshにmeshを入れてメッシュコライダのメッシュに自作のメッシュを適用します。

LateUpdateメソッド

LateUpdateメソッドでは当たり判定が有効な時だけCreateMeshメソッドを呼び出しメッシュを生成しています。

CreateMeshメソッド

CreateMeshメソッドで実際に当たり判定のメッシュを生成してます。

CreateMeshメソッドが呼ばれる度にメッシュ、頂点、三角形をクリアにしています。

頂点に使用するのは前の剣元、剣先の位置と、現在の剣元の2点、剣先の2点の6点なのでそれをveritciesListsに登録します。

頂点を登録したらその頂点から三角形を作る必要があります。

MeshColliderのConvexにチェックを入れた場合、厚みのあるコライダを自動で生成する為にメッシュの三角形で厚みのある面を生成しておく必要があります。

と思ってるんですが、厚みを付けて三角形を作成しなくてもエラーがでなくなりました・・・・(謎)。

とりあえず厚みは付けておいた方が問題はでないかもしれません。

以前出ていたエラーの内容としては

ConvexHullBuilder::CreateTrianglesFromPolygons: convex hull has a polygon with less than 3 vertices!

といった内容だったのですが、急に出なくなったのでわかりません。(^_^;)

平面の場合3点より多い頂点を使用しててもエラーになったんですが・・・うーむ。

前の剣元、剣先の位置と現在の剣元の1点、剣先の1点で平面のコライダを作り他のコライダとの判定をしようとしましたが、厚みを付ける為の面も作成しておかないとエラーが発生します。

その為、縦方向と横方向の面を1点ずつ作成します。

本来であれば四角形を2つの三角形で作り、それを4面作って一つの図形を作るところですが、Convexにチェックを入れると縦横の面を作成するだけで勝手に図形のメッシュを作ってくれました。

例えば↑のスクリプトの三角形でConvexにチェックを入れていないと

Convexにチェックを入れない場合

↑のように縦と横の四角形1面ずつしか作成されませんが、Convexにチェックを入れると

Convexにチェックを入れた場合

↑のように他の面も作成され面で囲われた図形のメッシュが生成されます。

Convexにチェックを入れない場合でも、四角形の面1つで当たり判定は出来ますがCube等の図形や他のメッシュコライダとの当たり判定が出来なくなります。

こういったことを考慮するとConvexにチェックを入れておくといいかもしれません。

ただ、このConvexにチェックを入れられるのはメッシュの頂点が255以下の場合のみです。

今回のサンプルでは使っている頂点は6つです。

本来であればメッシュコライダは動かないゲームオブジェクトに設定するものなので、頂点が多い動くメッシュと他のメッシュコライダとの当たり判定は処理負荷が大きくなる為、このような制限があるんですかね?他の理由かもしれませんが・・・。

三角形を作る処理でコメント化してあるのが本来作るべき面の設定箇所ですが、作る必要がないのでコメント化しています。

あ・・・・、四角形1面分足りてない気がするが・・・・(-_-)

これでTrailColliderスクリプトが出来たので、TrailColliderゲームオブジェクトのインスペクタの設定を見てみます。

TrailColliderのインスペクタの設定

↑のように設定しました。

MeshColliderの設定はスクリプトから設定しているので、特に変更する必要はありません。

アニメーションイベントで当たり判定のオン・オフ

アニメーションイベントは当たり判定開始時にStartSwordTrailイベント、判定終了時にEndSwordTrailイベントが発生するようにしておきます。

名前が剣の軌跡を作った時のイベントと同じなのはタイミングが同じだからで、そのまま使っています。

Animatorコンポーネントを持つキャラクター自身にRecieveSwordColliderEventスクリプトを作成し取り付けます。

swordColliderには剣、trailColliderにはTrailColliderゲームオブジェクトを設定します。

これで全ての機能が出来ました。

当たり判定がされるか確認する

機能が出来たので確認してみましょう。

まずはシーンビューで当たり判定であるメッシュコライダがどのように作成されているかを確認します。

メッシュコライダの確認

当たり判定は

↑のようになりました。

Cubeのような他のメッシュコライダとの当たり判定も出来ていますね。

動画ではCubeがでかいのでダメージUIがほとんどみえてませんが・・・。

今回の処理で少し気になる点としては、ビヘイビアのRecieveSwordColliderEventスクリプトの処理で全DamageScriptスクリプトを取得している処理ですかね。

処理が遅くなるようならば別のやり方を考えなければいけませんが・・・・良い考えが思い浮かびませぬ(‘_’)

今回の機能を作成した事で当たり判定のスルー問題も解消されそうです。(^^)/

スポンサーリンク

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

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