【Unity】 TypefaceAnimator を TextMeshProUGUI に対応する
5年程前の Unity 道場で公開されたテキストアニメーションコンポーネントのTypefaceAnimator。
DojoUI1/Assets/Fugaku at master · unity3d-jp/DojoUI1
https://github.com/unity3d-jp/DojoUI1/tree/master/Assets/Fugaku
こちらを TextMeshProUGUI に対応してみます。まずは素直にインポートします。インポートしてエラーが無ければ次へ進みます。
手順
TypefaceAnimator のソースコードをコピーして、Text コンポーネントを TextMeshProUGUI へ置き換えた TypefaceAnimatorTMPro.cs ファイルを作成します。
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using TMPro;
/// <summary>
/// Base class of Typeface Animator
/// </summary>
[RequireComponent(typeof(TextMeshProUGUI)), AddComponentMenu("UI/Effects/TypefaceAnimatorTMPro")]
public class TypefaceAnimatorTMPro : Coffee.UIExtensions.BaseMeshEffect
{
public enum TimeMode
{
Time,
Speed,
}
public enum Style
{
Once,
Loop,
PingPong,
}
public TimeMode timeMode = TimeMode.Time;
public float duration = 1.0f;
public float speed = 5.0f;
public float delay = 0;
public Style style = Style.Once;
public bool playOnAwake = true;
[SerializeField] float m_progress = 1.0f;
public bool usePosition = false;
public bool useRotation = false;
public bool useScale = false;
public bool useAlpha = false;
public bool useColor = false;
public UnityEvent onStart;
public UnityEvent onComplete;
[SerializeField] int characterNumber = 0;
float animationTime = 0;
Coroutine playCoroutine = null;
bool m_isPlaying = false;
public Vector3 positionFrom = Vector3.zero;
public Vector3 positionTo = Vector3.zero;
public AnimationCurve positionAnimationCurve = AnimationCurve.Linear (0, 0, 1, 1);
public float positionSeparation = 0.5f;
public float rotationFrom = 0;
public float rotationTo = 0;
public Vector2 rotationPivot = new Vector2(0.5f, 0.5f);
public AnimationCurve rotationAnimationCurve = AnimationCurve.Linear (0, 0, 1, 1);
public float rotationSeparation = 0.5f;
public bool scaleSyncXY = true;
public float scaleFrom = 0;
public float scaleTo = 1.0f;
public Vector2 scalePivot = new Vector2(0.5f, 0.5f);
public AnimationCurve scaleAnimationCurve = AnimationCurve.Linear (0, 0, 1, 1);
public float scaleFromY = 0;
public float scaleToY = 1.0f;
public Vector2 scalePivotY = new Vector2(0.5f, 0.5f);
public AnimationCurve scaleAnimationCurveY = AnimationCurve.Linear (0, 0, 1, 1);
public float scaleSeparation = 0.5f;
public float alphaFrom = 0.0f;
public float alphaTo = 1.0f;
public AnimationCurve alphaAnimationCurve = AnimationCurve.Linear (0, 0, 1, 1);
public float alphaSeparation = 0.5f;
public Color colorFrom = Color.white;
public Color colorTo = Color.white;
public AnimationCurve colorAnimationCurve = AnimationCurve.Linear (0, 0, 1, 1);
public float colorSeparation = 0.5f;
#if UNITY_EDITOR
protected override void OnValidate()
{
progress = m_progress;
base.OnValidate();
}
#endif
public float progress
{
get { return m_progress; }
set {
m_progress = value;
SetVerticesDirty();
}
}
public bool isPlaying
{
get { return m_isPlaying; }
}
protected override void OnEnable()
{
if(playOnAwake) Play();
base.OnEnable();
}
protected override void OnDisable ()
{
Stop();
base.OnDisable();
}
public void Play()
{
progress = 0;
switch (timeMode)
{
case TimeMode.Time:
animationTime = duration;
break;
case TimeMode.Speed:
animationTime = characterNumber / 10.0f / speed;
break;
}
switch (style)
{
case Style.Once:
playCoroutine = StartCoroutine(PlayOnceCoroutine());
break;
case Style.Loop:
playCoroutine = StartCoroutine(PlayLoopCoroutine());
break;
case Style.PingPong:
playCoroutine = StartCoroutine(PlayPingPongCoroutine());
break;
}
}
public void Stop()
{
if(playCoroutine != null) StopCoroutine(playCoroutine);
m_isPlaying = false;
playCoroutine = null;
}
IEnumerator PlayOnceCoroutine ()
{
if(delay > 0) yield return new WaitForSeconds(delay);
if(m_isPlaying) { yield break; }
m_isPlaying = true;
if(onStart != null) onStart.Invoke();
while(progress < 1.0f)
{
progress += Time.deltaTime / animationTime;
yield return null;
}
m_isPlaying = false;
progress = 1.0f;
if(onComplete != null) onComplete.Invoke();
}
IEnumerator PlayLoopCoroutine ()
{
if(delay > 0) yield return new WaitForSeconds(delay);
if(m_isPlaying) { yield break; }
m_isPlaying = true;
if(onStart != null) onStart.Invoke();
while(true)
{
progress += Time.deltaTime / animationTime;
if(progress > 1.0f)
{
progress -= 1.0f;
}
yield return null;
}
}
IEnumerator PlayPingPongCoroutine ()
{
if(delay > 0) yield return new WaitForSeconds(delay);
if(m_isPlaying) { yield break; }
m_isPlaying = true;
if(onStart != null) onStart.Invoke();
bool isPositive = true;
while(true)
{
float t = Time.deltaTime / animationTime;
if(isPositive)
{
progress += t;
if(progress > 1.0f)
{
isPositive = false;
progress -= t;
}
} else {
progress -= t;
if(progress < 0.0f)
{
isPositive = true;
progress += t;
}
}
yield return null;
}
}
public override void ModifyMesh (VertexHelper vertexHelper)
{
if (!IsActive() || vertexHelper.currentVertCount == 0)
return;
List<UIVertex> list = new List<UIVertex>();
vertexHelper.GetUIVertexStream(list);
List<UIVertex> modifiedList4 = new List<UIVertex>();
for (int i = 0; i < list.Count; i++)
{
int num = i % 6;
if(num == 0 || num == 1 || num == 2 || num == 4)
{
modifiedList4.Add(list[i]);
}
}
// calls the old ModifyVertices which was used on pre 5.2
ModifyVertices(modifiedList4);
List<UIVertex> modifiedList6 = new List<UIVertex>(list.Count);
for (int i = 0; i < list.Count / 6; i++)
{
int i4 = i * 4;
modifiedList6.Add(modifiedList4[i4]);
modifiedList6.Add(modifiedList4[i4 + 1]);
modifiedList6.Add(modifiedList4[i4 + 2]);
modifiedList6.Add(modifiedList4[i4 + 2]);
modifiedList6.Add(modifiedList4[i4 + 3]);
modifiedList6.Add(modifiedList4[i4]);
}
vertexHelper.Clear();
vertexHelper.AddUIVertexTriangleStream(modifiedList6);
}
public void ModifyVertices(List<UIVertex> verts)
{
if (!IsActive())
return;
Modify(verts);
}
void Modify(List<UIVertex> verts)
{
characterNumber = verts.Count / 4;
int currentCharacterNumber = 0;
for (int i = 0; i < verts.Count; i++)
{
if(i % 4 == 0)
{
currentCharacterNumber = i / 4;
UIVertex uiVertex0 = verts[i];
UIVertex uiVertex1 = verts[i + 1];
UIVertex uiVertex2 = verts[i + 2];
UIVertex uiVertex3 = verts[i + 3];
if (usePosition)
{
float temp = positionAnimationCurve.Evaluate(SeparationRate (progress, currentCharacterNumber, characterNumber, positionSeparation));
Vector3 offset = (positionTo - positionFrom) * temp + positionFrom;
uiVertex0.position += offset;
uiVertex1.position += offset;
uiVertex2.position += offset;
uiVertex3.position += offset;
}
if (useScale)
{
if(scaleSyncXY)
{
float temp = scaleAnimationCurve.Evaluate(SeparationRate (progress, currentCharacterNumber, characterNumber, scaleSeparation));
float offset = (scaleTo - scaleFrom) * temp + scaleFrom;
float centerX = (uiVertex1.position.x - uiVertex3.position.x) * scalePivot.x + uiVertex3.position.x;
float centerY = (uiVertex1.position.y - uiVertex3.position.y) * scalePivot.y + uiVertex3.position.y;
Vector3 center = new Vector3(centerX, centerY, 0);
uiVertex0.position = (uiVertex0.position - center) * offset + center;
uiVertex1.position = (uiVertex1.position - center) * offset + center;
uiVertex2.position = (uiVertex2.position - center) * offset + center;
uiVertex3.position = (uiVertex3.position - center) * offset + center;
} else {
float temp = scaleAnimationCurve.Evaluate(SeparationRate (progress, currentCharacterNumber, characterNumber, scaleSeparation));
float offset = (scaleTo - scaleFrom) * temp + scaleFrom;
float centerX = (uiVertex1.position.x - uiVertex3.position.x) * scalePivot.x + uiVertex3.position.x;
float centerY = (uiVertex1.position.y - uiVertex3.position.y) * scalePivot.y + uiVertex3.position.y;
Vector3 center = new Vector3(centerX, centerY, 0);
uiVertex0.position = new Vector3(((uiVertex0.position - center) * offset + center).x, uiVertex0.position.y, uiVertex0.position.z);
uiVertex1.position = new Vector3(((uiVertex1.position - center) * offset + center).x, uiVertex1.position.y, uiVertex1.position.z);
uiVertex2.position = new Vector3(((uiVertex2.position - center) * offset + center).x, uiVertex2.position.y, uiVertex2.position.z);
uiVertex3.position = new Vector3(((uiVertex3.position - center) * offset + center).x, uiVertex3.position.y, uiVertex3.position.z);
temp = scaleAnimationCurveY.Evaluate(SeparationRate (progress, currentCharacterNumber, characterNumber, scaleSeparation));
offset = (scaleToY - scaleFromY) * temp + scaleFromY;
centerX = (uiVertex1.position.x - uiVertex3.position.x) * scalePivotY.x + uiVertex3.position.x;
centerY = (uiVertex1.position.y - uiVertex3.position.y) * scalePivotY.y + uiVertex3.position.y;
center = new Vector3(centerX, centerY, 0);
uiVertex0.position = new Vector3(uiVertex0.position.x, ((uiVertex0.position - center) * offset + center).y, uiVertex0.position.z);
uiVertex1.position = new Vector3(uiVertex1.position.x, ((uiVertex1.position - center) * offset + center).y, uiVertex1.position.z);
uiVertex2.position = new Vector3(uiVertex2.position.x, ((uiVertex2.position - center) * offset + center).y, uiVertex2.position.z);
uiVertex3.position = new Vector3(uiVertex3.position.x, ((uiVertex3.position - center) * offset + center).y, uiVertex3.position.z);
}
}
if (useRotation)
{
float temp = rotationAnimationCurve.Evaluate(SeparationRate (progress, currentCharacterNumber, characterNumber, rotationSeparation));
float offset = (rotationTo - rotationFrom) * temp + rotationFrom;
float centerX = (uiVertex1.position.x - uiVertex3.position.x) * rotationPivot.x + uiVertex3.position.x;
float centerY = (uiVertex1.position.y - uiVertex3.position.y) * rotationPivot.y + uiVertex3.position.y;
Vector3 center = new Vector3(centerX, centerY, 0);
uiVertex0.position = Quaternion.AngleAxis(offset, Vector3.forward) * (uiVertex0.position - center) + center;
uiVertex1.position = Quaternion.AngleAxis(offset, Vector3.forward) * (uiVertex1.position - center) + center;
uiVertex2.position = Quaternion.AngleAxis(offset, Vector3.forward) * (uiVertex2.position - center) + center;
uiVertex3.position = Quaternion.AngleAxis(offset, Vector3.forward) * (uiVertex3.position - center) + center;
}
Color col = uiVertex0.color;
if (useColor)
{
float temp = colorAnimationCurve.Evaluate(SeparationRate (progress, currentCharacterNumber, characterNumber, colorSeparation));
col = (colorTo - colorFrom) * temp + colorFrom;
uiVertex0.color = uiVertex1.color = uiVertex2.color = uiVertex3.color = col;
}
if(useAlpha)
{
float temp = alphaAnimationCurve.Evaluate(SeparationRate (progress, currentCharacterNumber, characterNumber, alphaSeparation));
float a = (alphaTo - alphaFrom) * temp + alphaFrom;
col = new Color(col.r, col.g, col.b, col.a * a);
uiVertex0.color = uiVertex1.color = uiVertex2.color = uiVertex3.color = col;
}
verts[i] = uiVertex0;
verts[i + 1] = uiVertex1;
verts[i + 2] = uiVertex2;
verts[i + 3] = uiVertex3;
}
}
}
static float SeparationRate (float progress, int currentCharacterNumber, int characterNumber, float separation)
{
return Mathf.Clamp01((progress - currentCharacterNumber * separation / characterNumber) / (separation / characterNumber + 1 - separation));
}
}
次に、Editor フォルダに以下のソースファイルを追加します。
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(TypefaceAnimatorTMPro), true)]
public class TypefaceAnimatorTMProEditor : Editor
{
public override void OnInspectorGUI ()
{
GUILayout.Space(5f);
//base.OnInspectorGUI();
DrawProperties();
}
protected void DrawProperties ()
{
TypefaceAnimatorTMPro ta = target as TypefaceAnimatorTMPro;
EditorGUIUtility.labelWidth = 120f;
GUI.changed = false;
TypefaceAnimatorTMPro.TimeMode timeMode = (TypefaceAnimatorTMPro.TimeMode)EditorGUILayout.EnumPopup("Time Mode", ta.timeMode);
if(timeMode == TypefaceAnimatorTMPro.TimeMode.Time)
{
GUILayout.BeginHorizontal();
float duration = EditorGUILayout.FloatField("Duration", ta.duration, GUILayout.Width(160f));
GUILayout.Label("seconds");
GUILayout.EndHorizontal();
if (GUI.changed)
{
TypefaceTools.RegisterUndo("Time Change", ta);
ta.duration = Mathf.Max(duration, 0.01f);
EditorUtility.SetDirty(ta);
}
} else if (timeMode == TypefaceAnimatorTMPro.TimeMode.Speed) {
float speed = EditorGUILayout.FloatField("Speed", ta.speed, GUILayout.Width(160f));
if (GUI.changed)
{
TypefaceTools.RegisterUndo("Speed Change", ta);
ta.speed = Mathf.Max(speed, 0.01f);
EditorUtility.SetDirty(ta);
}
}
GUILayout.BeginHorizontal();
float delay = EditorGUILayout.FloatField("Start Delay", ta.delay, GUILayout.Width(160f));
GUILayout.Label("seconds");
GUILayout.EndHorizontal();
TypefaceAnimatorTMPro.Style style = (TypefaceAnimatorTMPro.Style)EditorGUILayout.EnumPopup("Play Style", ta.style);
bool playOnAwake = EditorGUILayout.Toggle("Play On Awake", ta.playOnAwake);
float progress = EditorGUILayout.Slider("Progress", ta.progress, 0f, 1f);
if (GUI.changed)
{
TypefaceTools.RegisterUndo("Base Change", ta);
ta.timeMode = timeMode;
ta.delay = Mathf.Max(delay, 0f);
ta.style = style;
ta.playOnAwake = playOnAwake;
ta.progress = progress;
EditorUtility.SetDirty(ta);
}
if (TypefaceTools.DrawHeader("Position", ref ta.usePosition))
{
ta.usePosition = true;
Vector3 from = EditorGUILayout.Vector3Field("From", ta.positionFrom);
Vector3 to = EditorGUILayout.Vector3Field("To", ta.positionTo);
AnimationCurve positionAnimationCurve = EditorGUILayout.CurveField("Animation Curve", ta.positionAnimationCurve);
float positionSeparation = EditorGUILayout.Slider("Separation", ta.positionSeparation, 0f, 1f);
if (GUI.changed)
{
TypefaceTools.RegisterUndo("Position Change", ta);
ta.positionFrom = from;
ta.positionTo = to;
ta.positionAnimationCurve = positionAnimationCurve;
ta.positionSeparation = positionSeparation;
EditorUtility.SetDirty(ta);
}
}
if (TypefaceTools.DrawHeader("Rotation", ref ta.useRotation))
{
ta.useRotation = true;
float from = EditorGUILayout.FloatField("From", ta.rotationFrom);
float to = EditorGUILayout.FloatField("To", ta.rotationTo);
Vector2 pivot = EditorGUILayout.Vector2Field("Pivot", ta.rotationPivot);
AnimationCurve rotationAnimationCurve = EditorGUILayout.CurveField("Animation Curve", ta.rotationAnimationCurve);
float rotationSeparation = EditorGUILayout.Slider("Separation", ta.rotationSeparation, 0f, 1f);
if (GUI.changed)
{
TypefaceTools.RegisterUndo("Rotaion Change", ta);
ta.rotationFrom = from;
ta.rotationTo = to;
ta.rotationPivot = new Vector2(Mathf.Clamp01(pivot.x), Mathf.Clamp01(pivot.y));
ta.rotationAnimationCurve = rotationAnimationCurve;
ta.rotationSeparation = rotationSeparation;
EditorUtility.SetDirty(ta);
}
}
if (TypefaceTools.DrawHeader("Scale", ref ta.useScale))
{
ta.useScale = true;
var boldtext = new GUIStyle (GUI.skin.label);
boldtext.fontStyle = FontStyle.Bold;
bool scaleSyncXY = EditorGUILayout.Toggle("Sync XY", ta.scaleSyncXY);
if(scaleSyncXY)
{
float from = EditorGUILayout.FloatField("From", ta.scaleFrom);
float to = EditorGUILayout.FloatField("To", ta.scaleTo);
Vector2 pivot = EditorGUILayout.Vector2Field("Pivot", ta.scalePivot);
AnimationCurve scaleAnimationCurve = EditorGUILayout.CurveField("Animation Curve", ta.scaleAnimationCurve);
float scaleSeparation = EditorGUILayout.Slider("Separation", ta.scaleSeparation, 0f, 1f);
if (GUI.changed)
{
TypefaceTools.RegisterUndo("Scale Change", ta);
ta.scaleSyncXY = scaleSyncXY;
ta.scaleFrom = from;
ta.scaleTo = to;
ta.scalePivot = new Vector2(Mathf.Clamp01(pivot.x), Mathf.Clamp01(pivot.y));
ta.scaleAnimationCurve = scaleAnimationCurve;
ta.scaleSeparation = scaleSeparation;
EditorUtility.SetDirty(ta);
}
} else {
EditorGUILayout.LabelField("Scalse X", boldtext);
float from = EditorGUILayout.FloatField(" From", ta.scaleFrom);
float to = EditorGUILayout.FloatField(" To", ta.scaleTo);
Vector2 pivot = EditorGUILayout.Vector2Field(" Pivot", ta.scalePivot);
AnimationCurve scaleAnimationCurve = EditorGUILayout.CurveField(" Animation Curve", ta.scaleAnimationCurve);
EditorGUILayout.LabelField("Scalse Y", boldtext);
float fromY = EditorGUILayout.FloatField(" From", ta.scaleFromY);
float toY = EditorGUILayout.FloatField(" To", ta.scaleToY);
Vector2 pivotY = EditorGUILayout.Vector2Field(" Pivot", ta.scalePivotY);
AnimationCurve scaleAnimationCurveY = EditorGUILayout.CurveField(" Animation Curve", ta.scaleAnimationCurveY);
float scaleSeparation = EditorGUILayout.Slider("Separation", ta.scaleSeparation, 0f, 1f);
if (GUI.changed)
{
TypefaceTools.RegisterUndo("Scale Change", ta);
ta.scaleSyncXY = scaleSyncXY;
ta.scaleFrom = from;
ta.scaleTo = to;
ta.scalePivot = new Vector2(Mathf.Clamp01(pivot.x), Mathf.Clamp01(pivot.y));
ta.scaleAnimationCurve = scaleAnimationCurve;
ta.scaleFromY = fromY;
ta.scaleToY = toY;
ta.scalePivotY = new Vector2(Mathf.Clamp01(pivotY.x), Mathf.Clamp01(pivotY.y));
ta.scaleAnimationCurveY = scaleAnimationCurveY;
ta.scaleSeparation = scaleSeparation;
EditorUtility.SetDirty(ta);
}
}
}
if (TypefaceTools.DrawHeader("Alpha", ref ta.useAlpha))
{
ta.useAlpha = true;
float from = EditorGUILayout.Slider("From", ta.alphaFrom, 0f, 1f);
float to = EditorGUILayout.Slider("To", ta.alphaTo, 0f, 1f);
AnimationCurve alphaAnimationCurve = EditorGUILayout.CurveField("Animation Curve", ta.alphaAnimationCurve);
float alphaSeparation = EditorGUILayout.Slider("Separation", ta.alphaSeparation, 0f, 1f);
if (GUI.changed)
{
TypefaceTools.RegisterUndo("Alpha Change", ta);
ta.alphaFrom = from;
ta.alphaTo = to;
ta.alphaAnimationCurve = alphaAnimationCurve;
ta.alphaSeparation = alphaSeparation;
EditorUtility.SetDirty(ta);
}
}
if (TypefaceTools.DrawHeader("Color", ref ta.useColor))
{
ta.useColor = true;
Color from = EditorGUILayout.ColorField("From", ta.colorFrom);
Color to = EditorGUILayout.ColorField("To", ta.colorTo);
AnimationCurve colorAnimationCurve = EditorGUILayout.CurveField("Animation Curve", ta.colorAnimationCurve);
float colorSeparation = EditorGUILayout.Slider("Separation", ta.colorSeparation, 0f, 1f);
if (GUI.changed)
{
TypefaceTools.RegisterUndo("Color Change", ta);
ta.colorFrom = from;
ta.colorTo = to;
ta.colorAnimationCurve = colorAnimationCurve;
ta.colorSeparation = colorSeparation;
EditorUtility.SetDirty(ta);
}
}
EditorGUILayout.PropertyField(serializedObject.FindProperty("onStart"), new GUIContent("On Start"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("onComplete"), new GUIContent("On Complete"));
}
}
これで TypeFaceAnimation の機能が TextMeshProUGUI で使える様になりました。