Unityのゲームで、キャラクターが元気な状態なのか、疲れている状態なのかでAnimatorの状態や遷移条件はそのままに、アニメーションクリップだけを替えたい事があります。
スクリプトを使えば、Animatorの歩いている状態(Walk)に設定されているアニメーションクリップを別のものに切り替える、ということでも実現出来そうです。
ですが、そんな時はAnimatorでベースとなるレイヤーと、シンクロした新しいレイヤーを作成した方が簡単です。
新しいレイヤーのBlendingをOverrideにし、ウエイトが0の場合は、ベースとなるレイヤー(通常状態)の状態と遷移を使い、1の場合は新しいレイヤー(疲れた状態)の状態と遷移を使う事が出来ます。
つまり、スクリプトから新しいレイヤーのウエイトを操作し、Animatorの状態と遷移はそのままにアニメーションを変更する事が出来ます。
キャラクターに設定したAnimatorに新しいレイヤーを作成する
まずは、キャラクターに設定しているAnimatorControllerに元気な時のレイヤーを作成してみましょう。
↑がキャラクターが元気な時の状態と遷移を作成したものです。
アニメーションパラメータにFloat型のSpeedを作成し、Idle→WalkはSpeedがGreaterで0.1、Walk→IdleはSpeedがLessで0.1という条件で遷移するように作成します。
IdleにはスタンダードアセットのEthanのIdle、WalkにはスタンダードアセットのEthanのWalkを設定し、普通に立っている、歩いているという状態になります。
次にキャラクターが疲れた状態のレイヤーを作成します。
↑の左上のLayersを押し、その右下の+を押します。
名前をTired Layerとしたら、右の歯車をクリックし、Syncにチェックを入れます。
すると、Source Layerに指定したレイヤーの状態と遷移がそのまま現れますが、アニメーションクリップが設定されていません。
Idle、Walkにキャラクターが疲れた時用のアニメーションクリップを設定します。
Tired LayerのBlendingをOverrideとしているので、上のWeightが1になればTired Layerの状態と遷移、Weightが0になればBase Layerの状態と遷移が使われるようになります。
つまり、このWeightをスクリプトから操作すれば、キャラクターのアニメーションだけを切り替える事が出来ます。
キャラクター操作スクリプトの作成
キャラクター操作スクリプトを作成し、Tired Layerのウエイトを操作していきます。
基本のキャラクターの移動やアニメーションの遷移条件の変更等は、
を参考にしてください。
キャラクター操作スクリプトの概要
今回のキャラクター操作スクリプトの処理を考えていきます。
キャラクターを動かしている状態(移動キーを押している)が一定時間続いたら、キャラクターを疲れている状態にします。
こまめに移動キーを離していれば、疲れは溜まらないとします。
キャラクターが疲れた状態になっている時に、その場で一定時間じっとしていれば元気な状態に戻るようにします。
疲れた状態で少しでも移動キーを押すと、回復までの経過時間がリセットされ、また最初から時間の計測をする事にします。
キャラクターが元気な状態か、疲れた状態かはTired Layerを取得し、レイヤーのウエイトをスクリプトから操作する事で切り替えます。
状態を変化させた時に、いきなりウエイトをパッと切り替えてしまうと、アニメーションもいきなり変化しておかしくなるので、ウエイトは徐々に変化させていきます。
疲れた状態になる時間、回復までの時間等はインスペクタで設定出来るようにします。
スクリプトの実装
作成するスクリプトの処理がわかったので、スクリプトを作成していきます。
フィールド宣言とStartメソッド
まずはフィールド宣言とStartメソッド内の初期化処理です。
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 UnityEngine; using System.Collections; public class ChangeAnimator : MonoBehaviour { // キャラクターの状態 enum State { Normal, Tired } private CharacterController cCon; private Animator animator; // 通常の歩くスピード [SerializeField] private float walkSpeed = 1.5f; // 疲れている時の歩くスピード [SerializeField] private float tiredWalkSpeed = 1f; // 最後に計算した歩くスピードを入れるフィールド private float nowSpeed; // 速度 private Vector3 velocity; // 状態 private State state; // Animatorの疲れた状態用レイヤー private int tiredLayer; // 疲れるまでの秒数 [SerializeField] private float timeToTiredness = 10f; // 歩き続けている秒数 [SerializeField]private float continuedWalkTime; // 普通の状態に戻るまでの時間 [SerializeField] private float recoveryTime = 5f; // 操作しなくなってからの時間 [SerializeField]private float idleTime; // 疲れる、回復する時に徐々にウエイトを変更する為のフラグ private bool smoothFlag; // ウエイト private float weight; // Use this for initialization void Start () { cCon = GetComponent <CharacterController> (); animator = GetComponent <Animator> (); velocity = Vector3.zero; tiredLayer = animator.GetLayerIndex ("Tired Layer"); nowSpeed = 0f; continuedWalkTime = 0f; idleTime = 0f; weight = 0f; // 最初の状態設定の時は第2引数にfirstフラグのtrueを渡してsmoothFlagを操作しない SetState(State.Normal, first : true); } |
キャラクターの状態を列挙型で作成しておきます。
キャラクターは元気な時と疲れた時で歩くスピードを変化させる為、インスペクタでそれぞれのスピードを設定出来るようにします。
疲れた時用のレイヤーTired Layerを取得する為、tiredLayerというint型のフィールドを宣言します。
Tired Layerのウエイトを操作する時に、レイヤー番号を使用する必要がある為にこのフィールドを使います。
tiredLayerはStartメソッド内で
animator.GetLayerIndex(“Tired Layer”)
と、レイヤー名でレイヤーのインデックス番号を取得し、代入しています。
smoothFlagは徐々にウエイトを操作する時に、今ウエイトを操作しているか?という状態フラグです。
キャラクターの状態を変化させた時にsmoothFlagをオンにし、ウエイトが0か1になったら(どちらの状態からどちらの状態へ変化したかで分かれる)SmoothFlagをオフにします。
ちなみに、フィールド宣言で使用している[SerializeField]はそのフィールドを直列化(シリアライズ化)することの宣言です。
クラス全体を直列化しデータとして保存する時には、publicなフィールドしか直列化出来ませんが、この[SerializeField]を付けておくとprivateなアクセス修飾子が付いていても直列化する事が出来ます。
また、privateでもインスペクタで値を設定する事が出来るようになります。
クラス全体の直列化に関しては、
を参照してください。
その他、説明していないフィールドはコメントを見て頂ければわかると思います。
Startメソッド内で
SetState(State.Normal, first : true);
という見慣れない?記述がありますが、これはSetStateというメソッドの第2引数にはtrueというbool値を渡すという事です。
first :
という見慣れない表記があるのは、いったい何のbool値を渡しているのか?をわかりやすくする為のC#?の記述の仕方です。
現時点でSetStateを作成していないので、このfirstが何なのかもわかりづらいですが・・・・(^_^;)
firstはSetStateを呼び出すのが最初かどうか?のbool値です。
first自体には意味がないので、単純に
SetState(State.Normal, true);
でいいんですが、何のbool値を渡しているかを解りやすくする為にfirstという文字を記述していただけです。
キャラクターの状態を変化させるメソッドがSetStateなんですが、処理の関係上、最初の呼び出しの時はTired Layerのウエイトを操作したくない為にこのフラグを使って、第2引数にtrueが渡ってきた時はウエイトを操作しないようにしています。
SetStateメソッド
Updateメソッドを見る前に、キャラクターの状態を変化させるSetStateメソッドを見ていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 状態変更メソッド、第2引数がなければfirstにはfalseが入る void SetState(State state, bool first = false) { this.state = state; // 最初の設定でなければスムーズフラグをオンにする if (!first) { smoothFlag = true; } if (state == State.Normal) { nowSpeed = walkSpeed; idleTime = 0f; } else if(state == State.Tired) { nowSpeed = tiredWalkSpeed; } } |
SetStateメソッドの第2引数の仮引数でfirstが宣言されていますが、firstにfalseを代入しています。
これはSetStateを呼び出した時に第2引数が指定されていなければ、firstにfalseを設定するという意味です。
ここら辺は
で解説しているので参照してみてください。
SetStateメソッドでは、キャラクターの状態フィールドstateを受け取った引数に変更し、firstがfalseだったらsmoothFlagをオンにし、ウエイト操作を行います。
元気な状態に変更した時は、nowSpeed(現在の歩くスピード)にwalkSpeedを設定し、idleTime(じっとして動かない時間)を0にします。
疲れた状態に変更した時はnowSpeedにtiredWalkSpeedを代入しています。
SetStateで行っている事はキャラクターの状態の変化と、その際に必要になるフィールドの変更ですね。
Updateメソッド
Updateメソッドではキャラクターの移動、アニメーションの切り替え、Tired Layerのウエイトの操作といった事を行います。
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 | void Update () { if (cCon.isGrounded) { velocity = Vector3.zero; velocity = new Vector3(Input.GetAxis ("Horizontal"), 0f, Input.GetAxis("Vertical")); if (velocity.magnitude > 0f) { animator.SetFloat ("Speed", 1f); transform.LookAt (transform.position + velocity); //操作を続けている時はcontinuedWalkTimeを増やしていく if(state == State.Normal) { continuedWalkTime += Time.deltaTime; // 疲れている状態で動きだしたら回復までの時間をリセット } else if (state == State.Tired) { idleTime = 0f; } } else { animator.SetFloat ("Speed", 0f); // 動いていなければ、continuedWalkTimeは0にリセット continuedWalkTime = 0f; // 疲れた状態で、操作していない時はidleTimeを増やしていく if (state == State.Tired) { idleTime += Time.deltaTime; } } // 歩き続けた場合、疲れる状態にする if (state == State.Normal) { if (continuedWalkTime >= timeToTiredness) { SetState (State.Tired); } // 十分休んだらノーマル状態にする } else if (state == State.Tired) { if (idleTime >= recoveryTime) { SetState (State.Normal); } } // 徐々に状態を変化させる if(smoothFlag) { if (state == State.Normal) { weight -= Time.deltaTime; if (weight <= 0f) { weight = 0f; smoothFlag = false; Debug.Log ("TiredToNormal"); } } else if (state == State.Tired) { weight += Time.deltaTime; if (weight >= 1f) { weight = 1f; smoothFlag = false; Debug.Log ("NormalToTired"); } } // Tired Layerのウエイトを操作し、徐々に変化させる animator.SetLayerWeight (tiredLayer, weight); } } velocity.y = Physics.gravity.y * Time.deltaTime; cCon.Move (velocity * nowSpeed * Time.deltaTime); } |
現在、元気な状態の時は疲れた状態へと遷移する為の時間(continuedWalkTime)を増やしていきます。
疲れている状態の時は、回復までの経過時間(idleTime)を最初からやり直す為0を代入しています。
疲れた状態へと遷移する為の時間(continuedWalkTime)を0にリセットします。
現在、疲れた状態の時は回復までの経過時間(idleTime)を足していきます。
元気な状態、疲れた状態でcontinuedWalkTime、idleTimeと比較し、キャラクターの状態を変更します。
smoothFlagがオンの時は、元気な状態か疲れた状態かでウエイトを徐々に足したり引いたりして操作します。
ウエイトが、それぞれ指定する値(0か1)を越えた時にsmoothFlagをオフにし、ウエイトの操作を終了します。
ウエイトの操作はMathf.MoveTowardsでも出来そうですね。
最後に
animator.SetLayerWeight (tiredLayer, weight);
でTired Layerのウエイトを変化させたweightに設定します。
これでスクリプトの作成は終了です。
キャラクターを操作しアニメーションが変化するか確認
機能が完成したので、キャラクターを動かしてレイヤーのウエイトが変化、アニメーションの変化を確認してみましょう。
↑のようになりました。
アニメーションの切り替えを、ウエイトを操作するだけで行う事が出来るので、疲れた状態のレイヤー、ダメージを負った時のレイヤーと切り替える事が出来ますね。
これはかなり使えるかも!?
と自画自賛してみたり・・・・(;一_一)