MRが楽しい

MRやVRについて学習したことを書き残す

Unityアニメーション講座 その4

本日はUnityアニメーションの講座の続きです。
f:id:bluebirdofoz:20170728041154j:plain

作成したプロジェクトに手を加えて、新しいアニメーションの追加にトライします。
bluebirdofoz.hatenablog.com

前回作成したプロジェクトを開きます。
f:id:bluebirdofoz:20170728041233j:plain

最初に、追加するアニメーションの新しい State を作成します。
まずは以下の手順で「UnityChanLocomotions」を開いてください。
 1.unitychan オブジェクトを選択する。
 2.Inpector タブにある Animator コンポーネント内の「UnityChanLocomotions」をダブルクリックする。
 3.画面中央に「UnityChanLocomotions」のフロー図が表示される。
f:id:bluebirdofoz:20170728041241j:plain

フロー図上で右クリックをして、Create State -> Empty を選択します。
f:id:bluebirdofoz:20170728041251j:plain

「New State」という新しい State が作成されます。
f:id:bluebirdofoz:20170728041301j:plain

今回はユニティちゃんの勝利ポーズのアニメーションを作成します。
以下の通り、State の名前とモーションを設定します。
 1.Inspector 上部の名前「Winner」を変更する。
 2.Motion : None(Motion) の右端にある ◎ ボタンをクリックする。
 3.表示されたダイアログから「WIN00」を選択する。
f:id:bluebirdofoz:20170728041312j:plain

Motion に WIN00 が設定されればOKです。前回学んだモーションの確認方法で WIN00 のモーションを確認できます。
f:id:bluebirdofoz:20170728041323j:plain


次に遷移条件の設定のため、「Winner」State へのトリガーとなる新しい Parameters 設定を追加します。
以下の手順で「WinBool」Parameter を作成してください。
 1.Parameters タブの右上にある + ボタンをクリックする。
 2.表示されたメニューから Bool を選択する。
 3.作成された Parameter に「WinBool」という名前を設定する。
f:id:bluebirdofoz:20170728041339j:plain

さて、追加した「WinBool」Parameter を元に Transition を作成し、「Idle」State から「Winner」State へ遷移を設定します。
「Idle」State 上で右クリックをして、Make Transition を選択します。
f:id:bluebirdofoz:20170728041352j:plain

矢印が表示されるので、そのまま「Winner」State をクリックします。
すると「Idle」State から「Winner」State へ矢印線が結ばれます。
f:id:bluebirdofoz:20170728041402j:plain

Transition の設定を行います。作成した矢印線をクリックして Inspector タブを開いてください。
f:id:bluebirdofoz:20170728041415j:plain

まずは以下の手順で Conditions の設定を行い、遷移条件を設定しましょう。
 1.Conditions の右下にある + ボタンをクリックすると、条件式が追加される。
 2.追加された条件を「WinBool : true」の条件で設定する。
f:id:bluebirdofoz:20170728041426j:plain

次に遷移のアニメーションの繋がりを設定します。
そのままの状態で「再生」ボタンを押してアニメーションを確認してみてください。
f:id:bluebirdofoz:20170728041437j:plain
勝利ポーズにすぐに遷移せず、立ちポーズの状態が長く続くアニメーションになっていると思います。

トリガーが発生すれば、すぐにポーズを遷移したいので、アニメーションの繋がりを調整します。
Settings ウィンドウの Winner ブロックを前方に移動させてください。
f:id:bluebirdofoz:20170728041448j:plain

合わせて、Idle ブロックから Winner ブロックへの遷移タイミングを示す青枠についても前方に移動させます。
f:id:bluebirdofoz:20170728041459j:plain

この状態で再び「再生」ボタンを押してアニメーションを確認してみてください。
先ほどと比べ、すぐに勝利ポーズへの遷移が始まることが確認できます。

更にもう一つ、Has Exit Time のチェックを外してください。
f:id:bluebirdofoz:20170728041604j:plain
Has Exit Time が無効の場合、「Idle」State の立ちモーションのアクションを即座にキャンセルして次のモーションに遷移します。
このチェックを外していないと例え、すぐに勝利ポーズへ遷移が始まるアニメーションを設定していても、その前の立ちモーションの完了を待ってしまうため、即座にモーションが遷移しません。


「Idle」State から「Winner」State への遷移が設定できました。しかし、このままだと一度「Winner」State に入ってしまうと「Idle」State に戻ってきません。
「Winner」State から「Idle」State への遷移も設定しておきましょう。
Transition を「Winner」State から「Idle」State に延ばします。
f:id:bluebirdofoz:20170728041653j:plain

「Winner」State から「Idle」State へ Transition の設定項目ですが、こちらは変更は不要です。
Conditions の設定がない場合、無条件で遷移が発生します。
このため、「Idle」State から「Winner」State への遷移が発生すると、その後、必ず「Winner」State から「Idle」State に戻る形となります。
そしてこのとき、「Winner」State から「Idle」State への遷移では Has Exit Time が無効されていないため、「Winner」State のモーションが一通り再生された後、「Idle」State の立ちポーズに戻るという流れになる訳です。


フローの設定はこれで完了です。
後はスクリプトにて、トリガーの発生を実装しましょう。
以下の手順を実施し、スクリプトファイルを開いてください。
 1.unitychan オブジェクトを選択する。
 2.Inpector タブにある UnityChanControlScriptWithRgidBody(Script) コンポーネント内にある
  Script「UnityChanControlScriptWithRgidBody」をダブルクリックする。
f:id:bluebirdofoz:20170728041720j:plain

UnityChanControlScriptWithRgidBody.cs が個別のテキストエディタで開かれます。
Winアニメーション用に修正した UnityChanControlScriptWithRgidBody.cs を以下に添付します。内容を上書きしてください。
・UnityChanControlScriptWithRgidBody.cs

//
// Mecanimのアニメーションデータが、原点で移動しない場合の Rigidbody付きコントローラ
// サンプル
// 2014/03/13 N.Kobyasahi
//
using UnityEngine;
using System.Collections;

namespace UnityChan
{
// 必要なコンポーネントの列記
    [RequireComponent(typeof(Animator))]
    [RequireComponent(typeof(CapsuleCollider))]
    [RequireComponent(typeof(Rigidbody))]

    public class UnityChanControlScriptWithRgidBody : MonoBehaviour
    {

        public float animSpeed = 1.5f;                // アニメーション再生速度設定
        public float lookSmoother = 3.0f;            // a smoothing setting for camera motion
        public bool useCurves = true;                // Mecanimでカーブ調整を使うか設定する
        // このスイッチが入っていないとカーブは使われない
        public float useCurvesHeight = 0.5f;        // カーブ補正の有効高さ(地面をすり抜けやすい時には大きくする)

        // 以下キャラクターコントローラ用パラメタ
        // 前進速度
        public float forwardSpeed = 7.0f;
        // 後退速度
        public float backwardSpeed = 2.0f;
        // 旋回速度
        public float rotateSpeed = 2.0f;
        // ジャンプ威力
        public float jumpPower = 3.0f; 
        // キャラクターコントローラ(カプセルコライダ)の参照
        private CapsuleCollider col;
        private Rigidbody rb;
        // キャラクターコントローラ(カプセルコライダ)の移動量
        private Vector3 velocity;
        // CapsuleColliderで設定されているコライダのHeiht、Centerの初期値を収める変数
        private float orgColHight;
        private Vector3 orgVectColCenter;
        private Animator anim;                            // キャラにアタッチされるアニメーターへの参照
        private AnimatorStateInfo currentBaseState;            // base layerで使われる、アニメーターの現在の状態の参照

        private GameObject cameraObject;    // メインカメラへの参照
        
        // アニメーター各ステートへの参照
        static int idleState = Animator.StringToHash ("Base Layer.Idle");
        static int locoState = Animator.StringToHash ("Base Layer.Locomotion");
        static int jumpState = Animator.StringToHash ("Base Layer.Jump");
        static int restState = Animator.StringToHash ("Base Layer.Rest");

// ++++++++++++++++++++++++++++++++++++ 勝利ポーズの追加 開始 ++++++++++++++++++++++++++++++++++++
        static int winState = Animator.StringToHash("Base Layer.Winner");
// ++++++++++++++++++++++++++++++++++++ 勝利ポーズの追加 終了 ++++++++++++++++++++++++++++++++++++

        // 初期化
        void Start ()
        {
            // Animatorコンポーネントを取得する
            anim = GetComponent<Animator> ();
            // CapsuleColliderコンポーネントを取得する(カプセル型コリジョン)
            col = GetComponent<CapsuleCollider> ();
            rb = GetComponent<Rigidbody> ();
            //メインカメラを取得する
            cameraObject = GameObject.FindWithTag ("MainCamera");
            // CapsuleColliderコンポーネントのHeight、Centerの初期値を保存する
            orgColHight = col.height;
            orgVectColCenter = col.center;
        }
    
    
        // 以下、メイン処理.リジッドボディと絡めるので、FixedUpdate内で処理を行う.
        void FixedUpdate ()
        {
            float h = Input.GetAxis ("Horizontal");                // 入力デバイスの水平軸をhで定義
            float v = Input.GetAxis ("Vertical");                // 入力デバイスの垂直軸をvで定義
            anim.SetFloat ("Speed", v);                            // Animator側で設定している"Speed"パラメタにvを渡す
            anim.SetFloat ("Direction", h);                         // Animator側で設定している"Direction"パラメタにhを渡す
            anim.speed = animSpeed;                                // Animatorのモーション再生速度に animSpeedを設定する
            currentBaseState = anim.GetCurrentAnimatorStateInfo (0);    // 参照用のステート変数にBase Layer (0)の現在のステートを設定する
            rb.useGravity = true;//ジャンプ中に重力を切るので、それ以外は重力の影響を受けるようにする
        
        
        
            // 以下、キャラクターの移動処理
            velocity = new Vector3 (0, 0, v);        // 上下のキー入力からZ軸方向の移動量を取得
            // キャラクターのローカル空間での方向に変換
            velocity = transform.TransformDirection (velocity);
            //以下のvの閾値は、Mecanim側のトランジションと一緒に調整する
            if (v > 0.1) {
                velocity *= forwardSpeed;        // 移動速度を掛ける
            } else if (v < -0.1) {
                velocity *= backwardSpeed;    // 移動速度を掛ける
            }
        
            if (Input.GetButtonDown ("Jump")) {    // スペースキーを入力したら

                //アニメーションのステートがLocomotionの最中のみジャンプできる
                if (currentBaseState.nameHash == locoState) {
                    //ステート遷移中でなかったらジャンプできる
                    if (!anim.IsInTransition (0)) {
                        rb.AddForce (Vector3.up * jumpPower, ForceMode.VelocityChange);
                        anim.SetBool ("Jump", true);        // Animatorにジャンプに切り替えるフラグを送る
                    }
                }
            }
        

            // 上下のキー入力でキャラクターを移動させる
            transform.localPosition += velocity * Time.fixedDeltaTime;

            // 左右のキー入力でキャラクタをY軸で旋回させる
            transform.Rotate (0, h * rotateSpeed, 0);    
    

            // 以下、Animatorの各ステート中での処理
            // Locomotion中
            // 現在のベースレイヤーがlocoStateの時
            if (currentBaseState.nameHash == locoState) {
                //カーブでコライダ調整をしている時は、念のためにリセットする
                if (useCurves) {
                    resetCollider ();
                }
            }
        // JUMP中の処理
        // 現在のベースレイヤーがjumpStateの時
        else if (currentBaseState.nameHash == jumpState) {
                cameraObject.SendMessage ("setCameraPositionJumpView");    // ジャンプ中のカメラに変更
                // ステートがトランジション中でない場合
                if (!anim.IsInTransition (0)) {
                
                    // 以下、カーブ調整をする場合の処理
                    if (useCurves) {
                        // 以下JUMP00アニメーションについているカーブJumpHeightとGravityControl
                        // JumpHeight:JUMP00でのジャンプの高さ(0~1)
                        // GravityControl:1⇒ジャンプ中(重力無効)、0⇒重力有効
                        float jumpHeight = anim.GetFloat ("JumpHeight");
                        float gravityControl = anim.GetFloat ("GravityControl"); 
                        if (gravityControl > 0)
                            rb.useGravity = false;    //ジャンプ中の重力の影響を切る
                                        
                        // レイキャストをキャラクターのセンターから落とす
                        Ray ray = new Ray (transform.position + Vector3.up, -Vector3.up);
                        RaycastHit hitInfo = new RaycastHit ();
                        // 高さが useCurvesHeight 以上ある時のみ、コライダーの高さと中心をJUMP00アニメーションについているカーブで調整する
                        if (Physics.Raycast (ray, out hitInfo)) {
                            if (hitInfo.distance > useCurvesHeight) {
                                col.height = orgColHight - jumpHeight;            // 調整されたコライダーの高さ
                                float adjCenterY = orgVectColCenter.y + jumpHeight;
                                col.center = new Vector3 (0, adjCenterY, 0);    // 調整されたコライダーのセンター
                            } else {
                                // 閾値よりも低い時には初期値に戻す(念のため)                    
                                resetCollider ();
                            }
                        }
                    }
                    // Jump bool値をリセットする(ループしないようにする)                
                    anim.SetBool ("Jump", false);
                }
            }
        // IDLE中の処理
        // 現在のベースレイヤーがidleStateの時
        else if (currentBaseState.nameHash == idleState) {
                //カーブでコライダ調整をしている時は、念のためにリセットする
                if (useCurves) {
                    resetCollider ();
                }
                // スペースキーを入力したらRest状態になる
                if (Input.GetButtonDown ("Jump")) {
                    anim.SetBool ("Rest", true);
                }
// ++++++++++++++++++++++++++++++++++++ 勝利ポーズの追加 開始 ++++++++++++++++++++++++++++++++++++
                // エンターキーを入力したらWin状態になる
                if (Input.GetKey(KeyCode.Return))
                {
                    anim.SetBool("WinBool", true);
                }
// ++++++++++++++++++++++++++++++++++++ 勝利ポーズの追加 開始 ++++++++++++++++++++++++++++++++++++
            }
        // REST中の処理
        // 現在のベースレイヤーがrestStateの時
        else if (currentBaseState.nameHash == restState) {
                //cameraObject.SendMessage("setCameraPositionFrontView");        // カメラを正面に切り替える
                // ステートが遷移中でない場合、Rest bool値をリセットする(ループしないようにする)
                if (!anim.IsInTransition (0)) {
                    anim.SetBool ("Rest", false);
                }
            }
// ++++++++++++++++++++++++++++++++++++ 勝利ポーズの追加 開始 ++++++++++++++++++++++++++++++++++++
        // WIN中の処理
        // 現在のベースレイヤーがwinStateの時
        else if (currentBaseState.nameHash == winState) {
                //cameraObject.SendMessage("setCameraPositionFrontView");        // カメラを正面に切り替える
                // ステートが遷移中でない場合、WinBool bool値をリセットする(ループしないようにする)
                if (!anim.IsInTransition(0))
                {
                    anim.SetBool("WinBool", false);
                }
            }
// ++++++++++++++++++++++++++++++++++++ 勝利ポーズの追加 終了 ++++++++++++++++++++++++++++++++++++
    }

    void OnGUI ()
        {
            GUI.Box (new Rect (Screen.width - 260, 10, 250, 150), "Interaction");
            GUI.Label (new Rect (Screen.width - 245, 30, 250, 30), "Up/Down Arrow : Go Forwald/Go Back");
            GUI.Label (new Rect (Screen.width - 245, 50, 250, 30), "Left/Right Arrow : Turn Left/Turn Right");
            GUI.Label (new Rect (Screen.width - 245, 70, 250, 30), "Hit Space key while Running : Jump");
            GUI.Label (new Rect (Screen.width - 245, 90, 250, 30), "Hit Spase key while Stopping : Rest");
            GUI.Label (new Rect (Screen.width - 245, 110, 250, 30), "Left Control : Front Camera");
            GUI.Label (new Rect (Screen.width - 245, 130, 250, 30), "Alt : LookAt Camera");
        }


        // キャラクターのコライダーサイズのリセット関数
        void resetCollider ()
        {
            // コンポーネントのHeight、Centerの初期値を戻す
            col.height = orgColHight;
            col.center = orgVectColCenter;
        }
    }
}

シーンの「再生」ボタンを押してシーンを開始します。
エンターキーを押してみてください。ユニティちゃんが勝利ポーズを取るアニメーションが始まれば成功です。
f:id:bluebirdofoz:20170728041741j:plain

以上でアニメーションの追加が出来ました。Unityアニメーション講座はここで一旦区切りとします。
ここまでお付き合い頂きありがとうございました。