今回はUnityのアニメーションイベントについてやっていきます。
すでにいくつもの記事でアニメーションイベントを使用した処理を記述していますが改めてアニメーションイベントに特化した記事を書こうと思いました。
アニメーションイベントは何に使えるかと言うと、例えば
といった時に使え。
剣を振った時が2項目ありますが、まぁスルーしていきましょう・・・・・(;一_一)
サンプルのキャラクターを作成
アニメーションイベントを使用する前にサンプルとして使用するキャラクターを作成します。
この記事で作成するキャラクターはアニメーションイベントの確認の為に使用する為、多少作りこんだものになっています。
今回の記事とは直接関係はないので無理に作って確認しなくてももっと簡易な方法で試してみてください。
具体的なキャラクターの作り方は
を参照してください。
StandardAssetsにあるモデルのEthanをシーンに配置しEthanのインスペクタにCharacterControllerコンポーネントを取りつけコライダのサイズを調整し、音声再生用のAudioSourceを取りつけます。
AudioSourceのPlay On Awakeのチェックは外しておきます。
EthanにはSaishouMoveCSharpというスクリプトを作成し取りつけます。
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 | using UnityEngine; using System.Collections; public class SaishouMoveCSharp : MonoBehaviour { private Animator animator; private AnimationClip attackClip; private CharacterController characterController; [SerializeField] private float walkSpeed = 2f; private Vector3 velocity; public float jumpPower; void Start () { animator = GetComponent<Animator>(); characterController = GetComponent<CharacterController>(); velocity = Vector3.zero; } void Update () { // 地面に接地してる時は初期化 if (characterController.isGrounded) { velocity = Vector3.zero; // 着地していたらアニメーションパラメータのJumpをfalse animator.SetBool ("Jump", false); // 入力値を計算 var input = new Vector3 (Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); // 方向キーが多少押されている if (input.magnitude > 0.1f && !animator.GetCurrentAnimatorStateInfo (0).IsTag ("Attack") ) { animator.SetFloat ("Speed", input.magnitude); transform.LookAt (transform.position + input); velocity += input.normalized * walkSpeed; // キーの押しが小さすぎる場合は移動しない } else { animator.SetFloat ("Speed", 0); } if (Input.GetButtonDown ("Fire1")) { animator.SetTrigger ("Attack"); } // ジャンプ if (Input.GetButtonDown ("Jump") && !animator.GetCurrentAnimatorStateInfo (0).IsName ("Jump") && !animator.IsInTransition (0) ) { animator.SetTrigger ("Jump"); velocity.y += jumpPower; } } velocity.y += Physics.gravity.y * Time.deltaTime; characterController.Move(velocity * Time.deltaTime); } } |
スクリプトの具体的な解説は先ほど紹介した他の記事内でされているのでそちらを参照してください。
とりあえずキャラクターが移動、ジャンプ、攻撃が出来るようにしたスクリプトになります。
スクリプト名は毎度の事ながらご愛敬ということで・・・・・(^_^;)
EthanのインスペクタのAnimatorに設定するAnimatorControllerは
Idle→RunはSpeedが0.1以上
Run→IdleはSpeedが0.1以下
Idle→JumpはJump
Run→JumpはJump
Jump→IdleはHas Exit Timeにチェックを入れ、アニメーションが終わったらIdleにいくようにする。
Idle→AttackはAttack
Run→AttackはAttack
Attack→IdleはHas Exit Timeにチェックを入れ、アニメーションが終わったらIdleにいくようにする。
のように条件を設定します。
Attack状態のTagにはAttackを設定しておきます。
アニメーションイベントの作成
本当はここからが本題で、ここまでのキャラクター作りはおまけです。
アニメーションイベント自体は簡単なものなので安心してください。(^_^)v
走るアニメーションにアニメーションイベントを設定
まずはキャラクターが歩いた時に足音を鳴らしたいのでAnimatorのRunに設定したアニメーションクリップにアニメーションイベントを設定していきましょう。
Runに設定したアニメーションクリップを選択します。
↑のように大元を選択します。
インスペクタに
↑のようにアニメーションのインポートセッティングが表示されます。
今回の走るアニメーションは
↑のような感じです。
アニメーションのインポートセッティングで下の方にスクロールし、Events部分をクリックします。
↑の赤い四角の中のボタンを押すとアニメーションクリップの再生位置にアニメーションイベントが作られます。
現在のアニメーションの再生位置は下のウインドウで確認出来ますので、キャラクターが地面に足を付いた位置を調べそこにStepというアニメーションイベントを作成します。
アニメーションが再生される下のウインドウの赤い縦線部分をクリックし足を着く位置を探します。
0秒付近でも足を着いていますがこれはアニメーションの最後の足を着いた時の繋がりなので無視します。
↑が最初に足をついた部分で0.08秒あたりです。
ここで先ほどのアニメーションイベント作成ボタンを押すと、
↑のようにアニメーションイベント設定ウインドウが開きますのでFunctionのNewEventとなっている部分をStepに書き換えます。
次に足を着く部分を探します。
↑のように0.16秒辺りですね。
ここにアニメーションイベントを作成しFunctionをStepにします。
↑がStepアニメーションイベントを設定した状態です。
これで足を着いた時にStepというアニメーションイベントが発生するようになります。
剣を振るアニメーションにアニメーションイベントを設定
次に剣を振り初めの時にAttackStart、振り終わりの時にAttackEndというアニメーションイベントを作成し剣のコライダをオン・オフ出来るようにします。
またAttackStartのタイミングで剣を振る音も再生するようにします。
まずは剣を振り始めのアニメーションイベントを作成します。
↑のような位置でアニメーションイベントを作成しFunctionにAttackStartと入れます。
次は剣を振り終わった時です。
↑のような位置にAttackEndというアニメーションイベントを作成します。
これで剣を振るアニメーションイベントの作成が終わりました。
アニメーションイベントのレシーバースクリプトを作成
アニメーションイベントは発生するようになりましたが、アニメーションイベントをスクリプトで受け取らなければいけません。
アニメーションイベントはAnimatorと同じ階層に送られるので、Animatorが設定されているEthanにアニメーションイベント受け取り用のスクリプトを設定します。
仮にアニメーション受け取り用のスクリプトを設定していないと、
と、AnimationEventのレシーバーがないとエラーになります。
アニメーションイベントの作成が出来たのでアニメーションイベントの受け取りスクリプトを作成しましょう。
スクリプトはEthanに設定します。
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 | using UnityEngine; using System.Collections; public class ReceiveEvent : MonoBehaviour { public Collider col; // 剣のコライダ public AudioSource audioSource; // AudioSource public AudioClip[] se; // 効果音の配列 // 足が地面に付いた時のイベント void Step() { Debug.Log ("Step"); audioSource.PlayOneShot (se[0]); } // 剣を振りおろす時のイベント void AttackStart() { audioSource.PlayOneShot (se[1]); col.enabled = true; } // 剣を振り終わった時のイベント void AttackEnd() { col.enabled = false; } } |
インスペクタで剣のコライダ、AudioSource、鳴らす効果音を設定出来るようにします。
StepメソッドがアニメーションイベントのStepを受け取り実行される処理です。
Stepを受け取ったら足音の効果音を鳴らします。
AttackStartでは剣を振る効果音の再生と剣のコライダをオンにしています。
AttackEndでは剣のコライダをオフにしています。
こうすることで剣を振る前や剣を振り終わった後に敵との接触判定をしないようにする事が出来ます。
今回の記事とは関係ないですが、コライダのオン・オフをしなくても変数としてフラグを用意しておき、剣の当たり判定部分でそのフラグを見て剣の当たり判定をするかどうかを判断する事も出来ます。
例えばisColliderValidを用意し、
1 2 3 4 5 6 7 8 9 10 11 12 13 | void AttackStart() { isColliderValid = true; } void AttackStart() { isColliderValid = false; } public bool IsColliderValid() { return isColliderValid; } |
AttackStartとAttackEndでフラグのオン・オフをし、剣が他のゲームオブジェクトと接触した時にIsColliderValidメソッドを使ってフラグの状態を見て、剣の当たり判定に利用します。
こうすることでコライダのオン・オフと同じような事が出来ます。
これでスクリプトが出来たのでEthanに取り付けパラメータを設定しましょう。
Colにはキャラクターが持っている剣のコライダを設定し、AudioSourceには同じ階層に設定したAudioSourceを設定します。
SeのSizeを2にしElement0には足音の効果音、Element1には剣を振る時の効果音を設定します。
これで完成したのでUnityを実行し確認してみましょう。
↑のようにアニメーションイベントのタイミングで音声が鳴っています。
スクリプトからアニメーションイベントを設定する
ここまででアニメーションイベントの作り方とイベントの受け取りが出来ました。
しかしアニメーションを他のキャラクターでも使いまわしをしていてアニメーションイベントが発生すると困る場合やアニメーションクリップ自体にアニメーションイベントを設定するのが嫌な場合はスクリプトからアニメーションイベントを設定する事も出来ます。
スクリプトから剣を振り初めと走っている時の足音のアニメーションイベントを作成してみます。
SaishouMoveCSharpに追記します。
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 | void Start() { animator = GetComponent<Animator>(); // アニメーションクリップの情報を取得 foreach (var clip in animator.runtimeAnimatorController.animationClips) { if (clip.name == "Attack1") { attackClip = clip; } else if (clip.name == "MyRun") { runClip = clip; } } // 攻撃アニメーションにアニメーションイベントを追加 if (attackClip != null) { AnimationEvent attackAnimationEvent = new AnimationEvent() { functionName = "AttackStartScriptEvent", time = 0.08f, }; attackClip.AddEvent(attackAnimationEvent); } if (runClip != null) { AnimationEvent[] runAnimationEvents = { new AnimationEvent { functionName = "RunStepScriptEvent", time = 0.2f }, new AnimationEvent { functionName = "RunStepScriptEvent", time = 0.5f } }; runClip.events = runAnimationEvents; } characterController = GetComponent<CharacterController>(); velocity = Vector3.zero; } |
Animatorに設定されているアニメーションクリップの情報を全て取得し、そのクリップの中の剣をふるアニメーションの名前がAttack1のもの、MyRunのものを探します。
剣を振るアニメーションクリップの名前がAttack1なので判定文字列にAttack1を使っています。
剣を振るアニメーションクリップの名前がSword_Attackという名前ならば判定文字列もSword_Attackとします。
attackClip、runClipにそのアニメーションクリップを保存しループを抜け出します。
AnimationEventクラスをインスタンス化しアニメーションイベント名とアニメーションイベントが発生する時間を設定します。
設定が終了したらAddEventを使用しそのアニメーションクリップにアニメーションイベントを設定します。
attackClipにはアニメーションイベントを一つ付けるだけなのでAddEventを使っていますが、runClipには二つ付けるのでrunClip.eventsにアニメーションイベントの配列を入れる形で設定しています。
これでスクリプトからUnity再生中のみアニメーションイベントを設定する事が出来ます。
走って足をついた時の秒数を指定しましたが、どうにも実際とはズレてしまう為に実行しながらAnimationEventのtimeの値は調べました。
スクリプトからアニメーションイベントを設定すれば手動でアニメーションイベントを設定し後が残るような事がないので他で使いまわしをしレシーバースクリプトがなくてもエラーになりません。
ReceiveEventにメソッドを定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // スクリプトで設定したアニメーションイベント(剣を振りおろす時) void AttackStartScriptEvent() { Debug.Log ("AttackStartScript"); audioSource.PlayOneShot (se[1]); col.enabled = true; } // スクリプトで設定したアニメーションイベント(走る時) void RunStepScriptEvent() { Debug.Log("RunStepScript"); audioSource.PlayOneShot(se[0]); Debug.Log("長さ" + GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).normalizedTime); } |
RunStepScriptEventメソッドでは現在再生しているアニメーションの再生時間を表示していますが、これは指定したアニメーションイベントの時間と実際のアニメーションクリップの時間がどうにも合わない為に表示してtimeの値を合わせるのに使った為です。
それではAttackStartメソッド、Stepメソッドの中身をコメント化し実行してみましょう。
↑のようにスクリプトで設定したアニメーションイベントが実行されているのがわかります。
ただしスクリプトで設定したアニメーションイベントは少し問題があります。
手動で設定した時よりも正確なアニメーションの位置でイベントが発生していない感じがあります。
今回の剣を振るアニメーション自体のアニメーションの長さが短いせいもあるかもしれませんが・・・・(^_^;)
より正確な位置でイベントを発生させたい時は手動で細かく設定していった方がいいかもしれませんね。