ユニティちゃんを眺めたいので頑張る⑤

Unity

ユニティちゃんを眺めたいので頑張る④の続き。

前回はモーションを切り替えるボタンを配置するスクロールビューを作って、表情を切り替えるスクロールビューと切り替えれるようにドロップダウンでメニューを作りました。

今回は、モーションを切り替えるボタンをスクロールビューの中に動的に表示する機能を作ろうと思います!

Animator Controllerとステート

作り始める前にふと思ったことがあります。FaceListManagerのAnimationsにモーションを入れたら動くんじゃね?

確認してみましたがダメでした。JUMP00というAnimation Clipを入れて再生してみたところボタンは生成されました。

が、Consoleになにやらwarningが。

Animator.GotoState: State could not be found

ステートが見つかりません。

Hierarchyのユニティちゃんを選んでAnimatorコンポーネントを見てみます。ControllerのところにUnityChanARPoseというのが選ばれていました。ここはまだ触ってないのでこれがデフォルトのようです。(ユニティちゃんを色々触りだしてから知ったのですが、Prefabs -> for Locomotionの中にあるunitychanを使って解説されていることが多いみたいです。いま使っているのはPrefabsの中にあるunitychanですが、ついてるコンポーネントとか設定がちょっと違うなーとは見てわかりましたが両者の大きな違いは何だろう状態です)

とりあえずUnityChanARPoseをUnityChanLocomotionsに変えてみます。再生すると、今までポーズ取ってたユニティちゃんが立ち状態になっていて、若干揺れている感じがする・・・。と思いながら見ていたらユニティちゃんがまばたきした!!(3分経過)

堪能したところでUnityChanLocomotionsを見てみます。Window -> Animatorを選んでAnimatorウィンドウを開いてみるとIdleとかJumpとかのステートが置いてあります。試しにJumpのステートを選んでInspectorを見るとMotionにJUMP00が設定されています。

このステートに遷移したら、設定されているモーションが実行されるわけですね。

作りたいものは「ボタンを押したらモーションが実行される」機能です。つまり、ボタンを押したらモーションが設定されているステートに遷移すればいいわけです。

が、もう一つの作りたいものは「モーション切り替えボタンを動的に生成する」機能です。前者の機能だけであればステートを準備してそのステートに遷移するためのボタンを置くだけでしたが、動的に作るとなるとどうやるんだろう?

Animator Controllerを作る

ボタンを生成する部分は表情リストを作るときと同じで良さそうです。ボタンにはまたイベントトリガーをつけて、押している間はモーションが再生され、離したら終了する感じに。

ボタンを押したときに再生したいモーションを設定するステートも一緒に作ってあげれば動きそうですね。

と考えたはいいものの、なかなかすんなりいかず、試行錯誤の末できあがったものがこちら。

using UnityEngine;
using System.Collections;
using System.IO;
using UnityEngine.UI;
using System.Collections.Generic;
using UnityEngine.EventSystems;

#if UNITY_EDITOR
// アニメーターステートを作成する際に使用
// UnityEditor上でのみ動作する
// ビルドする時に含まれているとエラーとなる
using UnityEditor.Animations;
using UnityEditor;
#endif

public class MotionManager : MonoBehaviour {

    // ユニティちゃんを指定する
    public GameObject unitychanObj;

    // ユニティちゃんのアニメーターを格納する
    private Animator anim;

    // モーションのアニメーションを配列に格納
    public AnimationClip[] animations;

    // 作成したモーション切替ボタンを格納するリスト
    List<Button> nodes = new List<Button>();

    // MotionList内コンテンツオブジェクト
    public GameObject motionListContent = null;

    // MotionNode
    public GameObject motionNode = null;

    // ステート情報
    private AnimatorStateInfo state;

    // リストが作成された
    private bool isLoad = false;

    // アニメーションステートを作成したいときにチェックを入れる
    public bool createAnimationState;

    void Awake () {
        // アニメーターを取得しておく
        anim = unitychanObj.GetComponent<Animator> ();

        // ステート情報を取得しておく
        state = anim.GetCurrentAnimatorStateInfo (0);
    }

    void Start () {
        this.CreateMotionList ();
    }

    // モーション切替リストを生成
    private void CreateMotionList () {
        Debug.Log ("モーションノードを生成します");
        if (!isLoad)
        {
            // アニメーションの数だけモーション切替ボタンを生成する
            foreach (var animation in animations)
            {
                // 子オブジェクトをインスタンス化して配列に格納する
                Button obj = this.SetChild (motionListContent, motionNode, animation).GetComponent <Button> ();

                // アニメーション名をボタンにセットする
                // ボタンノードは子オブジェクト
                obj.GetComponentInChildren <Text> ().text = animation.name;

                nodes.Add (obj);
            }
            isLoad = true;
        }
        Debug.Log ("モーションノードを生成しました");
        Debug.Log ("モーションノードのサイズ:" + nodes.Count);
    }

    // 子オブジェクトセット処理
    private GameObject SetChild (GameObject parent, GameObject child, AnimationClip animation, string name = null) {
        // プレハブからインスタンスを生成
        GameObject obj = Instantiate (child);

        // 作成したオブジェクトを子として登録
        obj.transform.SetParent (parent.transform);

        obj.transform.localPosition = new Vector3 (0f, 0f, 0f);
        obj.transform.localScale = new Vector3 (1f, 1f, 1f);

        #if UNITY_EDITOR
        // Inspectorでチェックを入れるとステートを作成する
        // ステートが存在しているのにチェックすると同じステートが作成されるので注意
        // UnityEditor上でのみ動作する
        // ビルドする時に含まれているとエラーとなる
        if (createAnimationState) {
            // アニメーションステートを作成する
            this.SetAnimationState (unitychanObj, animation);

            Debug.LogWarning ("【注意】ステートを作成しました。重複していないか確認してください。");
        }
        #endif

        // ボタンにイベントトリガーを追加:アニメーション実行
        obj.AddComponent<EventTrigger> ();
        var trigger = obj.GetComponent<EventTrigger> ();
        trigger.triggers = new List<EventTrigger.Entry> ();

        // PointerDown(押している)時のイベントを設定
        var entryDown = new EventTrigger.Entry ();
        entryDown.eventID = EventTriggerType.PointerDown;
        entryDown.callback.AddListener ((x) => {
            anim.SetBool (animation.name, true);
        });
        trigger.triggers.Add (entryDown);

        // PointerUp(離した)時のイベントを設定
        var entryUp = new EventTrigger.Entry ();
        entryUp.eventID = EventTriggerType.PointerUp;
        entryUp.callback.AddListener ((x) => {
            anim.SetBool (animation.name, false);
        });
        trigger.triggers.Add (entryUp);

        // 作成したオブジェクトの名前に(Clone)がつかないようにプレハブの名前を再付与
        obj.name = (name != null) ? name : child.name;

        return obj;
    }

    #if UNITY_EDITOR
    // ステートを作成する
    // UnityEditor上でのみ動作する
    // ビルドする時に含まれているとエラーとなる
    private void SetAnimationState (GameObject gameObject, AnimationClip animation) {
        // アニメーターコントローラの作成
        AnimatorController animatorController = gameObject.GetComponent<Animator> ().runtimeAnimatorController as AnimatorController;

        // アニメーション名と同じ名前のbool型のパラメータを追加する
        animatorController.AddParameter (animation.name, AnimatorControllerParameterType.Bool);

        // ルートのステートマシンを取得する
        var rootStateMachine = animatorController.layers[0].stateMachine; // ステートが配置されている

        // アニメーション名と同じ名前のステートを追加する
        var state = rootStateMachine.AddState (animation.name); // アニメーション名と同じ名前のステートをステートマシンに追加する

        // モーションを設定する
        state.motion = animation; // 同名のアニメーションクリップをモーションに設定する

        AnimatorStateMachine asm = animatorController.layers[0].stateMachine;
        foreach (var s in asm.states) {
            AnimatorState animState = s.state;
            // トランジションの追加
            if (animState.name == "Idle") {
                state.AddTransition (animState).AddCondition (AnimatorConditionMode.IfNot, 1, animation.name);
                animState.AddTransition (state).AddCondition (AnimatorConditionMode.If, 1, animation.name);
                break;
            }
        }
    }
    #endif
}

とりあえずこれで動きます()

MotionManagerを作ってスクリプトをアタッチ。

UnityChanViewer_025

表情リストと同じように設定します。Unitychan Objにはユニティちゃんを、Motion List Contentにはモーション用に作ったスクロールビューにあるContentを指定して、作成するノードのプレハブをMotion Nodeに設定。

Animationsには再生したいモーションを登録していきます。さて、モーションはいくつあるのだろうか。

UnityChanViewer_026

ProjectウィンドウのUnityChan -> Animationsに入っているアニメーションの中を見てみます。POSE01〜31はポーズかな? DAMAGED00とかはモーションぽい。

合わせると55個です……多い……。うーん、ポーズとモーションは分けたほうがいいかなぁ。今回はモーションだけを登録してみます。

AnimationsのSizeに24と設定して、Elementに各Animation Clipを設定していきます。数が多いので大変です。。

UnityChanViewer_027

モーションを設定したらCreate Animation Stateにチェックを入れます。ここにチェックが入っている状態で、UnityEditorで再生した場合にモーションを動かす用のステートを作成することができます。

一度Animatorウィンドウを見てみます。

UnityChanViewer_028

現在このようになっています。ここにステートを作って、そのステートにモーションを登録して、そのステートに遷移するトランジションを設定することでモーションを再生することができます。

今回は動的にステートを作成して、そのステートにモーションを登録&トランジションを設定するようにしました。Create Animation Stateにチェックが入っている状態で一度UnityEditorで再生してみます。

UnityChanViewer_029

とんでもないことになったと思います。

Animationsに設定したAnimation Clip名でステートが作成され、そのAnimation Clipがモーションとして設定されています。

トランジションはIdleにつなげてあります。どうするのが正解かわからなかったので、とりあえず動けばいいやの精神で。

再生を停止させたら忘れないうちにCreate Animation Stateのチェックを外します。これをつけたままもう一度再生してしまうと、同じステート&トランジションが作られてしまいますので注意!(存在チェック入れたら良かったと後で気がついた)

動かしてみる

それでは動かしてみます!

UnitychanViewer_MotionList

動いたああああああああああああ!!!

もっと簡単なやり方があるのかもしれませんが、とにかく動いたことが嬉しい!

ユニティちゃんのおかげで勉強のモチベーションが持続するし、今回動かしたいために色々調べて楽しかったです。

あとはカメラをぐりぐり動かせたらいいかなーと感じるのでまた頑張ってみます!

それにしてもユニティちゃんは天使っすなぁ……!

Light_Frame
この作品は、『ユニティちゃんライセンス』で提供されています。

コメント

タイトルとURLをコピーしました