EditorでAnimatorの再生 その1

EditorでAnimatorの再生 その1

 Editor上でAnimatorの再生するEditorWindowを作成しました。

Animatorの再生

 Animator.UpdateへTime.deltaTimeを渡すことでEditorでAnimatorを再生を行うことができます。

Animator _animator;
private void Update()
{
    if (_animator != null) _animator.Update(Time.deltaTime);
}

EditorWindowではLayout→Repaint→Updateの順に実行されます。試しにUpdate内以外でAnimatorの再生を行ってみました。

EditorWindowにおけるフレーム間の時間を取得

 UpdateはTime.deltaTimeによって、フレーム間の時間を取得できます。OnGUI内(LayoutやRepaint)のフレーム間の時間は取得する手段がないようなので、以下のScriptによりEditorApplication.timeSinceStartupを用いて時間間隔を取得します。さらに、再生、一時停止、停止や繰り返しの機能を追加しています。

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

namespace Karakan
{
    public class TimeController
    {
        float _previous_time = 0;
        float _current_time = 0;

        public bool isPlay { get; private set; } = false; //再生しているか
        public float startTime { get; set; } = 0; //開始時間
        public float endTime { get; set; } = 0; //終了時間
        public bool Loop { get; set; } //繰り返しをするか
        public float time { get { return _current_time; } set { _current_time = value; } } 再生時間
        public bool Return { get; private set; } = false; //Loop時にstartTimeへ戻った時にtrue
        public float speed { get; set; } = 1f; //再生スピード
        public float deltaTime { get; private set; } = 0f; //フレーム間の時間

        public void Play() { isPlay = true; } //再生

        public void Pause() { isPlay = false; } //一時停止

        public void Stop() { isPlay = false; time = startTime; } //停止

        public TimeController()
        {
            _previous_time = (float)EditorApplication.timeSinceStartup;
        }

        //Update内
        public void TimeUpdate()
        {
            Return = false;
            if (isPlay)
            {
                deltaTime = Time.deltaTime;
                _current_time += deltaTime * speed;
                if (Loop)
                {
                    Return = endTime <= _current_time ? true : false;
                    _current_time = Mathf.Repeat(_current_time, endTime);
                    _current_time = Mathf.Max(_current_time, startTime);
                }
            }
            time = _current_time;
        }

        //OnGUI内
        public void EditorTimeUpdate()
        {
            Return = false;
            float start_editor_time = (float)EditorApplication.timeSinceStartup;
            if (isPlay)
            {
                deltaTime = start_editor_time - _previous_time;
                _current_time += deltaTime * speed;

                if (Loop)
                {
                    Return = endTime <= _current_time ? true : false;
                    _current_time = Mathf.Repeat(_current_time, endTime);
                    _current_time = Mathf.Max(_current_time, startTime);
                }
            }
            _previous_time = (float)EditorApplication.timeSinceStartup;
            time = _current_time;
        }
    }
}

Animatorの再生

 Scene内の選択されたGameObjectのAnimatorを取得し、再生ボタンによりAnimatorを再生します。また、再生ボタンを再度クリックすると停止します。さらに、EventTypeによってAnimatorを実行するタイミングを変更できます。

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

namespace Karakan
{
    public class AnimatorPlayTest : EditorWindow
    {
        TimeController _time_controller;
        GameObject _go_target;
        Animator _animator;
        GUIStyle _style_toolbar_button;
        GUIContent _content_play_button = new GUIContent();
        bool _is_play = false;
        AnimatorEventType _anim_event_type = AnimatorEventType.Layout;

        enum AnimatorEventType{ Layout, Repaint, Update };

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

        private void OnEnable()
        {
            _time_controller = new TimeController();
            _content_play_button.image = EditorGUIUtility.Load("PlayButton") as Texture;
            _style_toolbar_button = new GUIStyle(EditorStyles.toolbarButton);
        }

        //GameObjectの選択とAnimatorの取得
        private void OnSelectionChange()
        {
            _go_target = Selection.activeGameObject;
            _animator = _go_target.GetComponent<Animator>();
            Repaint();
            if (_animator == null)
            {
                _go_target = null;
                return;
            }
            _time_controller.Stop();
            _animator.speed = 0f;
        }

        private void OnGUI()
        {
            PlayAnimator();

            int toolbar_button_width = 50;
            //Animatorの再生と停止
            EditorGUI.BeginChangeCheck();
            _is_play = GUILayout.Toggle(_is_play, _content_play_button, _style_toolbar_button, GUILayout.Width(toolbar_button_width));
            if (EditorGUI.EndChangeCheck())
            {
                if (_is_play)
                {
                    _time_controller.Play();
                    _animator.speed = 1f;
                }
                else
                {
                    _time_controller.Stop();
                    _animator.speed = 0f;
                }
            }
            if (_time_controller.isPlay) Repaint();

            if(_go_target != null) EditorGUILayout.LabelField("target object", _go_target.name);

            _anim_event_type = (AnimatorEventType)EditorGUILayout.EnumPopup("EventType", _anim_event_type);
        }

        //Animatorの実行
        void PlayAnimator()
        {
            switch (_anim_event_type)
            {
                case AnimatorEventType.Layout:
                    if (Event.current.type == EventType.Layout)
                    {
                        _time_controller.EditorTimeUpdate();
                        if (_animator != null)
                        {
                            _animator.Update(_time_controller.deltaTime);
                            Repaint();
                        }
                    }
                    break;

                case AnimatorEventType.Repaint:
                    if (Event.current.type == EventType.Repaint)
                    {
                        _time_controller.EditorTimeUpdate();
                        if (_animator != null)
                        {
                            _animator.Update(_time_controller.deltaTime);
                            Repaint();
                        }
                    }
                    break;

                case AnimatorEventType.Update:
                    Repaint();
                    break;
            }
        }

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

実行結果

 EditorWindowを開くと以下のように再生ボタンと選択したGameObjectの名前及びAnimatorを実行するタイミングを選択するポップアップが表示されます。

これを用いて、Layout、Repaint及びUpdateでAnimatorを実行した結果は以下の通りです。

Layout
Repaint
Update

どのタイミングでも問題なくAnimatorによるアニメーションの再生が出来ていることが分かります。

負荷をかけて実行

  以下のコードによりボタンを大量に追加し、負荷をかけて実行してみました。

_scroll_pos = EditorGUILayout.BeginScrollView(_scroll_pos);
for (int i = 0; i < 3000; i++)
{
    GUILayout.Button(i.ToString());
}
EditorGUILayout.EndScrollView();

実行結果

 EditorWindowを開くと大量にボタンが追加されています。

この状態でLayout、Repaint及びUpdateでAnimatorを実行した結果は以下の通りです。

Layout
Repaint
Update

LayoutとRepaintはアニメーションが正しく再生されていませんが、Updateでは正しく再生されています。よって、AnimatorはUpdate内で再生したほうが良いようです。

参考ページ

うにてぃブログ:【Unity】Editor Mode で Animator を動かす

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