今回は上半身のアニメーションだけを変更し、銃を構えた状態で歩く、走る、ジャンプする事が出来るようにしていきます。
上半身のアニメーションだけ変更するにはアニメーターのレイヤー機能とアバターの機能を使います。
アニメーターのレイヤー機能とアバター機能に関しては

も参考にしてください。
前回までの記事で

で銃を構える、撃つといったアニメーションを作成し、

でIKを使って左手を銃を支えるような位置に移動させています。
もしまだ作り終えてない方は記事を参照して作成してみてください。
上半身用のレイヤーを作成する
まずはMusasiAnimatorに上半身のアニメーションを管理するUpperBodyレイヤーを作成します。
↑のようにMusasiAnimatorのLayerを表示した状態で+をクリックします。
新しいレイヤーが出来るので名前をUpperBodyとしておきます。
UpperBodyの右側の歯車マークをクリックすると新しいウインドウが表示されますが、ここでAvatarMaskを設定してアニメーションで動かす部位を指定する事になります。
AvatarMaskはまだ作ってないので作成していきましょう。
上半身用のアバターマスクを作成する
Projectウインドウで右クリック→Create→Avatar Maskを選択します。
名前をUpperBodyにして、選択するとインスペクタにアバターマスクの情報が表示されます。
↑のように上半身だけを緑色にして、下半身は赤色に設定します。
MusasiAnimatorのUpperBodyレイヤーの右側の歯車をクリックしてWeightを1、Maskにさきほど作成したUpperBodyアバターマスクを設定します。
これでUpperBodyレイヤーに状態や遷移を作成するとBase Layerのアニメーションに上半身だけ上書きしたアニメーションが再生されます。
アニメーターで上半身用の状態と遷移を作成する
MusasiAnimatorにアニメーションパラメータBool型のWaitShotとTrigger型のShotを作成します。
GunIdleをデフォルト状態にし、GunIdleアニメーションを設定、
WaitShotにはWaitShotアニメーション、
ShotにはShotアニメーションを設定します。
それぞれ遷移を繋げて
GunIdle→WaitShotはWaitShotがtrue、Has Exit Timeのチェックを外す
WaitShot→GunIdleはWaitShotがfalse、Has Exit Timeのチェックを外す
WaitShot→ShotはShotがtrue、Has Exit Timeのチェックを外す
Shot→WaitShotは条件なしで、Has Exit Timeのチェックを入れる
Shot→WaitShotへの遷移はアニメーション終了まで待つので連続で撃ちたい場合はアニメーション速度を上げるか、アニメーションパラメータのShotをTriggerではなくBool型として条件で遷移させる方法に変えた方がいいかもしれません。
現状はとりあえずこのままでいきます。
Shot→WaitShotの遷移ではWaitShotの構え終わる前のアニメーションは再生したくないので、
とWaitShotのアニメーションを左にドラッグし93%と構え終わったあたりから再生するようにします。
キャラクター操作スクリプトMyCharaに銃を構える処理、銃を撃つ処理を追加
次にキャラクター操作スクリプトMyCharaに銃を構える処理、銃を撃つ処理を追加します。
1 2 3 4 | // 銃を構えているかどうか private bool waitShot = false; |
waitShotは銃を構えているかどうかのフラグです。
Updateメソッドに銃を構える処理と銃を撃つ処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 銃を構える if(Input.GetButton("Fire2")) { waitShot = true; animator.SetBool("WaitShot", true); } else { waitShot = false; animator.SetBool("WaitShot", false); } // 銃を撃つ if(waitShot && Input.GetButtonDown("Fire1")) { animator.SetTrigger("Shot"); } |
Input.GetButton(“Fire2”)に対応するボタンが押されていたら銃を構えます。
デフォルトではマウスの右ボタンが対応しています。
銃を構えている状態でFire1に対応するボタンが押されたら銃を撃ちます。
デフォルトでFire1にはマウスの左ボタンが設定されています。
上半身は銃を構えた状態で下半身は歩いている状態になるかどうか確認する
これでスクリプトが出来たのでUnityの実行ボタンを押して確認してみましょう。
↑がScene画面で動作を確認した動画です。
銃を構えた状態で歩いているのを確認出来ると思います。
(^^)v
↑がGame画面(実際のゲーム画面)で確認した動画です。
上半身だけが別アニメーションになっているかどうかはわかりません。
常に手が揺れてしまう為、動かないようにする
さらに、銃を構えている時に手が横に揺れている為狙いを定めるのが難しそうですね・・・。
歩いている時や走っている時はもちろん体が上下に動くのでいいと思いますが、せめて移動していない時ぐらいは狙いを固定出来るようにしましょう。
手が横に揺れているのはBase LayerのIdleのアニメーションが再生されているからです。
Base LayerのIdleにGunIdleを設定してください。
これで手が横に揺れる事はなくなりました。
ただ今度はまったく動かなくはなりますが・・・・(^_^;)
手を動かしたい場合はGunIdleのアニメーションで常に腕を動かすアニメーションにしておくといいかもしれません。
それでは再度確認してみましょう。
↑がGame画面で確認した動画です。
動いていない時は完全に腕が動かないのでやっぱりGunIdleのアニメーションを修正した方がいいかも・・・(T_T)
アニメーションじゃなくボーンを少し揺らすというのでもいいかなぁ・・・。
↑がScene画面で確認した動画です。
銃を構えた状態から通常の持っている状態への切り替わりが早すぎるのでうまい具合に調整してください。
WaitShot→GunIdleの遷移のアニメーションブレンドを調整してください。
でもまぁ・・・WaitShot→GunIdleの遷移画像を載せてないので上の動画のようにみんながわたくしと同じブレンドにしてるとは限りませんね・・・(-.-)
これで上半身のアニメーションをレイヤーとアバターマスクを使って変更する事が出来るようになりました。
銃を構える時だけIKを有効にし銃に左手を添える(2019/12/09に追記)
歩いている時等は左手にIKを働かせず、銃を構えた時に初めてIKを有効にし、左手を銃に添えたい事もあります。
そんな時はAnimatorControllerのStateにビヘイビアを取り付けて、そのState(状態)に入った時や抜けた時にIKのウエイトを操作することで実現出来ます。
MusashiAnimatorのUpperBodyレイヤーを選択し、Idle状態を選択してインスペクタのAdd Behaviourボタンを押して新しくLeftHandIKBehaviourビヘイビアスクリプトを作成します。
LeftHandBehaviourはIdle状態に取り付けたのでIdle状態に入った時や抜けた時に実行されるメソッドを修正していきます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class LeftHandIKBehaviour : StateMachineBehaviour { private HandIK handIK; // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { if (handIK == null) { handIK = animator.GetComponent<HandIK>(); } handIK.ReSetWeight(); } // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks //override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) //{ // //} // OnStateExit is called when a transition ends and the state machine finishes evaluating this state override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { if (handIK == null) { handIK = animator.GetComponent<HandIK>(); } handIK.SetWeight(); } // OnStateMove is called right after Animator.OnAnimatorMove() //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) //{ // // Implement code that processes and affects root motion //} // OnStateIK is called right after Animator.OnAnimatorIK() //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) //{ // // Implement code that sets up animation IK (inverse kinematics) //} } |
OnStateEnterは状態に入った時に呼ばれるのでそこで引数で受け取ったanimatorからムサシに取り付けているHandIKスクリプトを取得し保持します。
Idle状態に入った時にはIKのウエイトを0にしたいのでHandIKスクリプトのReSetWeightメソッドを呼び出します。
HandIKスクリプトは後で修正するので置いておきます。
OnStateExitはIdle状態を抜けた時に呼ばれるので、そこでHandIKスクリプトのSetWeightメソッドを呼び出し、ウエイトを1にします。
これで上半身用のレイヤーのIdle状態にビヘイビアスクリプトを取り付ける事が出来ました。
次にHandIKスクリプトに追記をします。
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 | // ウエイトを変更中かどうか private bool changingWeight; // 現在のウエイト private float ikWeight; // 目的のウエイト private float destinationWeight; // ウエイトを変えるスピード private float changeSpeed = 1f; private void Update() { if(changingWeight) { ikWeight = Mathf.MoveTowards(ikWeight, destinationWeight, changeSpeed * Time.deltaTime); if(Mathf.Approximately(ikWeight, destinationWeight)) { changingWeight = false; } } } void OnAnimatorIK() { // キャラクターの左手の位置と角度を合わせる if (equipFlag) { animator.SetIKPositionWeight (AvatarIKGoal.LeftHand, ikWeight); animator.SetIKRotationWeight (AvatarIKGoal.LeftHand, ikWeight); animator.SetIKPosition (AvatarIKGoal.LeftHand, leftHand.position); animator.SetIKRotation (AvatarIKGoal.LeftHand, leftHand.rotation); } } public void SetWeight() { destinationWeight = 1f; changingWeight = true; } public void ReSetWeight() { destinationWeight = 0f; changingWeight = true; } |
changingWeightはウエイトを変更中かどうか、ikWeightはIKのウエイト、destinationWeightはウエイトの目的値、changeSpeedはウエイトを変更するスピードです。
Updateメソッドではウエイトを変更中であれば現在のウエイトを目的のウエイトへと変更します。
Mathf.Approximatelyを使ってfloat値の比較をし、現在のウエイトと目的のウエイトが同じになったらウエイトの変更を終了します。
OnAnimatorIKで左手のIKのウエイトを1にしていましたが、そこをikWeightに変更します。
SetWeightとReSetWeightは目的のウエイトを設定し、ウエイトを変更中にしています。
これで通常の移動時はIKのウエイトが0で、銃を構えた時は1になります。
ここまでで銃を構えた時だけ上半身のアニメーションの上書きが出来ましたが、さらに左腕をBase LayerのアニメーションにするならUpper Bodyレイヤーに使用しているUpper Bodyアバターマスクの左腕部分を無効化します。
左腕部分用のレイヤーを新たに作成し、UpperBodyレイヤーと同じように状態と遷移を作成します。
新しくアバターマスクを作成し、左腕部分だけが有効になるようにして、AnimatorControllerの左腕用のレイヤーの右の歯車を押して設定します。
これで歩きや走りの時はBase Layerのアニメーション、銃を構えた時にBase LayerをUpperBodyレイヤーの上半身の右側、LeftHandレイヤーの左側で上書きすることになります。
他にもっと簡単なやり方がありそうですね。
次回は主人公キャラが視点を上下させた時に上半身の角度を視点の方向に曲げ銃口の先も視点の先になるようにしていきます。
最後の動画で既にその機能が出来ていますが、その部分は追記部分なので無視してください。(^_^;)