今回は連続攻撃をする時にアニメーションの移動値や回転値をキャラクターに反映させてみたいと思います。
わたくしの場合はキャラクターのAnimatorのApply Root Motionのチェックを外してアニメーションの移動や回転の値をキャラクターの移動に反映させなかった為、
連続攻撃を行うとキャラクターはその場でアニメーションをしますが、スクリプトでキャラクターを移動や回転をさせない限り移動値が反映されません。
そこでキャラクターが攻撃を行う時に限ってApply Root Motionのチェックを入れてアニメーションの移動や回転の値をキャラクターのTransformに反映させるようにしたいと思います。
キャラクターが連続攻撃を出来るようにするには
を参照してください。
↑の記事を見て頂くと連続攻撃の作り方がわかります。
アニメーションの値をキャラクターの移動値に反映させると
↑のように元のアニメーションの動きを使ってキャラクターが移動します。
攻撃アニメーションは自作の変な攻撃アニメーションなので気にしないでください・・・・(._.)
連続攻撃にアニメーション値を反映させる機能の作成
それでは機能を作成していきます。
操作するキャラクターにはCharacterControllerを取り付けコライダのサイズの調整をし、AnimatorのApply Root Motionのチェックを外しておきます。
AnimatorControllerの作成
操作キャラクターのAnimatorに設定するAnimatorControllerを作成します。
↑のような状態と遷移を作成し、Attack1、Attack2、Attack3Run、Attack3のインスペクタのTagにはAttackを指定しておきます。
アニメーションパラメータにはfloat型のSpeed、Trigger型のAttackを作成します。
IdleはHumanoidIdle、WalkはHumanoidWalk、Attack3RunはHumanoidRunというStandardAssetsのキャラクターに設定されているアニメーションを指定します。
Attack1、Attack2、Attack3は攻撃のアニメーションを指定してください。
Idle→WalkはHas Exit Timeのチェックを外し、条件にSpeedがGreaterで0.1を指定します。
Walk→IdleはHas Exit Timeのチェックを外し、条件にSpeedがLessで0.1を指定します。
Idle→Attack1とWalk→Attack1はHas Exit Timeのチェックを外し、条件にAttackがトリガーされた時を指定します。
Attack1→Attack2はHas Exit Timeにチェックを入れ、条件にAttackがトリガーされた時を指定します。
Attack2→Attack3RunはHas Exit Timeにチェックを入れ、条件にAttackがトリガーされた時を指定します。
Attack1→Idle、Attack2→Idle、Attack3Run→Attack3、Attack3→IdleはHas Exit Timeにチェックを入れ、条件には何も指定しません。
Has Exit Timeはアニメーションの再生が終わると遷移するので特別な条件を指定しなくてもアニメーションが終われば遷移します。
これでAttack1の状態でアニメーションパラメータのAttackがトリガーされたらAttack2・・・・と連続攻撃が出来るようになります。
Attack3の攻撃がされる前にAttack3Runで走り出すというアニメーションを挿入しています。
ここで注意があります。
例えばAttack1→IdleとAttack1→Attack2はHax Exit Timeにチェックが入っており時間が来た時にどちらに遷移させたらいいかわかりません。
↑の記事では別のやり方をしましたが、ここでは条件の遷移の順番を変えて対応してみます。
Attack1状態を選択しインスペクタを見ると以下のようになっています。
↑のようにTransitionsでAttack1→Attack2の遷移を上にドラッグしておきます。
他の状態のHax Exit Timeにチェックが入っている遷移を持っている状態では連続攻撃の遷移の方を上側にドラッグしておきます。
これでAnimatorControllerが出来ました。
キャラクター操作スクリプトの作成
次はキャラクターの操作をスクリプトを作成していきます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class ContinuityAttackChara : MonoBehaviour { private CharacterController characterController; private Animator animator; private Vector3 velocity; [SerializeField] private float walkSpeed = 1.5f; // Use this for initialization void Start () { characterController = GetComponent<CharacterController> (); animator = GetComponent<Animator> (); velocity = Vector3.zero; } // Update is called once per frame void Update () { if (characterController.isGrounded) { velocity = Vector3.zero; if (!animator.GetCurrentAnimatorStateInfo (0).IsTag ("Attack")) { var input = new Vector3 (Input.GetAxis ("Horizontal"), 0f, Input.GetAxis ("Vertical")); if (input.magnitude > 0f) { transform.LookAt (transform.position + input); animator.SetFloat ("Speed", input.magnitude); velocity = input * walkSpeed; } else { animator.SetFloat ("Speed", 0f); } } } // モーションの移動値で地面から離れる可能性がある為、接地条件外で判別 if (Input.GetButtonDown ("Fire1")) { animator.SetTrigger ("Attack"); } // Attackタグに遷移した時は移動値を重力だけにする if (animator.GetCurrentAnimatorStateInfo(0).IsTag("Attack")) { velocity = new Vector3(0f, velocity.y, 0f); } velocity.y += Physics.gravity.y * Time.deltaTime; characterController.Move (velocity * Time.deltaTime); } } |
アニメーションの値をキャラクターのTransformに反映させると攻撃中に地面と接地していないと判断されて攻撃ボタンが押せなくなるので、地面と接地しているかどうかの条件の外に攻撃ボタンを押したかどうかの条件を入れます。
また攻撃中はキーボードの操作でキャラクターを移動させたくないのでAnimatorControllerでAttackタグが設定された状態にいない時だけ移動出来るようにします。
また最初の攻撃時は移動値が反映されてしまう事があるので、Attackタグが設定された状態にいる時はvelocityのXとZの値を0にします。
これで操作キャラクターのスクリプトが出来ました。
試しに実行して確認してみると
↑のようになります。
連続攻撃は出来ていますが、AnimatorのApply Root Motionのチェックを外したままなのでアニメーションの値がキャラクターのTransformに反映されていません(その場でアニメーションするだけ)。
また最後のAttack3の攻撃中に攻撃ボタンを押しておくとAttack3が終わった時にはすぐにAttack1へと遷移してしまいます。
Behaviourを設定し、Apply Root Motionにチェックを入れる
スクリプトでApply Root Motionにチェックを入れたり外したりをしていきます。
Idle状態になった時にApply Root Motionのチェックを外し、Attack1状態になった時にApply Root Motionにチェックを入れればうまくいきそうです。
そこでIdle状態を選択し、インスペクタのAdd Behaviourボタンを押して新しくEndAttackBehaviourという名前のビヘイビアを作成します。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class EndAttackBehaviour : StateMachineBehaviour { // 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) { animator.applyRootMotion = false; animator.ResetTrigger ("Attack"); } // 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) { // //} // OnStateMove is called right after Animator.OnAnimatorMove(). Code that processes and affects root motion should be implemented here //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { // //} // OnStateIK is called right after Animator.OnAnimatorIK(). Code that sets up animation IK (inverse kinematics) should be implemented here. //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { // //} } |
OnStateEnterメソッドはその状態に入った時に呼ばれるので、そこでAnimatorのApply Root Motionのチェックを外します。
またAttack3の状態の時にAttackトリガーがされているとそのままAttack1に遷移してしまいますので、ここでAttackトリガーをキャンセルし、Attack3状態の時に攻撃ボタンを押してもキャンセルするようにします。
次はAttack1状態を選択しインスペクタのAdd Behaviourボタンを押してStartAttackBehaviourビヘイビアを作成し取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class StartAttackBehaviour : StateMachineBehaviour { // 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) { animator.applyRootMotion = true; } // 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) { // //} // OnStateMove is called right after Animator.OnAnimatorMove(). Code that processes and affects root motion should be implemented here //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { // //} // OnStateIK is called right after Animator.OnAnimatorIK(). Code that sets up animation IK (inverse kinematics) should be implemented here. //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { // //} } |
こちらはAnimatorのApply Root Motionにチェックを入れるだけです。
試しにIdle状態を見てみると、
↑のような感じになります。
Attack1状態もほぼ同じです。
これで機能が完成しました!