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

Unity

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

前回モーションを再生できるようにしました。設定しているときに見つけたPOSE01〜POSE31を、せっかくなのでポーズリストとして追加しようと思います。記事の内容的にはスクロールビューとアニメーションクリップの復習と、ドロップダウンの項目をスクリプトで追加する機能を実装、な感じです。

Scroll Viewを追加する

まずはポーズリストを作ります。このあたりはユニティちゃんを眺めたいので頑張る④でやったモーションリストを追加した方法と同じです。

UnityChanViewer_030

既に実装してあるMotionListCanvasを複製し、名前をPose〜に変更。スクロールビューのContent内にボタンとなるノードを置いて、PoseNodeというプレハブを作っておきます。

Dropdownを調整する

次にドロップダウンメニューからポーズリストの表示を切り替えれるようにします。

ユニティちゃんを眺めたいので頑張る④で作ったスクリプトだと、リストを追加する度に修正が必要になってしまうので、ドロップダウンメニューの項目をInspectorから増やしたりできるようにします。

ListChanger.csを以下のようにしました。

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

public class ListChanger : MonoBehaviour {

    // ドロップダウンメニュー
    public Dropdown dropdownMenu;

    // メニュー項目
    public GameObject menuTemplate;

    // メニュー項目内のコンテンツ
    public GameObject menuTemplateContent;

    // ドロップダウンメニューに表示したい項目
    public GameObject[] menuLists;
    private List<Canvas> menuListsCanvas = new List<Canvas> ();

    // 最初に表示したい項目の番号(Elementの番号を指定)
    public int defaultListNumber;

    void Awake () {
        float templateHeight = 0;

        // ドロップダウンメニューに追加したリスト分の項目を追加する
        // 項目名はリスト名(Dropdown名)から取りたい
        foreach (GameObject menuList in menuLists) {
            // 表示したい項目にはCanvasを指定していて、ドロップダウンメニューは Canvas -> Panel -> Dropdown にある
            // まずはCanvasの子を取得
            foreach (Transform child in menuList.transform){
                // Panel(子)をtag検索
                if(child.tag == "Panel"){
                    // 子のGameObjectを取得
                    GameObject childObject = child.gameObject;

                    // 次にPanelの子(Canvasの孫)を取得
                    foreach (Transform descendant in childObject.transform){
                        // Dropdown(孫)をtag検索
                        if(descendant.tag == "Dropdown"){
                            // ドロップダウンメニューにDropdown名と同じ名前の項目を追加する
                            dropdownMenu.options.Add (new Dropdown.OptionData { text = descendant.name });
                        }
                    }
                }
            }

            // 高さを調整するために、項目の数だけContentの高さを足していく
            templateHeight += menuTemplateContent.GetComponent<RectTransform> ().rect.height;

            // Canvasを取得してリストに追加する
            menuListsCanvas.Add (menuList.GetComponent<Canvas> ());
        }

        // ドロップダウンメニューのTemplateの高さを調整
        if (templateHeight > 240.0f) {
            // 調整の高さが画面を飛び出さないように
            templateHeight = 240.0f;
        }
        Vector2 templateSizeDelta = menuTemplate.GetComponent<RectTransform> ().sizeDelta;
        templateSizeDelta.y = templateHeight;
        menuTemplate.GetComponent<RectTransform> ().sizeDelta = templateSizeDelta;

        // ドロップダウンメニューを更新(Optionsを変更したら、これがないと最初に選択されている項目が表示されない)
        dropdownMenu.RefreshShownValue ();
	}

    void Start () {
        // 最初に表示したい項目の番号が指定されていなかったら、追加された項目の1番目(Element 0)を指定する
        if (defaultListNumber == null) {
            defaultListNumber = 0;
        }

        // 起動後は最初に表示したいリストだけ表示して、他のリストは非表示にする
        for (int i = 0; i < menuListsCanvas.Count; i++) {
            menuListsCanvas [i].enabled = false;
            if (i == defaultListNumber) {
                menuListsCanvas [i].enabled = true;
            }
        }
    }
	
    // ListMenuDropdown
    public void SelectedListMenuDropdown (Dropdown dropdown) {
        // 選択されたメニュー番号のリストだけ表示して、他のリストは非表示にする
        for (int i = 0; i < menuListsCanvas.Count; i++) {
            menuListsCanvas [i].enabled = false;
            if (i == dropdown.value) {
                menuListsCanvas [i].enabled = true;
            }
        }
    }
}

使うために準備が必要です。ドロップダウンメニューの項目名を、表示したい項目に追加したリストから取得できるようにしてありますが、子要素を取得するためにtagで検索するようにしました。そのため、あらかじめリストにtagを指定します。

UnityChanViewer_031

各リストの、〜CanvasのTagに『Canvas』、〜Backgroundに『Panel』、〜Listに『Dropdown』をそれぞれ設定。こうすることで、〜Canvasの子要素を『Panel』でtag検索して、一致したオブジェクト(〜Background)の子要素をさらに『Dropdown』でtag検索してドロップダウンまで辿り着いています。リストの作り方を同じようにしないと使えませんが、とりあえず今回試す分にはこれで充分です。

準備が済んだらInspectorで設定をします。

UnityChanViewer_032

Dropdown Menuにはドロップダウンメニューとなるオブジェクトを指定。指定したオブジェクトにリスト分の項目が追加されます。

Menu Templateはドロップダウンメニュー内の各項目が表示される領域です。メニュー項目を増やした分、この領域の高さを調整したいので、ListMenuDropdown -> Templateを指定しています。

Menu Template Contentは各項目自体です。この項目の高さ分を項目の数だけ加算するためにListMenuDropdown -> Template -> Viewport -> Contentを指定しています。

この高さ調整によって画面を飛び出てしまう場合は高さを指定してあげる必要があると思います。今回はドロップダウンメニューのPos Yの値で設定しています。これもInspectorで調整できるようにすると長くなりそうなので省きました。

ちなみに、Menu Templateの高さ以上のコンテンツがあるとスクロールバーが表示されます。

Menu ListsのSizeには追加したいリストの数を入力。

Elementにリストの親オブジェクト(〜Canvas)を指定します。

Default List Numberは、起動後最初に表示したいリストの番号を指定します。番号はElementの番号です。

次にスクリプトの説明です。

dropdownMenu.options.Add (new Dropdown.OptionData { text = descendant.name });

これでドロップダウンメニューに項目を追加します。Dropdownの要素を追加したいときはOptionsにAddすれば良いです。textは要素名です。リスト名(Dropdownにつけた名前)が設定されるようにしています。

dropdownMenu.RefreshShownValue ();

忘れてはいけないのがこちら。Optionsを変更したらRefreshShownValueで更新してあげないと、最初に選択されている項目が表示されません。

// ListMenuDropdown
public void SelectedListMenuDropdown (Dropdown dropdown) {
    // 選択されたメニュー番号のリストだけ表示して、他のリストは非表示にする
    for (int i = 0; i < menuListsCanvas.Count; i++) {
        menuListsCanvas [i].enabled = false;
        if (i == dropdown.value) {
            menuListsCanvas [i].enabled = true;
        }
    }
}

ドロップダウンメニューで項目を選択したときは、選択した項目の番号のリストだけ表示して、他のリストは非表示にするようにしました。

ドロップダウンメニューの調整はこんな感じで終わりにします。

ポーズ用のリスト

さて、最後にポーズを再生させる用のボタンをリストに配置しようと思います。やり方はモーション用リストを作ったときと同じです。

空のオブジェクトを作ってPoseListManagerという名前に設定し、PoseListManager.csを作ってアタッチします。

PoseListManager.csのスクリプトはモーションと一緒です。名称だけちょっと変更しています。

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 PoseListManager : MonoBehaviour {

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

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

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

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

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

    // PoseNode
    public GameObject poseNode = null;

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

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

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

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

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

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

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

あとは同じようにInspectorの設定です。

UnityChanViewer_033

Sizeに31と設定し、ちまちまElementにAnimation Clipを設定していきます。(あとで思いましたが、リスト用のフォルダを作って、リソースを配列に入れたほうが楽だったのではないかと……)

動かしてみる

初回はCreate Animation Stateにチェックを入れて再生します。これでポーズ用のステートが作成されます。Animatorウィンドウは見ないほうがいいです。大変なことになっていますから。(綺麗な作り方を学びたい)

UnitychanViewer_PoseList

これでポーズの切り替えもできるようになりました!

今のカメラの位置だと座ったとき見づらいですね。次はカメラ位置の調整をしてみます。

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

コメント

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