UnityとTestMesh Proで文字アニメーションを作っていると、頻繁に動きを確認しながら進めることになります。このとき、確認のたびにシーンを再生してもいいのですが、部分的に確認するだけの場合では、少々面倒です。もっと手軽に確認したいので、シーンを再生せずにEditor上で確認するものを作ってみました。
また、コードを読んでもらえばわかりますが、TextMesh Pro以外でもEditor上から、一定間隔で実行したい処理の確認などに使うこともできるでしょう。
今回は、そのコードの紹介になります。
後述しますが、シーン再生時と完全に一緒にはならない課題が残っているコードではあります。実装中の簡単な確認時に使い、最終チェックはシーンを再生して行うなどの使い分けが必要かもしれません。
もし解決方法をご存知の方は、指摘していただけると嬉しいです。
実行環境
- Unity 2018.2
- TextMesh Pro 1.3.0
何ができるのか
コードの全文
部分ごとの説明をする前に、コードの全文を載せます。
やっていることは、あまり多くありません。
大まかには、以下の2点です。
- アニメーション用の処理を呼び出す
- Editor上で一定間隔で更新されるようにする
アニメーション用の処理
// 説明のためのダミー
public class CircleText : MonoBehaviour {
private void Awake() {
//初期化処理
}
void Update() {
// アニメーションさせる処理
}
}
プレビュ用の処理
このファイルはEditorフォルダの下に置いてください。
詳しくは、Unityのエディタ拡張について調べると分かるはずです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;
using System.Reflection;
[CustomEditor(typeof(CircleText)), ExecuteInEditMode]
public class PreviewText : Editor {
static double prevUpdateTime = 0;
MethodInfo updateMethod;
CircleText circleText;
public override void OnInspectorGUI() {
base.OnInspectorGUI();
circleText = target as CircleText;
Type t = circleText.GetType();
updateMethod =
t.GetMethod(
"Update",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (GUILayout.Button("Preview")) {
MethodInfo mi =
t.GetMethod(
"Awake",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
mi.Invoke(circleText, null);
EditorApplication.update += UpdateAtEditor;
}
if (GUILayout.Button("Stop")) {
EditorApplication.update -= UpdateAtEditor;
}
}
private void UpdateAtEditor() {
if ((EditorApplication.timeSinceStartup - prevUpdateTime) < 0.016666f) {
return;
}
prevUpdateTime = EditorApplication.timeSinceStartup;
updateMethod.Invoke(circleText, null);
SceneView.RepaintAll();
}
}
ボタンを作る
これは、簡単です。
UnityEditor.Editorを継承したクラス内で、以下のように書くだけで、Editorのinspector上にボタンが表示されるようになります。
if (GUILayout.Button("ボタンに表示する文字")) {
// ボタンが押されたときの処理
}
privateなアニメーション用処理を呼び出す
ここからが、ちょっと厄介なことになります。
アニメーション用の処理を実行したいのですが、この処理がprivate関数(他クラスからアクセスできない)になっているので、処理が呼び出せません。
今回のコードでは、UnityのAwake関数、Update関数を実行していますが、自分で定義した関数でも同じようにできます。そこで、処理を関数に切り出して、修飾子をpublicに変えてもいいですが、Editor側の都合で、実際のコードが影響を受けるのは、芳しくありません。
そのため、リフレクションを使用して、privateな関数を呼び出しています。
circleText = target as CircleText;
Type t = circleText.GetType();
updateMethod = t.GetMethod("Update",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
updateMethod.Invoke(circleText, null);
このように書くことで、アニメーション処理クラス(CircleText)のUpdate関数が実行できます。
また、このとき以下の属性をクラスに設定する必要があります。
"CircleText"のところは、対象となるクラス名になります。
[CustomEditor(typeof(CircleText))]
ポイントは、GetMethod関数の第1引数で実行したい関数名を文字列で指定します。ここでは、"Update"です。Invoke関数での第1引数でクラスのインスタンスを指定します。今回はUpdate関数に引数がないため、第2引数はnullになっています。
Editor上から定期的に更新されるようにする
以下のようにすることで、定期的にEditor上から関数を呼び出す設定ができます。
"+"で追加し、"-"で削除します。
そのため、"stop"ボタンを押したときは、"-"で削除しています。
Editorの処理のため、削除し忘れるとEditorを終了しない限り実行され続けるので、注意してください。
EditorApplication.update += UpdateAtEditor; // 追加
EditorApplication.update -= UpdateAtEditor; // 削除
ただし、ここで注意しないといけないのは、"EditorApplication.update"が100fpsで更新されている点です。(Unity Documentation)
60fpsで再生する想定の場合は、合わせる必要があります。
そのため、以下のように時間(1/60 = 0.01666)になるまで、処理をスキップするようにしています。
if ((EditorApplication.timeSinceStartup - prevUpdateTime) < 0.016666f) {
return;
}
prevUpdateTime = EditorApplication.timeSinceStartup;
まとめ
今回、アニメーションをEditor上で簡易に確認するコードを説明しました。
この方法は、アニメーションの確認だけでなく、他にもEditor上から何かしらの処理を呼び出したいときに使用できます。
課題
シーン再生時と完全に一致するようにはできませんでした。しかし、もともとの目的がどのようにアニメーションするかを手軽に確認したいことだったので、そのままになっている課題があります。
- 更新頻度が60fpsになっていないように見える
- 調整する処理を追加していますが、実際に再生したときの移動量を見るとなっていないように見えます
- 移動時に位置を加算する方法をとっているとカクカクした動きになる