今回はUnityでアクションゲームを作った時に主人公が連続攻撃を出来るようにします。
例えばマウスの左クリックをすると上から剣を振りおろす攻撃をします(そうプログラミングした場合)が、剣を振りおろしている最中にもう一度マウスの左クリックをすると剣を振りおろした後に左横から右横へと剣を振る別のアニメーションの攻撃を行います。
連続攻撃にする為には現在の攻撃が行われている最中にマウスの左クリックを押す必要があり、最初の振りおろす攻撃が終わった後にマウスの左クリックを押した場合は振りおろす攻撃になります。
今回の機能を作成すると、
↑のような感じのものが出来上がります。
↑の動画は後から作成したものなのでアニメーションが打撃系に変わっています・・・・(^_^;)
この連続攻撃の機能を作るには2つの方法があります。
- アニメーターのBehaviourスクリプトで制御
- 通常のスクリプトで再生アニメーションを調べ制御
の方法になります。
通常のスクリプトで制御すると結構面倒臭い事になるので、Behaviourスクリプトで制御します。
まずは機能を作成する前に攻撃のアニメーションと装備する武器を用意します。
剣とアニメーションをAssetStoreで探す
AssetsStoreで無料のものを使わせて頂きましょう。
Warrior Pack Bundle 2 FREE
1H_Swing_Low_Left
上の2つが攻撃用のアニメーション。
Simple Medieval Sword
上が武器の3Dオブジェクトになります。
まったく同じものを使用する必要はなくご自分で用意したものをお使いください。
上記のAssetは現在わたくしのUnityバージョンだとダウンロード出来ないので、
Animations Kick
で検索して出てくるキックアニメーションを使うといいかもしれません。
武器をキャラクターに装備させる
まずはキャラクターに武器を装備させます。
EthanRightArmの子要素に武器をドラッグ&ドロップし位置を調整します。
武器の合わせ方は
を参照してください。
アニメーターコントローラを作成する
次にキャラクターのアニメーターコントローラを開きアニメーションパラメータにAttackをBool型かもしくはTrigger型で作成しておきます。
マウスの左クリックを押した時にこのAttackをOnにします。
上のようにアニメーターコントローラーの遷移を繋ぎます。
Attack1からAttack2、Attack3と攻撃が遷移します。
AttackをBoolにした場合
IdleとRunからAttack1 HasExitTimeのチェックを外し、Attackがtrueという条件
Attack1→Idle HasExitTimeにチェックを入れ、Attackがfalseという条件
Attack1→Attack2 HasExitTimeにチェックを入れ、Attackがtrueという条件
Attack2→Idle HasExitTimeにチェックを入れ、Attackがfalseという条件
Attack2→Attack3 HasExitTimeにチェックを入れ、Attackがtrueという条件
Attack3→Idle HasExitTimeにチェックを入れ、条件はなし
という流れを作成します。
AttackをTriggerにした場合
IdleとRunからAttack1 HasExitTimeのチェックを外し、Attackという条件
Attack1→Idle HasExitTimeにチェックを入れる
Attack1→Attack2 HasExitTimeにチェックを入れ、Attackという条件
Attack2→Idle HasExitTimeにチェックを入れる
Attack2→Attack3 HasExitTimeにチェックを入れ、Attackという条件
Attack3→Idle HasExitTimeにチェックを入れる
という流れを作りますが、Attack1→Idle、Attack1→Attack2等の条件でHas Exit Timeに両方チェックを入れている為、Transitionsの順序によってはAttack2に行きません。
そこでTransitionsの順序を変更します。
↑のようにAttack1状態を選択し、インスペクタでAttack1→Attack2を上にドラッグして順序を変えておきます。
Attack2の状態も同じようにAttack2→Attack3の状態を上にします。
AnimatorControllerについては
を参照してください。
Attack1~Attack3までそれぞれの攻撃アニメーションをセットしてください。
わたくしは
Attack1にKnight@Attack1.FBX
Attack2に51_1H_Hero@1H_sword_swing_low_left_humanoid.FBX
Attack3にMage@Attack1.FBX
を設定してみました。
これでアニメーターで攻撃の遷移が作成出来ました。
アニメーターコントローラの遷移方法を考える
次にAttack1のアニメーション再生中にマウスの左クリックを押した時にAttack2に遷移させる方法を考えます。
遷移するには現在Attack1のアニメーションが再生中である事を検出する事、アニメーションパラメータのAttackがOnである事がわからなければなりません。
またアニメーションパラメータのAttackはIdleやRunからAttack1に遷移した時にOnにしたまま(攻撃に遷移する際に必ずOnにしているはず、その機能はまだ作っていませんが)Offにしていないので、Attack1に遷移したらOffにする必要もあります(Trigger型にした場合はする必要はありません)。
これらの処理をまとめて行います。
アニメーターコントローラのBehaviourスクリプトを作成
アニメーターコントローラのBehaviourスクリプトを使います。
Attack1を選択すると上のようにインスペクタが表示されます。
Add BehaviourボタンをクリックしBehaviourスクリプトのContinuityAttackBehaviourを作成します。
Behaviourスクリプトを作成するとデフォルトでコメント化されているメソッドが記述されています。
BehaviourはC#でしか作れないみたい?(JavaScriptでも作成出来ます)。
Behaviourに関しては
を参照してください。
連続攻撃用BehaviourのContinuityAttackBehaviourを記述する
AttackをBoolで作成した場合
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 | using UnityEngine; using System.Collections; public class ContinuityAttackBehaviour : 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.SetBool ("Attack", false); } // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { if(Input.GetButtonDown("Fire1")) { animator.SetBool ("Attack", true); } } // 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) { animator.SetBool ("Attack", false); } // 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(この状態に入ったら)
OnStateUpdate(この状態である場合)
OnStateExit(この状態を抜ける時)
のコメントを外します。
それぞれの関数(C#なのでメソッドですかね?)はカッコ内で記述したタイミングで呼ばれます。
さきほどの処理をタイミングに応じて変更していきます。
この状態に入ったらアニメーションパラメータAttackをfalseにする
この状態である場合、マウスの左クリックを押したらAttackをtrueにする
この状態を抜ける時、Attackをfalseにする
Attackの値の取り扱いで注意すべき事
状態を抜ける時にAttackをfalseにしていますが、この状態に入った時にfalseにしてるので
要らないと思うかもしれません。
しかし、この条件がないとAttack3からIdleへと遷移する時に、アニメーションパラメータのAttackがOnの時、つまりAttack3のアニメーション再生中にマウスの左クリックを押していた時は、Idleに遷移した後すぐにAttack1へと移行してしまいます。
通常のスクリプトから制御する事も可能だと思いますが、複雑化していくのでBehaviourで制御した方がいいかもしれません。
もちろんAttack3→Idle→Attack1と連続して遷移させたい場合はこの条件は要りません。
AttackをTriggerで作成した場合
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 | using UnityEngine; using System.Collections; public class ContinuityAttackBehaviour : 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.ResetTrigger ("Attack"); } // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { if(Input.GetButtonDown("Fire1")) { animator.SetBool ("Attack", true); } } // 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(stateInfo.IsName ("Attack3")) { animator.ResetTrigger ("Attack"); } } // 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) { // //} } |
AttackをTriggerで作成した場合はOnStateEnterでanimator.ResetTrigger(“Attack”)でトリガーをキャンセルします。
OnStateUpdateでFire1ボタンを押したらAttackをトリガーします。
animator.SetBoolになっていますが、トリガーの場合SetBoolを使ってもトリガーされます。
OnStateExitではAttack3状態の時だけAttackをリセットさせます。
これは連続攻撃の最後の攻撃中にFire1ボタンを押した時Attack1へと遷移するのを避けるためで、Attack3状態を抜ける時は必ずリセットします。
これでBehaviourスクリプトが完成したのでAttack2、Attack3の状態にもこのContinuityAttackBehaviourを追加してください。
キャラクター操作スクリプトでマウスの左クリックをした時に攻撃させる処理を追加する
最後にキャラクター操作スクリプトに攻撃処理を加えます。
サンプル用にContinuityAttackPlayerスクリプトを作成し取り付けます。
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 | using UnityEngine; using System.Collections; public class ContinuityAttackPlayer : MonoBehaviour { private Animator animator; private CharacterController characterController; private Vector3 velocity; [SerializeField] private float walkSpeed = 1.5f; [SerializeField] private float jumpPower = 5f; void Start () { animator = GetComponent<Animator>(); characterController = GetComponent<CharacterController>(); velocity = Vector3.zero; } void Update () { if(characterController.isGrounded) { velocity = Vector3.zero; var input = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical")); // 方向キーが多少押されている、攻撃中は移動させない if(input.magnitude > 0f && !animator.GetCurrentAnimatorStateInfo(0).IsName("Jump") && !animator.GetCurrentAnimatorStateInfo(0).IsName("Attack1") && !animator.GetCurrentAnimatorStateInfo(0).IsName("Attack2") && !animator.GetCurrentAnimatorStateInfo(0).IsName("Attack3") ) { animator.SetFloat("Speed", input.magnitude); transform.LookAt(transform.position + input); velocity += input.normalized * walkSpeed; // キーの押しが小さすぎる場合は移動しない } else { animator.SetFloat("Speed", 0); } if(Input.GetButtonDown("Jump") && !animator.GetCurrentAnimatorStateInfo(0).IsName("Jump") && !animator.GetCurrentAnimatorStateInfo(0).IsName("Attack1") && !animator.GetCurrentAnimatorStateInfo(0).IsName("Attack2") && !animator.GetCurrentAnimatorStateInfo(0).IsName("Attack3") && !animator.IsInTransition(0) ) { animator.SetBool("Jump", true); velocity.y += jumpPower; } else if(Input.GetButtonDown("Fire1") && !animator.GetCurrentAnimatorStateInfo(0).IsName("Jump") // ジャンプしていない && !animator.IsInTransition(0) // 遷移途中でない ) { animator.SetBool("Attack", true); } } velocity.y += Physics.gravity.y * Time.deltaTime; characterController.Move(velocity * Time.deltaTime); } } |
移動値を計算している個所に攻撃中は移動をさせたくない為、条件を追加します。
またマウスの左クリックが押されたらアニメーションパラメータのAttackをOnにします。
Trigger型のアニメーションパラメータはSetBoolを使ってもトリガー出来るのでスクリプトはBoolと共通にしました。
条件には他の状態にいない、や遷移途中でない事を条件に加えます。
IsInTransition(レイヤー番号)
で遷移途中かどうかを判断出来ます。
Unityの実行ボタンを押して確認してみましょう。
攻撃のアニメーションがバラバラなのであれですが、
流れるようなアニメーションを用意すれば綺麗に出来ると思います。
連続攻撃のアニメーション遷移が出来たので他に必要な処理も追加しよう
これでUnityのアクションゲームで連続攻撃を実現する方法が完成しました。
今回は連続攻撃のアニメーション遷移を作成しましたが、武器のコライダの設定、コライダのOn・Off、当たり判定のスクリプトは設定されていません。
その辺りは、
を参考にして作成してみてください。
アニメーターコントローラの遷移を分け、Behaviourで押したキーによって違う状態に遷移させると、違う攻撃の繋げ方を作成する事も出来ます。
多少処理が難しくなりそうですが、攻撃ボタンを押した時のアニメーションの再生位置によって次の攻撃を変更する。
といった事も出来そうですね。
格闘ゲームとかで使えそう(^_^)v