EditorでAnimatorの再生 その2

EditorでAnimatorの再生 その2

 前回(EditorでAnimatorの再生 その1)、Animator.UpdateによるAnimatorの再生を行いました。今回は、EditorWindowへAnimatorに設定されているパラメーターを表示し、それらを変更することでアニメーションの遷移を行う機能を追加しました。

Script

 作成したScriptは以下の通りです。

using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
using UnityEditor;

namespace Karakan
{
    public class AnimatorPlayerB : EditorWindow
    {
        GameObject _go_target;
        Animator _animator;
        TimeController _time_controller;

        Action<float> _action_play_animator;
        Action<int[], float> _action_init_animator;

        bool _flg_play = false, _flg_pause = false, _flg_stop = false,
            _flg_delay_pause = false, _flg_lock = false;

        int[] _default_state_hashes;
        float _play_speed = 1f;
        string _target_name = "None";
        AnimatorControllerParameter[] _animator_controller_param;

        //gui
        GUIContent _content_play_button = new GUIContent();
        GUIContent _content_pause_button = new GUIContent();
        GUIStyle _style_toolbar_button;

        int _border_line_width = 1;

        [MenuItem("Tools/AnimatorPlayerB")]
        public static void OpenWindow()
        {
            EditorWindow ed = EditorWindow.GetWindow(typeof(AnimatorPlayerB), false, "AnimatorPlayerB");
            ed.minSize = new Vector2(200, 456);
        }

        private void OnEnable()
        {
            _time_controller = new TimeController();

            _content_play_button.image = EditorGUIUtility.Load("PlayButton") as Texture;
            _content_pause_button.image = EditorGUIUtility.Load("PauseButton") as Texture;
            _style_toolbar_button = new GUIStyle(EditorStyles.toolbarButton);

            _action_play_animator = (delta_time) =>
            {
                if (_animator == null) return;
                _animator.Update(delta_time);
            };

            _action_init_animator = (def_state_hash, time) =>
            {
                for (int i = 0; i < def_state_hash.Length; i++) { _animator.Play(def_state_hash[i], -1, time); }
            };
        }

        private void OnDisable()
        {
            if (_animator != null)
            {
                _action_init_animator(_default_state_hashes, 0f);
                _action_play_animator(1f);
            }
        }

        public void OnSelectionChange()
        {
            if (!_flg_lock)
            {
                _go_target = Selection.activeGameObject;
                Repaint();
            }
            else
            {
                return;
            }

            if (_go_target == null)
            {
                _go_target = null;
                _animator = null;
                _target_name = "None";
                return;
            }

            _animator = _go_target.GetComponent<Animator>();
            if (_animator == null)
            {
                _go_target = null;
                _target_name = "None";
                return;
            }

            _target_name = _go_target.name;
            _animator.speed = 0f;

            //get default state hash
            int animator_layer_count = _animator.layerCount;
            _default_state_hashes = new int[animator_layer_count];
            for (int i = 0; i < animator_layer_count; i++)
            {
                AnimatorStateInfo anim_info = _animator.GetCurrentAnimatorStateInfo(i);
                _default_state_hashes[i] = anim_info.fullPathHash;
            }

            //get parameters
            _animator_controller_param = _animator.parameters;
            Repaint();
        }

        private void OnGUI()
        {
            TimeControlUnit();

            EditorGUILayout.LabelField("Selected GameObject:" + _target_name);

            BorderLine(_border_line_width);

            EditorGUILayout.LabelField("Parameters");
            if (_animator != null)
            {
                for (int i = 0; i < _animator_controller_param.Length; i++)
                {
                    switch (_animator_controller_param[i].type)
                    {
                        case AnimatorControllerParameterType.Bool:
                            string bool_name = _animator_controller_param[i].name;
                            bool flg = EditorGUILayout.Toggle(bool_name, _animator.GetBool(bool_name));
                            _animator.SetBool(bool_name, flg);
                            break;

                        case AnimatorControllerParameterType.Float:
                            string float_name = _animator_controller_param[i].name;
                            float float_val = EditorGUILayout.FloatField(float_name, _animator.GetFloat(float_name));
                            _animator.SetFloat(float_name, float_val);
                            break;

                        case AnimatorControllerParameterType.Int:
                            string int_name = _animator_controller_param[i].name;
                            int int_val = EditorGUILayout.IntField(int_name, _animator.GetInteger(int_name));
                            _animator.SetInteger(int_name, int_val);
                            break;

                        case AnimatorControllerParameterType.Trigger:
                            string trigger_name = _animator_controller_param[i].name;
                            if (GUILayout.Button(trigger_name))
                            {
                                _animator.SetTrigger(trigger_name);
                            }
                            break;
                    }
                }
            }
        }

        void TimeControlUnit()
        {
            if (_animator == null) return;

            Event ev = Event.current;
            _time_controller.speed = _play_speed;

            //コントロール用toolbar
            int toolbar_button_width = 50;
            EditorGUILayout.BeginHorizontal();
            {
                //Play/Stop
                EditorGUI.BeginChangeCheck();
                bool temp_flg_play = GUILayout.Toggle(_flg_play, _content_play_button, _style_toolbar_button, GUILayout.Width(toolbar_button_width));
                if (ev.button == 0) _flg_play = temp_flg_play;
                if (EditorGUI.EndChangeCheck())
                {
                    if (_flg_play)
                    {
                        _flg_pause = false;
                        _time_controller.Play();
                        _animator.speed = _play_speed;
                    }
                    else
                    {
                        _time_controller.Stop();
                        _flg_pause = false;
                        GUI.changed = true;
                        _flg_stop = true;
                    }
                }

                //Pause
                EditorGUI.BeginChangeCheck();
                bool temp_flg_pause = GUILayout.Toggle(_flg_pause, _content_pause_button, _style_toolbar_button, GUILayout.Width(toolbar_button_width));
                if (ev.button == 0) _flg_pause = temp_flg_pause;
                if (_flg_play == false && _time_controller.isPlay == false) _flg_pause = false;
                if (EditorGUI.EndChangeCheck())
                {
                    if (_flg_pause)
                    {
                        _time_controller.Pause();
                        _animator.speed = 0f;
                        _flg_delay_pause = true;
                    }
                    else
                    {
                        if (_flg_play)
                        {
                            _time_controller.Play();
                            _animator.speed = _play_speed;
                        }
                    }
                }

                //再生時間
                float time = _time_controller.time;
                int hour = (int)(time / 3600f);
                time -= hour * 3600f;
                int minutes = (int)(time / 60f);
                float sec = time - minutes * 60f;
                EditorGUILayout.LabelField(hour.ToString() + ":" + minutes.ToString() + ":" + sec.ToString("f3"));

                if (_flg_stop)
                {
                    _time_controller.time = 0f;
                    _animator.speed = 0f;
                    _action_init_animator(_default_state_hashes, 0f);
                    _flg_stop = false;
                }

                GUILayout.Box(GUIContent.none, _style_toolbar_button, GUILayout.ExpandWidth(true));

                _flg_lock = GUILayout.Toggle(_flg_lock, new GUIContent("Lock"), _style_toolbar_button, GUILayout.Width(toolbar_button_width));
            }
            EditorGUILayout.EndHorizontal();

            if (_time_controller.isPlay) HandleUtility.Repaint();
        }

        void BorderLine(int height)
        {
            GUILayout.Box(GUIContent.none, GUILayout.ExpandWidth(true), GUILayout.Height(height));
        }

        private void Update()
        {
            if (_time_controller != null && _animator != null)
            {
                _time_controller.TimeUpdate();
                _action_play_animator?.Invoke(Time.deltaTime);
            }
        }
    }
}

パラメーターの取得と表示

 Aniamtor.parametersによってAnimetorに設定されているAnimatorControllerParameterを取得することができます。これを用いて各パラメーターの型に応じてコントロールの表示を行います。

AnimatorControllerParameter[] _animator_controller_param;

public void OnSelectionChange()
{
    ・・・
    //get parameters
    _animator_controller_param = _animator.parameters;
    Repaint();
}

private void OnGUI()
{

   ・・・
   if (_animator != null)
   {
       for (int i = 0; i < _animator_controller_param.Length; i++)
       {
           switch (_animator_controller_param[i].type)
           {
               case AnimatorControllerParameterType.Bool:
                   string bool_name = _animator_controller_param[i].name;
                   bool flg = EditorGUILayout.Toggle(bool_name, _animator.GetBool(bool_name));
                   _animator.SetBool(bool_name, flg);
                   break;

               case AnimatorControllerParameterType.Float:
                   string float_name = _animator_controller_param[i].name;
                   float float_val = EditorGUILayout.FloatField(float_name, _animator.GetFloat(float_name));
                   _animator.SetFloat(float_name, float_val);
                   break;

               case AnimatorControllerParameterType.Int:
                   string int_name = _animator_controller_param[i].name;
                   int int_val = EditorGUILayout.IntField(int_name, _animator.GetInteger(int_name));
                   _animator.SetInteger(int_name, int_val);
                   break;

               case AnimatorControllerParameterType.Trigger:
                   string trigger_name = _animator_controller_param[i].name;
                   if (GUILayout.Button(trigger_name))
                   {
                       _animator.SetTrigger(trigger_name);
                   }
                   break;
           }
       }
   }
}

Animatorの初期化

 Animatorを読み込んだ際、Animator.GetCurrentAnimatorStateInfoによって各レイヤーのEntryに接続されているAnimation Stateの情報を取得します。この情報からAnimation Stateのハッシュを取得します。取得したハッシュを用いてAnimator.Playを実行することで初期化を行います。

Action<int[], float> _action_init_animator;

private void OnEnable()
{
    ・・・
    _action_init_animator = (def_state_hash, time) =>
    {
        for (int i = 0; i < def_state_hash.Length; i++) { _animator.Play(def_state_hash[i], -1, time); }
    };
}

public void OnSelectionChange()
{
    ・・・
    //get default state hash
    int animator_layer_count = _animator.layerCount;
    _default_state_hashes = new int[animator_layer_count];
    for (int i = 0; i < animator_layer_count; i++)
    {
        AnimatorStateInfo anim_info = _animator.GetCurrentAnimatorStateInfo(i);
        _default_state_hashes[i] = anim_info.fullPathHash;
    }
    ・・・
}

void TimeControlUnit()
{
    ・・・
    _action_init_animator(_default_state_hashes, 0f);
    ・・・
}

再生時間

 再生時間はUpdate内で計測しており、Layout→Repaint→Updateの順に実行されるためEditorWindowで表示される数値は1フレームの遅れが生じます。

private void OnGUI()
{
    TimeControlUnit();
    ・・・
}

void TimeControlUnit()
{
    ・・・
//再生時間
float time = _time_controller.time;
    //再生時間
    float time = _time_controller.time;
    int hour = (int)(time / 3600f);
    time -= hour * 3600f;
    int minutes = (int)(time / 60f);
    float sec = time - minutes * 60f;
    EditorGUILayout.LabelField(hour.ToString() + ":" + minutes.ToString() + ":" + sec.ToString("f3"));
    ・・・
}

private void Update()
{
    if (_time_controller != null && _animator != null)
    {
        _time_controller.TimeUpdate();
        ・・・
    }
}

実行結果

 以上のScriptを実行するにあたり作成したAnimatorControllerは以下の通りです。

Base Layer
フェイスアニメーション

実行結果は以下の通りです。Speedの変更によりStanding→Walking→Runing、トリガーによるジャンプができています。また、フェイスアニメーションも問題なくできておりAnimatorの再生に問題はないようです。ジャンプ中の一時停止及び再開も問題なくできており、Animatorの停止時に初期状態へ戻す処理も機能していることが分かります。

このコンテンツはユニティちゃんライセンス条項の元に提供されています