4月7日、Unity Technologies Japan.によるキャラクター「ユニティちゃん」のアセットが公開されました。今回はこのユニティちゃんを、Mecanimを使って歩かせてみたいと思います。

Mecanimとは、Unity4で実装されたアニメーションシステムです。Mecanimを使うと、ほとんどスクリプトを記述せずにアニメーションを設定することができます。

第1回目である今回はMecanim基本編として、ステートマシンアニメーションパラメータについて簡単に解説しながら、歩くユニティちゃんを作っていきます。


準備


本特集は Unity4 を使用します。

また、以下のアセットを使用するのでプロジェクトに追加しておきましょう。

  • ユニティちゃん
  • 本特集は、バージョン1.1.1までのアセットを対象としています。 「DATA DOWNLOAD > キャラクター利用のガイドライン > データダウンロード > リリースノート」より適当なバージョンのアセットをダウンロードしてください。
  • Mecanim Example Scenes

アセットを追加する際、ThirdPersonCameraというクラス名が競合するため、ユニティちゃんアセット側のThirdPersonCamera.csは削除します。

上図のように、シーン上には UnityChan\Prefabs\unitychan.prefab を配置し。Terrain と Directional Light はお好みで配置しておきましょう。 Terrainのテクスチャは、ユニティちゃんの移動がわかるようにするため、模様が付いているものがいいでしょう。


予備知識


Mecanimを理解するには AnimationClip と AnimatorController, Animator を知っておく必要があります。要点だけ抑えると以下のようになります。

Animator

実際にモデルをアニメーションさせるコンポーネント。アニメーションさせたいGameObjectへアタッチして使います。

AnimatorController

Animatorがモデルをアニメーションさせるときの、その制御情報を保持するオブジェクト。Unity上で作成,編集し、Animatorへアタッチして使います。

AnimationClip

単一のアニメーション情報を保持するオブジェクト。外部で作成されたアニメーションをUnity上にインポートすると最終的に生成されます。AnimatorController内のステート一つ,ないし複数にアタッチして使います。


モデルを動かす -ステートマシン-


AnimatorControllerを作成する

まずはじめに、AnimatorControllerを作成しましょう。 [Assets] -> [Create] -> [AnimatorController] を選択することで作成できます。今回は UnityChanAnimator という名前をつけます。


ステートを作成する

AnimatorControllerを作成すると上図のように、Animatorウィンドウがアクティブになります。この画面でアニメーションの設定をしていきましょう。

Animatorウィンドウのグリッド上で右クリックし [Create State] -> [Empty] を選択すると、 [New State] と書かれたブロックが作成されます。これが1つのステートを表しています。

クリックすると右のInspectorにこのステートの情報が表示されます。Inspector内のMotion欄にこのステートのアニメーションを設定します。ここにアニメーションクリップをドラッグ&ドロップしましょう。今回は Assets\Locomotion\Animations\DefaultAvatar@WalkForward_NtrlFaceFwd.fbx の Walk をアタッチしました。

これでステートの設定は完了です。シーン上の unitychan の Animator に UnityChanAnimator.controller をアタッチすることを忘れずに行います。

また、カメラがユニティちゃんを追跡するためのスクリプトを用意しました。Main Camera にアタッチしておきましょう。

using UnityEngine;
using System.Collections;

public class ViewerCamera : MonoBehaviour {

    public GameObject viewObject = null;

    public float rotationSensitivity = 0.01f;
    public float distanceSensitivity = 0.01f;
    public float followObjectSmooth = 3f;
    public float maxRotationY = 0.45f;
    public float minRotationY = -0.45f;
    public float minDistance = 0.5f;
    public float maxDistance = 5f;

    public float defaultDistance = 2f;
    public float defaultAngularPositionX = 0f;
    public float defaultAngularPositionY = 0f;

    protected float distance = 0f;
    protected Vector2 cameraPosParam = Vector2.zero;

    private Vector3 clickedPos = Vector3.zero;
    private int clickedFlag = 0; //0:none 1:left 2:right
    private Vector3 pivotTemp = Vector3.zero;
    private float distanceTemp = 0f;
    private Vector2 cameraPosParamTemp = Vector2.zero;
    // Use this for initialization
    void Start () {
        this.distance = this.defaultDistance;
        this.cameraPosParam = new Vector2 (this.defaultAngularPositionX / 180f * Mathf.PI, this.defaultAngularPositionY / 180f * Mathf.PI);
        this.pivotTemp = this.transform.position;
    }

    // Update is called once per frame
    void Update () {
        if (this.clickedFlag == 0) {
            if (Input.GetMouseButtonDown(0)) {
                this.clickedPos = Input.mousePosition;
                this.cameraPosParamTemp = this.cameraPosParam;
                this.clickedFlag = 1;
            }
        }

        if (this.clickedFlag == 0) {
            if (Input.GetMouseButtonDown(1)) {
                this.clickedPos = Input.mousePosition;
                this.distanceTemp = this.distance;
                this.clickedFlag = 2;
            }
        }

        if (this.clickedFlag == 1 && Input.GetMouseButtonUp(0)) {
            this.clickedFlag = 0;
        }

        if (this.clickedFlag == 2 && Input.GetMouseButtonUp(1)) {
            this.clickedFlag = 0;
        }

        Vector3 mousePosDistance = Input.mousePosition - this.clickedPos;

        switch (this.clickedFlag) {
        case 1:
            var diff = new Vector2 (mousePosDistance.x, -mousePosDistance.y) * rotationSensitivity;
            this.cameraPosParam.x = this.cameraPosParamTemp.x + diff.x;
            this.cameraPosParam.y = Mathf.Clamp(this.cameraPosParamTemp.y + diff.y, this.minRotationY * Mathf.PI, this.maxRotationY * Mathf.PI);
            break;
        case 2:
            this.distance = Mathf.Clamp (this.distanceTemp + mousePosDistance.y * this.distanceSensitivity, this.minDistance, this.maxDistance);
            break;
        }

        Vector3 orbitPos = GetOrbitPosition (this.cameraPosParam, this.distance);

        Vector3 pivot = Vector3.Lerp(this.pivotTemp, this.viewObject.transform.position, Time.deltaTime * this.followObjectSmooth);
        this.transform.position = pivot + orbitPos;
        this.transform.LookAt (this.viewObject.transform);

        this.pivotTemp = pivot;
    }

    private Vector3 GetOrbitPosition(Vector2 anglarParam, float distance){
        float x = Mathf.Sin (anglarParam.x) * Mathf.Cos (anglarParam.y);
        float z = Mathf.Cos (anglarParam.x) * Mathf.Cos (anglarParam.y);
        float y = Mathf.Sin (anglarParam.y);

        return new Vector3 (x, y, z) * distance;
    }
}

ViewerCameraの Follow Object はカメラの注視点です。ユニティちゃんの首 (unitychan\Character1_Reference\Character1_Spine\Character1_Spine1\Character1_Spine2\Character1_Neck) を設定しておくと良いでしょう。

左ドラッグでカメラ位置の移動、右ドラッグで距離の移動ができます。

それでは、実行してみましょう。

ユニティちゃんが歩きました!


ステートを遷移させる

次にユニティちゃんが 停止 -> 歩き -> 停止 … と振る舞うようにしてみましょう。

新しくステートを作り、今度は Assets\Locomotion\Animations\DefaultAvatar@Idle_Neutral.fbx の Idle をアタッチしておきます。ついでにステート名をそれぞれ Walk, Idle に変更しました。

アニメーションの遷移は非常に直感的に設定することができます。

遷移前のステートを右クリックして Make Transition を選択。遷移先をクリックすると設定完了です。交互に遷移できるように設定してみましょう。

最後に、初期ステートを Idle に変更する必要があります。Idle を右クリックし、 Set As Default を選択してください。オレンジ色のステートが初期ステートです。Idleステートがオレンジ色になっていることを確認しましょう。

上図のようになっていることを確認したら実行してみましょう。

ユニティちゃんが歩いたり、立ち止まったりするようになりました。


アニメーションを制御する -アニメーションパラメータ-


ユニティちゃんのアニメーションは遷移するようになりましたが、このままでは自在に制御できません。これを行うためには アニメーションパラメータ を使います。

今回は、↑キーを押している間は歩く。押していない時は立ち止まる。という制御を行ってみたいと思います。


パラメータを追加する

Animatorウィンドウの左下に Parameters という欄があります。この右端にある + から、今回は Bool を選択します。そうすると、New Bool というパラメータが追加されます。名前を New Bool から Do Walk に変更しましょう。

パラメータの追加はこれで完了です。

※パラメータの種類に Trigger というタイプがありました。これの実体は Bool です。普通の Bool との違いは、「ステートの遷移のトリガとなった時、自動的に false になる」という点です。用途によってうまく使い分けましょう。


遷移条件を設定する

ステートの遷移には、パラメータを使った条件を付けることができます。

遷移を表す矢印をクリックしてください。Inspectorに遷移に関する情報が表示されます。

Inspector内の Conditions欄を見てください。これが遷移条件です。

デフォルトでは Exit Time という条件が設定されています。これは「アニメーションが指定の正規化時間に到達した時」という遷移条件です。これを「Do Walk が true の時」,「Do Walk が false の時」という条件にそれぞれ変更します。

Exit Time と書かれているボックスをクリックすると、リスト中に先ほど追加した Do Walk があるので選択します。Idle -> Walk の遷移なら true, Walk -> Idle の遷移なら false を設定します。これで遷移条件の設定は完了です。


スクリプトからパラメータを制御する

最後の仕上げとして、↑キーを入力している時は Do Walk を true,押していない時は false に変更するスクリプトを走らせます。下のスクリプトをunitychanにアタッチしてください。

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Animator))]
public class UnityChanController : MonoBehaviour {

    private Animator animator;
    private int doWalkId;

    // Use this for initialization
    void Start () {
        animator = GetComponent<Animator> ();
        doWalkId = Animator.StringToHash ("Do Walk");
    }

    // Update is called once per frame
    void Update () {
        if (Input.GetKey(KeyCode.UpArrow)) {
            animator.SetBool(doWalkId, true);
        }else{
            animator.SetBool(doWalkId, false);
        }
    }
}

Boolパラメータの設定には、AnimatorのSetBool(int, bool) を用います。

※パラメータには他にFloat, Int, Triggerがあります。

これでユニティちゃんを制御できるようになりました。


おわりに


今回はMecanimの基本中の基本である、Animatorを使ったアニメーションの適用、ステートマシンでのアニメーション遷移を使ってユニティちゃんを動かしました。あとはステートを増やし、必要な入力をAnimatorに伝えるだけで、ゲームのキャラクターのように自在に操作することもできるでしょう。

ほとんどスクリプトを書かなくてもユニティちゃんを思うがままに。Unityすごい!

次回はMecanimを更に掘り下げて、複数のアニメーションクリップをブレンドする アニメーションブレンド ,複数のステートマシンを並行して走らせる アニメーションレイヤー ,アニメーションを体の一部のみに適用する ボディマスク を使って、ユニティちゃんを走らせたり表情をつけるなど、より魅力的にしていきます。