【Unity】 TypefaceAnimator を TextMeshProUGUI に対応する

DEVELOP, Unity

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 で使える様になりました。

Posted by kazupon