今回はアニメーションの動きに合わせてキャラクターの手や足、体の位置を合わせる機能を作ります。
手や足の位置を合わせるにはIKを使って通常のアニメーションから独立して手や足の位置や角度を指定する事が出来ましたが、今回の場合は元々のアニメーションの再生位置によってその時の手や足の位置を特定の位置に持っていくという方法です。
例えば少し高い所に飛び乗るアニメーションがあって、手をついたところのアニメーション位置の時に手の部分を山の手をつく位置に持っていくということを行います。
IKの場合と違って元々のアニメーションはそのままでアニメーションの再生位置によってキャラクターの位置を決めます。
キャラクターの移動には

で作った機能を使います。

のキー操作でも出来ますが一部違う場所もあります。
違う個所は機能のOn・Offをしている個所だけなのでその他は同じです。
キャラクターの移動は今回の機能を実現するのには関係なくキャラクターを通常通り動かす時に使うだけです。
アニメーションの動きに手や足の位置を合わせる方法を考える
まずはアニメーションの再生位置によってキャラクターの手や足、体を特定の位置に移動させる方法を考えます。
IKの時と同じようにアニメーターコントローラーでIK Passのチェックを入れOnAnimatorMoveメソッド内でキャラクターの位置を動かす事は出来ます。
しかし手や足の位置を合わせるのはなかなか難しそうです。
IKを使えば出来ると思いますが、結構大変そうなのと元のアニメーションを変更してしまいます。
こう考えていくと難しそうですが比較的簡単にアニメーションの再生位置によって手や足の位置を合わせる事が出来る機能があるんです。
それがターゲットマッチングという機能です。
Animator.MatchTargetメソッドを使えばアニメーションの再生位置に合わせてキャラクターを移動させる事が出来ます。
しかしこのターゲットマッチング機能はアニメーションの動きをキャラクターに反映させている場合だけ使えます。
わたくしがキャラクターの移動に関して行ってきたアニメーションの動作をキャラクターの動きに反映させない方法ではターゲットマッチングは使えません。
上の画像にあるAnimatorのApply Root Motionにチェックが入っていないと使えません(上の画像のようにチェックを外していると使えません)。
しかし最初からここにチェックを入れると今まで作成してきたキャラクター移動のスクリプトがうまく動作しなくなります。
Apply Root Motionにチェックを入れてアニメーションの動きでキャラクターを動かしている場合はそのままターゲットマッチングを使えます。
スクリプトでキャラクターを動かしていた場合はどうすればいいんでしょうか?
ターゲットマッチングを使う時だけキャラクター移動のスクリプトを使わずAnimatorのApply Root Motionにチェックを入れることにします。
まずはターゲットマッチングメソッドの使い方を見てみます。
1 2 3 | Animator.MatchTarget(到達の位置, 到達の角度, 到達させるボディーパーツ, ウエイト, 開始時間, 終了時間) |
アニメーションの開始時間から終了時間の間で到達の位置と角度に到達させるボディーパーツ(手、足等)をウエイトの分だけ到達させます。
アニメーションの開始時間と終了時間はアニメーションクリップを見て調べます。
調べ方は後でやります。
このメソッドを使えばアニメーションの再生位置によって到達したい体のパーツを到達したい位置に移動出来ます。
キャラクターが登る山のゲームオブジェクト等を用意する
では今回の機能を作る為のゲームオブジェクトを用意します。
JumpUpObjがキャラクターがジャンプして登る山です。
JumpTargetは右手が到達する位置です。アニメーションは変わらないので他のパーツの指定はしません。
SearchPlayerはキャラクターが山に登る為にジャンプが出来る範囲で、キャラクターがその範囲に入ったかどうかを調べます。
上が出来上がったゲームオブジェクトです。
透明な緑色がSearchPlayerで白いブロックが登る山です。
飛び上がって地面を掴み登るアニメーションの開始時間と終了時間を調べる
それではキャラクターが山にジャンプしてつかまるアニメーションを用意し、ターゲットマッチングで使用する
アニメーションの開始時間と終了時間を調べます。
使用するアニメーションは上のように飛び上がって地面を掴み登るアニメーションです。
ターゲットマッチングの開始時間は足を離す直前にしました。
13%の所を開始時間にします。
注)この後に作成するスクリプトでは11%の位置を設定してあります。
終了時間は手を地面について伸びあがる前の部分にしました。
30.2%の所です。
注)この後に作成するスクリプトでは29.3%の位置を設定してあります。
アニメーションクリップによって開始時間と終了時間を変更してください。
アニメーターコントローラーにJumpUp状態を作る
アニメーターコントローラーにJumpUp状態を作成し、先ほどのアニメーションクリップを設定します。
アニメーションパラメータにTrigger型のJumpUpを作成します。
遷移条件は
Idle→JumpUpはJumpUpがトリガーされた時、HasExitTimeはオフ
Walk→JumpUpはJumpUpがトリガーされた時、HasExitTimeはオフ
とIdle状態やWalk状態から遷移させるように作成します。
ターゲットマッチングを使用したスクリプトMatchTargetMoveスクリプトを作成する
ターゲットマッチングに使用するアニメーションクリップの位置は調べ終わったので、実際に使用するスクリプトを作成します。
キャラクター移動スクリプトの中に記述した方が解りやすいとは思いますが、別にスクリプトを作りキャラクターに取り付けました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | using UnityEngine; using System.Collections; public class MatchTargetMove : MonoBehaviour { private Animator animator; private NavMeshAgent agent; // ナビゲーション機能で動かすスクリプト private NavMove navMove; // ジャンプ中かどうか [SerializeField] private bool isJump; // ジャンプエリア内かどうか [SerializeField] private bool inJumpArea; private CharacterController characterController; // 到達地点 private Transform jumpTarget; void Start () { animator = GetComponent<Animator>(); agent = GetComponent<NavMeshAgent>(); navMove = GetComponent<NavMove>(); characterController = GetComponent<CharacterController>(); isJump = false; NotJumpArea(); } void Update () { // マウスの右クリックを押した時 かつ ジャンプエリア内 かつ ジャンプ中じゃない時 if(Input.GetButtonDown("Fire2") && inJumpArea && !isJump) { // ジャンプエリア外にする NotJumpArea(); animator.SetTrigger("JumpUp"); } // ジャンプ中で一周り目の時 if(animator.GetCurrentAnimatorStateInfo(0).IsName("JumpUp") && animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 0.9f && !animator.IsInTransition (0) ){ // 移動関連のコンポーネントを無効化 navMove.enabled = false; agent.enabled = false; characterController.enabled = false; // ターゲットマッチングを使用する為applyRootMotionをtrueにする animator.applyRootMotion = true; // ジャンプ中フラグをオンにする isJump = true; // ターゲットマッチング animator.MatchTarget (jumpTarget.position, jumpTarget.rotation, AvatarTarget.RightHand, new MatchTargetWeightMask (new Vector3 (1f, 1f, 1f), 1f), 0.110f, 0.293f); } // ジャンプ終了したら元に戻す if(isJump) { if(animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 0.9f) { isJump = false; animator.applyRootMotion = false; characterController.enabled = true; agent.enabled = true; navMove.enabled = true; jumpTarget = null; } } } // ジャンプエリアに入った時に実行 public void JumpArea(Transform jumpTarget) { inJumpArea = true; this.jumpTarget = jumpTarget; } // ジャンプエリアから出て行った時に実行 public void NotJumpArea() { inJumpArea = false; } } |
jumpTagetフィールドを宣言し、ジャンプエリアに入った時にジャンプの到達点のTransformを設定します。
ジャンプするのはマウスの右ボタンを押した時でジャンプエリアに入っていてジャンプフラグがオフの時です。
その時にアニメーションパラメータのJumpUpをオンにします。
1回の再生ではnormalizedTimeは0から1の間の値になりますが、2回目は1から2の間となり、整数部分がループ回数で増えていきます。
アニメーションクリップはループ再生させていませんが、1秒を超える値が入ってきたのでこの条件を加えました。
ジャンプ中でアニメーションの遷移をしていない時にターゲットマッチングを使用します。
その前にターゲットマッチングを使用する準備を行っています。
1 2 3 4 5 6 7 8 9 10 | // 移動関連のコンポーネントを無効化 navMove.enabled = false; agent.enabled = false; characterController.enabled = false; // ターゲットマッチングを使用する為applyRootMotionをtrueにする animator.applyRootMotion = true; // ジャンプ中フラグをオンにする isJump = true; |
ジャンプ中はナビゲーション移動スクリプトのNavMove、ナビゲーションエージェント、CharacterControllerをOffにし、AnimatorのapplyRootMotionをOn、jumpFlagをOnにしています。
キャラクターにはCharacterControllerが取り付けてあると仮定していますが、CapsuleColliderが取り付けてある場合はそれをオフにします。
CharacterControllerは敵からのダメージを受けた時の為の当たり判定として設定してあるだけでCapsuleColliderでも問題ありません(今回は特別使ってませんが)。
ターゲットマッチングを使用するにはAnimatorのApply Root Motionにチェックを入れる必要がある為、チェックを入れます。
1 2 3 4 | // ターゲットマッチング animator.MatchTarget (jumpTarget.position, jumpTarget.rotation, AvatarTarget.RightHand, new MatchTargetWeightMask (new Vector3 (1f, 1f, 1f), 1f), 0.110f, 0.293f); |
マッチターゲットでは到達位置、角度、到達させるボディーパーツ(ここでは右手)、ウエイト、開始時間と終了時間を指定しています。
到達位置と角度はインスペクタで設定したJumpTargetの位置と角度になります。
ボディーパーツは
AvatarTarget.パーツ
を使います。パーツの部分はRoot、Body、RightHand、LeftHand、RightFoot、LeftFootが指定出来ます。
IKのAvatarIKGoalと同じですね。
ウエイトはMatchTargetWeightMaskメソッドを使います。
指定している引数は
MatchTargetWeightMask(Vector3(X, Y, Z)のウエイト, 角度のウエイト)
になります。
jumpFlagがOnの間はジャンプアニメーションの再生位置が90%を超えたらナビゲーション機能等を元に戻し、普通の移動が出来るようにします。
90%にしたのはジャンプアニメーションの最後の方の部分が終わるまで通常の移動が出来ないので、なるべく早めに通常の移動が出来るようにする為です。
90%をぎりぎりの98%等にするとジャンプし山に登ったあともしばらく通常の移動が出来なくなります。
ジャンプアニメーションの再生が9割終わったらジャンプを終了させます。
ジャンプが終了したら移動機能等を復活させています。
キャラクター検知スクリプトSearchJumpUpCharaを作成する
キャラクターのジャンプ機能が出来たので最後にSearchPlayerに設定するキャラクター検知スクリプトSearchJumpUpCharaを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | using UnityEngine; using System.Collections; public class SearchJumpUpChara : MonoBehaviour { // ジャンプの到達点 [SerializeField] private Transform jumpTarget; void OnTriggerEnter(Collider col) { if(col.tag == "Player") { col.gameObject.GetComponent<MatchTargetMove>().JumpArea(jumpTarget); } } void OnTriggerExit(Collider col) { if(col.tag == "Player") { col.gameObject.GetComponent<MatchTargetMove>().NotJumpArea(); } } } |
キャラクターを検知したらキャラクターに設置してるスクリプトMatchTargetMoveのJumpAreaとNotJumpAreaメソッドを呼び出します。
進入時はJumpArea、出ていった時はNotJumpAreaメソッドを呼び出します。
侵入時はインスペクタで設定したjumpTargetを引数として渡します。
こうすることで他の山を作成した時に山ごとにインスペクタで到達点を変更するだけでよくなります。
ターゲットマッチング機能で山をうまく登れるか確認する
これで機能が完成したので、Unityの実行ボタンを押して確認してみましょう。
上が上からみた動画です。
SearchPlayer内でマウスの右クリックを押すと山にジャンプして掴まり登ります。
キャラクターが後ろ向きでも向きを変えちゃんと掴まっています。
上の動画がアップです。
多少手が山に食い込んでいますが・・・・、これは各自調整してください(^_^;)
到達点の位置やターゲットマッチングの開始時間と終了時間の調整でうまく出来ると思います。
ジャンプ中も敵から攻撃を受ける仕様の場合はCharacterControllerを無効にすると当たり判定が出来なくなるので注意が必要です。
ジャンプ中に別のコライダを設定して別の当たり判定領域を設定し、IsTriggerのチェックを入れて当たり判定に利用するというのも出来そうですが面倒くさいですね・・・(^_^;)
多少ズレてもCharacterControllerは無効化しない方がいいのかも。
これでUnityでアニメーションの動きに合わせて手の位置を山に合わせる機能が出来ました。
以前からターゲットマッチングの機能を使いたかったんですがどうしても使えなかったので放置してました。
アニメーションの動きに合わせて位置を合わせるのでApplyRootMotionのチェックが必要だったんですね。
チェックしなくても出来る方法もあるのかも!?
とりあえず出来た方法でやっていこう・・・・((+_+))