TextAnimation

6.9k words

简介

记录研究 Text Animator 插件的实现
Text Animator

Text的区别

Image text
TextMesh 是将每个字符都作为一个Mesh进行渲染显示,相对于原先 Text 将整体划分为一个 Mesh,可拓展性更高

字体的显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Shader "TextMeshPro/Bitmap" {
Properties {
_MainTex ("Font Atlas", 2D) = "white" {}
_FaceTex ("Font Texture", 2D) = "white" {}
_FaceColor ("Text Color", Color) = (1,1,1,1)
}

fixed4 frag (v2f IN) : SV_Target
{
fixed4 color = tex2D(_MainTex, IN.texcoord0);
color = fixed4 (tex2D(_FaceTex, IN.texcoord1).rgb * IN.color.rgb, IN.color.a * color.a);
}
}

其中 MainTex 的 Alpha 通道存储文字的遮罩
Image text
FaceTex 对应着 文本的字体贴图,切换这张贴图可以实现字体的切换

主要组件

TextAnimatorPlayer

默认TextAnimatorPlayer,可以动态显示字母(就像打字机一样)

TextAnimator

控制文本的效果显示

function UpdateEffectsToMesh

实现思路
从 TextMeshProUGUI 组件内容中,解析其中的标签,注册到行为中,获取 Mesh 的顶点数据、顶点色数据,修改后再赋予给 Mesh
自己实现的效果,简单的修改 Mesh 顶点x坐标

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class VertexData
{
public Vector3[] vertices=new Vector3[4];
public Color32[] color = new Color32[4];
}
public class AnimationTest : MonoBehaviour
{
public float timeInterval = 2;
private VertexData[] vertexDatas;
TMP_TextInfo textInfo;
TMP_Text tmp_text;
// Start is called before the first frame update
void Start()
{
tmp_text = GetComponent<TMP_Text>();
textInfo = tmp_text.GetTextInfo(tmp_text.text);
vertexDatas = new VertexData[textInfo.characterCount];
for (int z = 0; z < vertexDatas.Length; z++)
{
vertexDatas[z] = new VertexData();
for (int k = 0; k < 4; k++)
{
vertexDatas[z].vertices[k] = textInfo.meshInfo[textInfo.characterInfo[z].materialReferenceIndex].vertices[textInfo.characterInfo[z].vertexIndex + k];
vertexDatas[z].color[k] = textInfo.meshInfo[textInfo.characterInfo[z].materialReferenceIndex].colors32[textInfo.characterInfo[z].vertexIndex + k];
}
}
}

float lastTime = 0;
Vector3 vector = Vector3.zero;
// Update is called once per frame
void Update()
{
if (Time.realtimeSinceStartup - lastTime > timeInterval) {
vector.x = Random.Range(-1f, 1f) * 10;
lastTime = Time.realtimeSinceStartup;


for (int z = 0; z < vertexDatas.Length; z++)
{
vertexDatas[z].vertices = MoveChar(vertexDatas[z].vertices, vector);
vertexDatas[z].color = RandomColor();
}

UpdateMesh();
}
}

private Color32[] RandomColor() {
Color32[] colors = new Color32[4];
Color32 color = new Color32((byte)Random.Range(155, 255), (byte)Random.Range(155, 255), (byte)Random.Range(155, 255), 255);
for (int k = 0; k < 4; k++) {
colors[k] = color;
}
return colors;
}

private void UpdateMesh()
{
for (int z = 0; z < vertexDatas.Length; z++)
{
for (int k = 0; k < 4; k++)
{
textInfo.meshInfo[textInfo.characterInfo[z].materialReferenceIndex].vertices[textInfo.characterInfo[z].vertexIndex + k] = vertexDatas[z].vertices[k];
textInfo.meshInfo[textInfo.characterInfo[z].materialReferenceIndex].colors32[textInfo.characterInfo[z].vertexIndex+k] = vertexDatas[z].color[k];
}
}
tmp_text.UpdateVertexData();
}
private Vector3[] MoveChar(Vector3[] vec, Vector3 dir)
{
for (byte j = 0; j < vec.Length; j++)
{
vec[j] += dir;
}
return vec;
}

}


Image text
Image text
Image text
UpdateEffectsToMesh 对每个 behaviorEffects 设置时间,开始计算

1
2
3
4
5
for (int i = 0; i < behaviorEffects.Count; i++)
{
behaviorEffects[i].SetAnimatorData(m_time);
behaviorEffects[i].Calculate();
}

对规定区域的字符进行处理,应用 behaviorEffects 的效果

1
2
3
4
5
6
7
8
9
10
void TryApplyingBehaviors()
{
if (enabled_globalBehaviors && enabled_localBehaviors)
{
foreach (int behaviorIndex in characters[i].indexBehaviorEffects)
{
behaviorEffects[behaviorIndex].ApplyEffect(ref characters[i].data, i);
}
}
}

ShakeBehavior
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//在时间间隔控制下,得到随机的索引
public override void Calculate()
{
timePassed += time.deltaTime;
//Changes the shake direction if enough time passed
if (timePassed >= shakeDelay)
{
timePassed = 0;

randIndex = Random.Range(0, TextUtilities.fakeRandomsCount);

//Avoids repeating the same index twice
if (lastRandomIndex == randIndex)
{
randIndex++;
if (randIndex >= TextUtilities.fakeRandomsCount)
randIndex = 0;
}

lastRandomIndex = randIndex;
}
}
//改变顶点位置
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
data.vertices.MoveChar
(
TextUtilities.fakeRandoms[
Mathf.RoundToInt((charIndex + randIndex) % (TextUtilities.fakeRandomsCount - 1))
] * shakeStrength * uniformIntensity
);
}

WiggleBehavior
1
2
3
4
5
//按照正弦函数,改变顶点位置
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
data.vertices.MoveChar(directions[charIndex] * Mathf.Sin(time.timeSinceStart* frequency + charIndex) * amplitude * uniformIntensity);
}

FadeBehavior
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//Tween实现颜色的渐变
Color32 temp;
public override void ApplyEffect(ref CharacterData data, int charIndex)
{
if (data.passedTime <= delay) //not passed enough time yet
return;

charPCTs[charIndex] += time.deltaTime;

if (charPCTs[charIndex] > 1) charPCTs [charIndex] = 1;

//Lerps
if (charPCTs[charIndex] < 1 && charPCTs[charIndex] >= 0)
{
for (var i = 0; i < TextUtilities.verticesPerChar; i++)
{
temp = data.colors[i];
temp.a = 0;

data.colors[i] = Color32.LerpUnclamped(data.colors[i], temp, Tween.EaseInOut(charPCTs[charIndex]));
}
}
else //Keeps them hidden
{
for (var i = 0; i < TextUtilities.verticesPerChar; i++)
{
temp = data.colors[i];
temp.a = 0;

data.colors[i] = temp;
}
}

}

BounceBehavior
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//弹跳
public override void ApplyEffect(ref CharacterData data, int charIndex)
{

//Calculates the tween percentage
float BounceTween(float t)
{
const float stillTime = .2f;
const float easeIn = .2f;
const float bounce = 1 - stillTime - easeIn;

if (t <= easeIn)
return Tween.EaseInOut(t / easeIn);
t -= easeIn;

if (t <= bounce)
return 1 - Tween.BounceOut(t / bounce);

return 0;
}

data.vertices.MoveChar(Vector3.up * uniformIntensity * BounceTween((Mathf.Repeat(time.timeSinceStart * frequency - waveSize * charIndex, 1))) * amplitude);
}

对于每个字符创建相应的数据,BehaviorEffect 通过修改数据中的值,实现对于 TextMesh 进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// <summary>
/// Contains characters data that can be modified during TextAnimator effects.
/// </summary>
public struct CharacterData
{
/// <summary>
/// Time passed since the character is visible (or just started the appearance effect).
/// </summary>
public float passedTime;

/// <summary>
/// A character's vertices colors.
/// </summary>
/// <remarks>
/// The array size is usually <see cref="TextUtilities.verticesPerChar"/>
/// </remarks>
public Color32[] colors;

/// <summary>
/// A character's vertices positions.
/// </summary>
/// <remarks>
/// The array size is usually <see cref="TextUtilities.verticesPerChar"/>
/// </remarks>
public Vector3[] vertices;

/// <summary>
/// Related character information from TextMeshPro.<br/>
/// P.S. If you want to modify vertices/colors, please use the <see cref="vertices"/> and <see cref="colors"/> arrays variables instead.
/// </summary>
public TMPro.TMP_CharacterInfo tmp_CharInfo;
}