TextMeshPro与TextMeshProUGUI
开始使用TextMeshPro没多久,想要通过操控其顶点的方式,为TextMeshPro的文字制作一点动态效果,于是对TextMeshPro进行了一番了解,在此要感谢youtube视频作者Zolran的视频,如果要跳转原作者,需要科学上网.
视频中使用的脚本
using UnityEngine;
using System.Collections;
using TMPro;
public class VertexAttributeModifier : MonoBehaviour {
public enum AnimationMode { VertexColor, UiVertexColor, Wave, Jitter, Warp, WarpUI, Dangling, Reveal };
public AnimationMode MeshAnimationMode = AnimationMode.Wave;
public AnimationCurve VertexCurve = new AnimationCurve(new Keyframe(0, 0), new Keyframe(0.25f, 2.0f), new Keyframe(0.5f, 0), new Keyframe(0.75f, 2.0f), new Keyframe(1, 0f));
public float AngleMultiplier = 1.0f;
public float SpeedMultiplier = 1.0f;
public float CurveScale = 1.0f;
private TextMeshPro m_TextMeshPro;
private TextMeshProUGUI m_TextMeshProUGUI;
private TextContainer m_TextContainer;
private TMP_TextInfo m_textInfo;
private string textLabel = "Text <#ff8000>silliness with TextMesh<#00aaff>Pro!";
private struct VertexAnim
{
public float angleRange;
public float angle;
public float speed;
}
void Awake()
{
// Get a reference to the TextMeshPro Component if one exists. If not add one.
m_TextMeshPro = GetComponent() ?? gameObject.AddComponent();
m_TextMeshProUGUI = GetComponent(); // ?? gameObject.AddComponent();
m_TextMeshPro.alignment = TextAlignmentOptions.Center;
//m_TextMeshPro.enableWordWrapping = true;
//m_TextMeshPro.colorGradient = new VertexGradient(Color.white, Color.white, Color.blue, Color.cyan);
//m_TextMeshPro.enableVertexGradient = true;
m_TextContainer = GetComponent();
//m_TextContainer.width = 40f;
}
void Start()
{
m_TextMeshPro.ForceMeshUpdate(); // We force the mesh update in order to get the mesh created so we can have valid data to play with :)
switch (MeshAnimationMode)
{
case AnimationMode.VertexColor:
StartCoroutine(AnimateVertexColors());
break;
case AnimationMode.UiVertexColor:
StartCoroutine(AnimateUIVertexColors());
break;
case AnimationMode.Wave:
StartCoroutine(AnimateVertexPositions());
break;
case AnimationMode.Jitter:
StartCoroutine(AnimateVertexPositionsII());
break;
case AnimationMode.Warp:
StartCoroutine(AnimateVertexPositionsIII());
break;
case AnimationMode.WarpUI:
StartCoroutine(AnimateUIVertexPositionsIII());
break;
case AnimationMode.Dangling:
StartCoroutine(AnimateVertexPositionsIV());
break;
case AnimationMode.Reveal:
StartCoroutine(AnimateVertexPositionsVI());
break;
//case AnimationMode.Test:
// StartCoroutine(AnimateVertexPositionsV());
// break;
}
}
IEnumerator AnimateVertexColors()
{
TMP_TextInfo textInfo = m_TextMeshPro.textInfo;
int currentCharacter = 0;
Color32[] newVertexColors = textInfo.meshInfo.vertexColors;
Color32 c0 = m_TextMeshPro.color;
c0.a = 127;
Color32 c1 = c0;
m_TextMeshPro.renderMode = TextRenderFlags.DontRender;
while (true)
{
int characterCount = textInfo.characterCount;
// If No Characters then just yield and wait for some text to be added
if (characterCount == 0)
{
yield return new WaitForSeconds(0.25f);
continue;
}
newVertexColors = textInfo.meshInfo.vertexColors;
currentCharacter = (currentCharacter + 1) % characterCount;
int vertexIndex = textInfo.characterInfo[currentCharacter].vertexIndex;
if (!textInfo.characterInfo[currentCharacter].isVisible)
continue;
if (currentCharacter == 0)
{
c0 = new Color32((byte)Random.Range(0, 255), (byte)Random.Range(0, 255), (byte)Random.Range(0, 255), 127);
}
c1 = new Color32((byte)Random.Range(0, 255), (byte)Random.Range(0, 255), (byte)Random.Range(0, 255), 127);
newVertexColors[vertexIndex + 0] = c1;
newVertexColors[vertexIndex + 1] = c1;
newVertexColors[vertexIndex + 2] = c1;
newVertexColors[vertexIndex + 3] = c1;
m_TextMeshPro.mesh.vertices = textInfo.meshInfo.vertices;
m_TextMeshPro.mesh.uv = textInfo.meshInfo.uv0s;
m_TextMeshPro.mesh.uv2 = textInfo.meshInfo.uv2s;
m_TextMeshPro.mesh.colors32 = newVertexColors;
yield return new WaitForSeconds(0.05f);
}
}
IEnumerator AnimateUIVertexColors()
{
TMP_TextInfo textInfo = m_TextMeshProUGUI.textInfo;
CanvasRenderer uiRenderer = m_TextMeshProUGUI.canvasRenderer;
int currentCharacter = 0;
UIVertex[] uiVertices; // = textInfo.meshInfo.uiVertices;
Color32 c0 = m_TextMeshProUGUI.color;
c0.a = 127; // Since we are modifying the vertex color directly, we need to be mindful that bold information is encoded in the alpha. 0 - 127 is normal weight and 128 - 255 is bold.
Color32 c1 = c0;
m_TextMeshPro.renderMode = TextRenderFlags.DontRender;
while (true)
{
uiVertices = textInfo.meshInfo.uiVertices;
int characterCount = textInfo.characterCount;
// If No Characters then just yield and wait for some text to be added
if (characterCount == 0)
{
yield return new WaitForSeconds(0.25f);
continue;
}
if (textInfo.characterInfo[currentCharacter].isVisible)
{
int vertexIndex = textInfo.characterInfo[currentCharacter].vertexIndex;
// Pick new bottom color once per cycle
if (currentCharacter == 0)
{
c0 = new Color32((byte)Random.Range(0, 255), (byte)Random.Range(0, 255), (byte)Random.Range(0, 255), 127);
}
c1 = new Color32((byte)Random.Range(0, 255), (byte)Random.Range(0, 255), (byte)Random.Range(0, 255), 127);
uiVertices[vertexIndex + 0].color = c0;
uiVertices[vertexIndex + 1].color = c1;
uiVertices[vertexIndex + 2].color = c1;
uiVertices[vertexIndex + 3].color = c0;
uiRenderer.SetVertices(uiVertices, uiVertices.Length);
}
currentCharacter = (currentCharacter + 1) % characterCount;
yield return new WaitForSeconds(0.05f);
}
}
IEnumerator AnimateVertexPositions()
{
VertexCurve.preWrapMode = WrapMode.Loop;
VertexCurve.postWrapMode = WrapMode.Loop;
Vector3[] newVertexPositions;
//Matrix4x4 matrix;
int loopCount = 0;
while (true)
{
m_TextMeshPro.renderMode = TextRenderFlags.DontRender; // Instructing TextMesh Pro not to upload the mesh as we will be modifying it.
m_TextMeshPro.ForceMeshUpdate(); // Generate the mesh and populate the textInfo with data we can use and manipulate.
TMP_TextInfo textInfo = m_TextMeshPro.textInfo;
int characterCount = textInfo.characterCount;
newVertexPositions = textInfo.meshInfo.vertices;
for (int i = 0; i < characterCount; i++)
{
if (!textInfo.characterInfo[i].isVisible)
continue;
int vertexIndex = textInfo.characterInfo[i].vertexIndex;
float offsetY = VertexCurve.Evaluate((float)i / characterCount + loopCount / 50f) * CurveScale; // Random.Range(-0.25f, 0.25f);
newVertexPositions[vertexIndex + 0].y += offsetY;
newVertexPositions[vertexIndex + 1].y += offsetY;
newVertexPositions[vertexIndex + 2].y += offsetY;
newVertexPositions[vertexIndex + 3].y += offsetY;
}
loopCount += 1;
// Upload the mesh with the revised information
m_TextMeshPro.mesh.vertices = newVertexPositions;
m_TextMeshPro.mesh.uv = m_TextMeshPro.textInfo.meshInfo.uv0s;
m_TextMeshPro.mesh.uv2 = m_TextMeshPro.textInfo.meshInfo.uv2s;
m_TextMeshPro.mesh.colors32 = m_TextMeshPro.textInfo.meshInfo.vertexColors;
yield return new WaitForSeconds(0.025f);
}
}
IEnumerator AnimateVertexPositionsII()
{
Matrix4x4 matrix;
Vector3[] vertices;
int loopCount = 0;
// Create an Array which contains pre-computed Angle Ranges and Speeds for a bunch of characters.
VertexAnim[] vertexAnim = new VertexAnim[1024];
for (int i = 0; i < 1024; i++ )
{
vertexAnim[i].angleRange = Random.Range(10f, 25f);
vertexAnim[i].speed = Random.Range(1f, 3f);
}
m_TextMeshPro.renderMode = TextRenderFlags.DontRender;
while (loopCount < 10000)
{
m_TextMeshPro.ForceMeshUpdate();
vertices = m_TextMeshPro.textInfo.meshInfo.vertices;
int characterCount = m_TextMeshPro.textInfo.characterCount;
for (int i = 0; i < characterCount; i++)
{
// Setup initial random values
VertexAnim vertAnim = vertexAnim[i];
TMP_CharacterInfo charInfo = m_TextMeshPro.textInfo.characterInfo[i];
// Skip Characters that are not visible
if (!charInfo.isVisible)
continue;
int vertexIndex = charInfo.vertexIndex;
//Vector2 charMidTopline = new Vector2((vertices[vertexIndex + 0].x + vertices[vertexIndex + 2].x) / 2, charInfo.topRight.y);
Vector2 charMidBasline = new Vector2((vertices[vertexIndex + 0].x + vertices[vertexIndex + 2].x) / 2, charInfo.baseLine);
// Need to translate all 4 vertices of each quad to aligned with middle of character / baseline.
//Vector3 offset = charMidTopline;
Vector3 offset = charMidBasline;
vertices[vertexIndex + 0] += -offset;
vertices[vertexIndex + 1] += -offset;
vertices[vertexIndex + 2] += -offset;
vertices[vertexIndex + 3] += -offset;
vertAnim.angle = Mathf.SmoothStep(-vertAnim.angleRange, vertAnim.angleRange, Mathf.PingPong(loopCount / 25f * vertAnim.speed, 1f));
Vector3 jitterOffset = new Vector3(Random.Range(-.25f, .25f), Random.Range(-.25f, .25f), 0);
//matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 0, vertexAnim[i].angle), Vector3.one);
//matrix = Matrix4x4.TRS(jitterOffset, Quaternion.identity, Vector3.one);
matrix = Matrix4x4.TRS(jitterOffset * CurveScale, Quaternion.Euler(0, 0, Random.Range(-5f, 5f) * AngleMultiplier), Vector3.one);
vertices[vertexIndex + 0] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 0]);
vertices[vertexIndex + 1] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 1]);
vertices[vertexIndex + 2] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 2]);
vertices[vertexIndex + 3] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 3]);
vertices[vertexIndex + 0] += offset;
vertices[vertexIndex + 1] += offset;
vertices[vertexIndex + 2] += offset;
vertices[vertexIndex + 3] += offset;
vertexAnim[i] = vertAnim;
}
loopCount += 1;
m_TextMeshPro.mesh.vertices = vertices;
m_TextMeshPro.mesh.uv = m_TextMeshPro.textInfo.meshInfo.uv0s;
m_TextMeshPro.mesh.uv2 = m_TextMeshPro.textInfo.meshInfo.uv2s;
//m_TextMeshPro.mesh.colors32 = m_TextMeshPro.textInfo.meshInfo.vertexColors;
//Debug.Log("Vertex Attributes Modified.");
yield return new WaitForSeconds(0.1f * SpeedMultiplier);
}
}
private AnimationCurve CopyAnimationCurve(AnimationCurve curve)
{
AnimationCurve newCurve = new AnimationCurve();
newCurve.keys = curve.keys;
return newCurve;
}
IEnumerator AnimateVertexPositionsIII()
{
VertexCurve.preWrapMode = WrapMode.Clamp;
VertexCurve.postWrapMode = WrapMode.Clamp;
Vector3[] vertexPositions;
Matrix4x4 matrix;
//int loopCount = 0;
m_TextMeshPro.hasChanged = true; // Need to force the TextMeshPro Object to be updated.
float old_CurveScale = CurveScale;
AnimationCurve old_curve = CopyAnimationCurve(VertexCurve);
while (true)
{
if (!m_TextMeshPro.hasChanged && old_CurveScale == CurveScale && old_curve.keys[1].value == VertexCurve.keys[1].value)
{
yield return null;
continue;
}
old_CurveScale = CurveScale;
old_curve = CopyAnimationCurve(VertexCurve);
//Debug.Log("Updating object!");
m_TextMeshPro.renderMode = TextRenderFlags.DontRender; // Instructing TextMesh Pro not to upload the mesh as we will be modifying it.
m_TextMeshPro.ForceMeshUpdate(); // Generate the mesh and populate the textInfo with data we can use and manipulate.
TMP_TextInfo textInfo = m_TextMeshPro.textInfo;
int characterCount = textInfo.characterCount;
Debug.Log(characterCount);
if (characterCount == 0) continue;
vertexPositions = textInfo.meshInfo.vertices;
//int lastVertexIndex = textInfo.characterInfo[characterCount - 1].vertexIndex;
float boundsMinX = m_TextMeshPro.bounds.min.x;
float boundsMaxX = m_TextMeshPro.bounds.max.x;
for (int i = 0; i < characterCount; i++)
{
if (!textInfo.characterInfo[i].isVisible)
continue;
int vertexIndex = textInfo.characterInfo[i].vertexIndex;
// Compute the baseline mid point for each character
Vector3 offsetToMidBaseline = new Vector2((vertexPositions[vertexIndex + 0].x + vertexPositions[vertexIndex + 2].x) / 2, textInfo.characterInfo[i].baseLine);
//float offsetY = VertexCurve.Evaluate((float)i / characterCount + loopCount / 50f); // Random.Range(-0.25f, 0.25f);
// Apply offset to adjust our pivot point.
vertexPositions[vertexIndex + 0] += -offsetToMidBaseline;
vertexPositions[vertexIndex + 1] += -offsetToMidBaseline;
vertexPositions[vertexIndex + 2] += -offsetToMidBaseline;
vertexPositions[vertexIndex + 3] += -offsetToMidBaseline;
// Compute the angle of rotation for each character based on the animation curve
float x0 = (offsetToMidBaseline.x - boundsMinX) / (boundsMaxX - boundsMinX); // Character's position relative to the bounds of the mesh.
float x1 = x0 + 0.0001f;
float y0 = VertexCurve.Evaluate(x0) * CurveScale;
float y1 = VertexCurve.Evaluate(x1) * CurveScale;
Vector3 horizontal = new Vector3(1, 0, 0);
//Vector3 normal = new Vector3(-(y1 - y0), (x1 * (boundsMaxX - boundsMinX) + boundsMinX) - offsetToMidBaseline.x, 0);
Vector3 tangent = new Vector3(x1 * (boundsMaxX - boundsMinX) + boundsMinX, y1) - new Vector3(offsetToMidBaseline.x, y0);
float dot = Mathf.Acos(Vector3.Dot(horizontal, tangent.normalized)) * 57.2957795f;
Vector3 cross = Vector3.Cross(horizontal, tangent);
float angle = cross.z > 0 ? dot : 360 - dot;
matrix = Matrix4x4.TRS(new Vector3(0, 0, y0), Quaternion.Euler(0, -angle, 0), Vector3.one);
vertexPositions[vertexIndex + 0] = matrix.MultiplyPoint3x4(vertexPositions[vertexIndex + 0]);
vertexPositions[vertexIndex + 1] = matrix.MultiplyPoint3x4(vertexPositions[vertexIndex + 1]);
vertexPositions[vertexIndex + 2] = matrix.MultiplyPoint3x4(vertexPositions[vertexIndex + 2]);
vertexPositions[vertexIndex + 3] = matrix.MultiplyPoint3x4(vertexPositions[vertexIndex + 3]);
vertexPositions[vertexIndex + 0] += offsetToMidBaseline;
vertexPositions[vertexIndex + 1] += offsetToMidBaseline;
vertexPositions[vertexIndex + 2] += offsetToMidBaseline;
vertexPositions[vertexIndex + 3] += offsetToMidBaseline;
}
// Upload the mesh with the revised information
m_TextMeshPro.mesh.vertices = vertexPositions;
m_TextMeshPro.mesh.uv = m_TextMeshPro.textInfo.meshInfo.uv0s;
m_TextMeshPro.mesh.uv2 = m_TextMeshPro.textInfo.meshInfo.uv2s;
m_TextMeshPro.mesh.colors32 = m_TextMeshPro.textInfo.meshInfo.vertexColors;
yield return new WaitForSeconds(0.025f);
}
}
IEnumerator AnimateUIVertexPositionsIII()
{
VertexCurve.preWrapMode = WrapMode.Clamp;
VertexCurve.postWrapMode = WrapMode.Clamp;
CanvasRenderer uiRenderer = m_TextMeshProUGUI.canvasRenderer;
UIVertex[] uiVertices;
Matrix4x4 matrix;
//int loopCount = 0;
m_TextMeshProUGUI.hasChanged = true; // Need to force the TextMeshPro Object to be updated.
float old_CurveScale = CurveScale;
AnimationCurve old_curve = CopyAnimationCurve(VertexCurve);
while (true)
{
if (!m_TextMeshProUGUI.hasChanged && old_CurveScale == CurveScale && old_curve.keys[1].value == VertexCurve.keys[1].value)
{
yield return null;
continue;
}
old_CurveScale = CurveScale;
old_curve = CopyAnimationCurve(VertexCurve);
//Debug.Log("Updating object!");
m_TextMeshProUGUI.renderMode = TextRenderFlags.DontRender; // Instructing TextMesh Pro not to upload the mesh as we will be modifying it.
m_TextMeshProUGUI.ForceMeshUpdate(); // Generate the mesh and populate the textInfo with data we can use and manipulate.
TMP_TextInfo textInfo = m_TextMeshProUGUI.textInfo;
int characterCount = textInfo.characterCount;
if (characterCount == 0) continue;
uiVertices = textInfo.meshInfo.uiVertices;
//int lastVertexIndex = textInfo.characterInfo[characterCount - 1].vertexIndex;
float boundsMinX = m_TextMeshProUGUI.bounds.min.x;
float boundsMaxX = m_TextMeshProUGUI.bounds.max.x;
for (int i = 0; i < characterCount; i++)
{
if (!textInfo.characterInfo[i].isVisible)
continue;
int vertexIndex = textInfo.characterInfo[i].vertexIndex;
// Compute the baseline mid point for each character
Vector3 offsetToMidBaseline = new Vector2((uiVertices[vertexIndex + 0].position.x + uiVertices[vertexIndex + 2].position.x) / 2, textInfo.characterInfo[i].baseLine);
//float offsetY = VertexCurve.Evaluate((float)i / characterCount + loopCount / 50f); // Random.Range(-0.25f, 0.25f);
// Apply offset to adjust our pivot point.
uiVertices[vertexIndex + 0].position += -offsetToMidBaseline;
uiVertices[vertexIndex + 1].position += -offsetToMidBaseline;
uiVertices[vertexIndex + 2].position += -offsetToMidBaseline;
uiVertices[vertexIndex + 3].position += -offsetToMidBaseline;
// Compute the angle of rotation for each character based on the animation curve
float x0 = (offsetToMidBaseline.x - boundsMinX) / (boundsMaxX - boundsMinX); // Character's position relative to the bounds of the mesh.
float x1 = x0 + 0.0001f;
float y0 = VertexCurve.Evaluate(x0) * CurveScale;
float y1 = VertexCurve.Evaluate(x1) * CurveScale;
Vector3 horizontal = new Vector3(1, 0, 0);
//Vector3 normal = new Vector3(-(y1 - y0), (x1 * (boundsMaxX - boundsMinX) + boundsMinX) - offsetToMidBaseline.x, 0);
Vector3 tangent = new Vector3(x1 * (boundsMaxX - boundsMinX) + boundsMinX, y1) - new Vector3(offsetToMidBaseline.x, y0);
float dot = Mathf.Acos(Vector3.Dot(horizontal, tangent.normalized)) * 57.2957795f;
Vector3 cross = Vector3.Cross(horizontal, tangent);
float angle = cross.z > 0 ? dot : 360 - dot;
matrix = Matrix4x4.TRS(new Vector3(0, y0, 0), Quaternion.Euler(0, 0, angle), Vector3.one);
uiVertices[vertexIndex + 0].position = matrix.MultiplyPoint3x4(uiVertices[vertexIndex + 0].position);
uiVertices[vertexIndex + 1].position = matrix.MultiplyPoint3x4(uiVertices[vertexIndex + 1].position);
uiVertices[vertexIndex + 2].position = matrix.MultiplyPoint3x4(uiVertices[vertexIndex + 2].position);
uiVertices[vertexIndex + 3].position = matrix.MultiplyPoint3x4(uiVertices[vertexIndex + 3].position);
uiVertices[vertexIndex + 0].position += offsetToMidBaseline;
uiVertices[vertexIndex + 1].position += offsetToMidBaseline;
uiVertices[vertexIndex + 2].position += offsetToMidBaseline;
uiVertices[vertexIndex + 3].position += offsetToMidBaseline;
}
// Upload the mesh with the revised information
uiRenderer.SetVertices(uiVertices, uiVertices.Length);
yield return new WaitForSeconds(0.025f);
}
}
IEnumerator AnimateVertexPositionsIV()
{
Matrix4x4 matrix;
Vector3[] vertices;
int loopCount = 0;
// Create an Array which contains pre-computed Angle Ranges and Speeds for a bunch of characters.
VertexAnim[] vertexAnim = new VertexAnim[1024];
for (int i = 0; i < 1024; i++)
{
vertexAnim[i].angleRange = Random.Range(10f, 25f);
vertexAnim[i].speed = Random.Range(1f, 3f);
}
m_TextMeshPro.renderMode = TextRenderFlags.DontRender;
while (loopCount < 10000)
{
m_TextMeshPro.ForceMeshUpdate();
vertices = m_TextMeshPro.textInfo.meshInfo.vertices;
int characterCount = m_TextMeshPro.textInfo.characterCount;
for (int i = 0; i < characterCount; i++)
{
// Setup initial random values
VertexAnim vertAnim = vertexAnim[i];
TMP_CharacterInfo charInfo = m_TextMeshPro.textInfo.characterInfo[i];
// Skip Characters that are not visible
if (!charInfo.isVisible)
continue;
int vertexIndex = charInfo.vertexIndex;
Vector2 charMidTopline = new Vector2((vertices[vertexIndex + 0].x + vertices[vertexIndex + 2].x) / 2, charInfo.topRight.y);
// Vector2 charMidBasline = new Vector2((vertices[vertexIndex + 0].x + vertices[vertexIndex + 2].x) / 2, charInfo.baseLine);
// Need to translate all 4 vertices of each quad to aligned with middle of character / baseline.
Vector3 offset = charMidTopline;
// Vector3 offset = charMidBasline;
vertices[vertexIndex + 0] += -offset;
vertices[vertexIndex + 1] += -offset;
vertices[vertexIndex + 2] += -offset;
vertices[vertexIndex + 3] += -offset;
vertAnim.angle = Mathf.SmoothStep(-vertAnim.angleRange, vertAnim.angleRange, Mathf.PingPong(loopCount / 25f * vertAnim.speed, 1f));
Vector3 jitterOffset = new Vector3(Random.Range(-.25f, .25f), Random.Range(-.25f, .25f), 0);
matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 0, vertexAnim[i].angle), Vector3.one);
//matrix = Matrix4x4.TRS(jitterOffset, Quaternion.identity, Vector3.one);
//matrix = Matrix4x4.TRS(jitterOffset, Quaternion.Euler(0, 0, Random.Range(-5f, 5f)), Vector3.one);
vertices[vertexIndex + 0] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 0]);
vertices[vertexIndex + 1] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 1]);
vertices[vertexIndex + 2] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 2]);
vertices[vertexIndex + 3] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 3]);
vertices[vertexIndex + 0] += offset;
vertices[vertexIndex + 1] += offset;
vertices[vertexIndex + 2] += offset;
vertices[vertexIndex + 3] += offset;
vertexAnim[i] = vertAnim;
}
loopCount += 1;
m_TextMeshPro.mesh.vertices = vertices;
m_TextMeshPro.mesh.uv = m_TextMeshPro.textInfo.meshInfo.uv0s;
m_TextMeshPro.mesh.uv2 = m_TextMeshPro.textInfo.meshInfo.uv2s;
m_TextMeshPro.mesh.colors32 = m_TextMeshPro.textInfo.meshInfo.vertexColors;
//Debug.Log("Vertex Attributes Modified.");
yield return new WaitForSeconds(0.1f);
}
}
IEnumerator AnimateVertexPositionsV()
{
VertexCurve.preWrapMode = WrapMode.Loop;
VertexCurve.postWrapMode = WrapMode.Loop;
Vector3[] newVertexPositions;
//Matrix4x4 matrix;
int loopCount = 0;
while (true)
{
m_TextMeshPro.renderMode = TextRenderFlags.DontRender; // Instructing TextMesh Pro not to upload the mesh as we will be modifying it.
m_TextMeshPro.ForceMeshUpdate(); // Generate the mesh and populate the textInfo with data we can use and manipulate.
TMP_TextInfo textInfo = m_TextMeshPro.textInfo;
int characterCount = textInfo.characterCount;
newVertexPositions = textInfo.meshInfo.vertices;
for (int i = 0; i < characterCount; i++)
{
if (!textInfo.characterInfo[i].isVisible)
continue;
int vertexIndex = textInfo.characterInfo[i].vertexIndex;
float offsetY = VertexCurve.Evaluate((float)i / characterCount + loopCount / 50f); // Random.Range(-0.25f, 0.25f);
newVertexPositions[vertexIndex + 0].y += offsetY;
newVertexPositions[vertexIndex + 1].y += offsetY;
newVertexPositions[vertexIndex + 2].y += offsetY;
newVertexPositions[vertexIndex + 3].y += offsetY;
}
loopCount += 1;
// Upload the mesh with the revised information
m_TextMeshPro.mesh.vertices = newVertexPositions;
m_TextMeshPro.mesh.uv = m_TextMeshPro.textInfo.meshInfo.uv0s;
m_TextMeshPro.mesh.uv2 = m_TextMeshPro.textInfo.meshInfo.uv2s;
m_TextMeshPro.mesh.colors32 = m_TextMeshPro.textInfo.meshInfo.vertexColors;
yield return new WaitForSeconds(0.025f);
}
}
IEnumerator AnimateVertexPositionsVI()
{
Matrix4x4 matrix;
Vector3[] vertices;
int loopCount = 0;
// Create an Array which contains pre-computed Angle Ranges and Speeds for a bunch of characters.
VertexAnim[] vertexAnim = new VertexAnim[1024];
for (int i = 0; i < 1024; i++)
{
vertexAnim[i].angleRange = Random.Range(90f, 90f);
vertexAnim[i].speed = Random.Range(1f, 3f);
}
m_TextMeshPro.renderMode = TextRenderFlags.DontRender;
int direction = 1;
m_TextMeshPro.ForceMeshUpdate();
vertices = m_TextMeshPro.textInfo.meshInfo.vertices;
while (loopCount < 10000)
{
//m_TextMeshPro.ForceMeshUpdate();
//vertices = m_TextMeshPro.textInfo.meshInfo.vertices;
int characterCount = m_TextMeshPro.textInfo.characterCount;
for (int i = 0; i < characterCount; i++)
{
// Setup initial random values
VertexAnim vertAnim = vertexAnim[i];
TMP_CharacterInfo charInfo = m_TextMeshPro.textInfo.characterInfo[i];
// Skip Characters that are not visible
if (!charInfo.isVisible)
continue;
int vertexIndex = charInfo.vertexIndex;
Vector2 charMidTopline = new Vector2((vertices[vertexIndex + 0].x + vertices[vertexIndex + 2].x) / 2, charInfo.topRight.y);
// Vector2 charMidBasline = new Vector2((vertices[vertexIndex + 0].x + vertices[vertexIndex + 2].x) / 2, charInfo.baseLine);
// Need to translate all 4 vertices of each quad to aligned with middle of character / baseline.
Vector3 offset = charMidTopline;
// Vector3 offset = charMidBasline;
float angle = 0;
while (angle < 90)
{
vertices[vertexIndex + 0] += -offset;
vertices[vertexIndex + 1] += -offset;
vertices[vertexIndex + 2] += -offset;
vertices[vertexIndex + 3] += -offset;
matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 15 * direction, 0), Vector3.one);
vertices[vertexIndex + 0] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 0]);
vertices[vertexIndex + 1] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 1]);
vertices[vertexIndex + 2] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 2]);
vertices[vertexIndex + 3] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 3]);
vertices[vertexIndex + 0] += offset;
vertices[vertexIndex + 1] += offset;
vertices[vertexIndex + 2] += offset;
vertices[vertexIndex + 3] += offset;
m_TextMeshPro.mesh.vertices = vertices;
m_TextMeshPro.mesh.uv = m_TextMeshPro.textInfo.meshInfo.uv0s;
m_TextMeshPro.mesh.uv2 = m_TextMeshPro.textInfo.meshInfo.uv2s;
m_TextMeshPro.mesh.colors32 = m_TextMeshPro.textInfo.meshInfo.vertexColors;
angle += 15;
yield return null;
//vertexAnim[i] = vertAnim;
}
}
loopCount += 1;
direction *= -1;
//Debug.Log("Vertex Attributes Modified.");
yield return new WaitForSeconds(0.1f);
}
}
}
很不幸的是,在unity新版本中,这个脚本已经没法直接拿来使用,通过一番查找,找到了新版本的脚本
using UnityEngine;
using System.Collections;
using TMPro;
public class VertexAttributeModifierNew : MonoBehaviour {
public enum AnimationMode { VertexColor, UiVertexColor, Wave, Jitter, Warp, WarpUI, Dangling, Reveal };
public AnimationMode MeshAnimationMode = AnimationMode.Wave;
public AnimationCurve VertexCurve = new AnimationCurve(new Keyframe(0, 0), new Keyframe(0.25f, 2.0f), new Keyframe(0.5f, 0), new Keyframe(0.75f, 2.0f), new Keyframe(1, 0f));
public float AngleMultiplier = 1.0f;
public float SpeedMultiplier = 1.0f;
public float CurveScale = 1.0f;
private TextMeshPro m_TextMeshPro;
private TextMeshProUGUI m_TextMeshProUGUI;
private TextContainer m_TextContainer;
private TMP_TextInfo m_textInfo;
private string textLabel = "Text <#ff8000>silliness with TextMesh<#00aaff>Pro!";
private struct VertexAnim
{
public float angleRange;
public float angle;
public float speed;
}
void Awake()
{
// Get a reference to the TextMeshPro Component if one exists. If not add one.
m_TextMeshPro = GetComponent() ?? gameObject.AddComponent();
m_TextMeshProUGUI = GetComponent(); // ?? gameObject.AddComponent();
m_TextMeshPro.alignment = TextAlignmentOptions.Center;
//m_TextMeshPro.enableWordWrapping = true;
//m_TextMeshPro.colorGradient = new VertexGradient(Color.white, Color.white, Color.blue, Color.cyan);
//m_TextMeshPro.enableVertexGradient = true;
m_TextContainer = GetComponent();
//m_TextContainer.width = 40f;
}
void Start()
{
m_TextMeshPro.ForceMeshUpdate(); // We force the mesh update in order to get the mesh created so we can have valid data to play with :)
switch (MeshAnimationMode)
{
case AnimationMode.VertexColor:
StartCoroutine(AnimateVertexColors());
break;
case AnimationMode.UiVertexColor:
StartCoroutine(AnimateUIVertexColors());
break;
case AnimationMode.Wave:
StartCoroutine(AnimateVertexPositions());
break;
case AnimationMode.Jitter:
StartCoroutine(AnimateVertexPositionsII());
break;
case AnimationMode.Warp:
StartCoroutine(AnimateVertexPositionsIII());
break;
case AnimationMode.WarpUI:
StartCoroutine(AnimateUIVertexPositionsIII());
break;
case AnimationMode.Dangling:
StartCoroutine(AnimateVertexPositionsIV());
break;
case AnimationMode.Reveal:
StartCoroutine(AnimateVertexPositionsVI());
break;
//case AnimationMode.Test:
// StartCoroutine(AnimateVertexPositionsV());
// break;
}
}
IEnumerator AnimateVertexColors()
{
TMP_TextInfo textInfo = m_TextMeshPro.textInfo;
int currentCharacter = 0;
Color32[] newVertexColors = textInfo.meshInfo[0].colors32;
Color32 c0 = m_TextMeshPro.color;
c0.a = 127;
Color32 c1 = c0;
m_TextMeshPro.renderMode = TextRenderFlags.DontRender;
while (true)
{
int characterCount = textInfo.characterCount;
// If No Characters then just yield and wait for some text to be added
if (characterCount == 0)
{
yield return new WaitForSeconds(0.25f);
continue;
}
newVertexColors = textInfo.meshInfo[0].colors32;
currentCharacter = (currentCharacter + 1) % characterCount;
int vertexIndex = textInfo.characterInfo[currentCharacter].vertexIndex;
if (!textInfo.characterInfo[currentCharacter].isVisible)
continue;
if (currentCharacter == 0)
{
c0 = new Color32((byte)Random.Range(0, 255), (byte)Random.Range(0, 255), (byte)Random.Range(0, 255), 127);
}
c1 = new Color32((byte)Random.Range(0, 255), (byte)Random.Range(0, 255), (byte)Random.Range(0, 255), 127);
newVertexColors[vertexIndex + 0] = c1;
newVertexColors[vertexIndex + 1] = c1;
newVertexColors[vertexIndex + 2] = c1;
newVertexColors[vertexIndex + 3] = c1;
m_TextMeshPro.mesh.vertices = textInfo.meshInfo[0].vertices;
m_TextMeshPro.mesh.uv = textInfo.meshInfo[0].uvs0;
m_TextMeshPro.mesh.uv2 = textInfo.meshInfo[0].uvs2;
m_TextMeshPro.mesh.colors32 = newVertexColors;
yield return new WaitForSeconds(0.05f);
}
}
IEnumerator AnimateUIVertexColors()
{
TMP_TextInfo textInfo = m_TextMeshPro.textInfo;
int currentCharacter = 0;
Color32[] uiVertices; // = textInfo.meshInfo.uiVertices;
Color32 c0 = m_TextMeshPro.color;
c0.a = 127; // Since we are modifying the vertex color directly, we need to be mindful that bold information is encoded in the alpha. 0 - 127 is normal weight and 128 - 255 is bold.
Color32 c1 = c0;
m_TextMeshPro.renderMode = TextRenderFlags.DontRender;
while (true)
{
// Get the index of the material used by the current character.
int materialIndex = textInfo.characterInfo[currentCharacter].materialReferenceIndex;
// Get the vertex colors of the mesh used by this text element (character or sprite).
uiVertices = textInfo.meshInfo[materialIndex].colors32;
int characterCount = textInfo.characterCount;
// If No Characters then just yield and wait for some text to be added
if (characterCount == 0)
{
yield return new WaitForSeconds(0.25f);
continue;
}
if (textInfo.characterInfo[currentCharacter].isVisible)
{
int vertexIndex = textInfo.characterInfo[currentCharacter].vertexIndex;
// Pick new bottom color once per cycle
if (currentCharacter == 0)
{
c0 = new Color32((byte)Random.Range(0, 255), (byte)Random.Range(0, 255), (byte)Random.Range(0, 255), 127);
}
c1 = new Color32((byte)Random.Range(0, 255), (byte)Random.Range(0, 255), (byte)Random.Range(0, 255), 127);
uiVertices[vertexIndex + 0] = c0;
uiVertices[vertexIndex + 1] = c1;
uiVertices[vertexIndex + 2] = c1;
uiVertices[vertexIndex + 3] = c0;
// uiRenderer.SetVertices(uiVertices, uiVertices.Length);
m_TextMeshPro.UpdateVertexData(TMP_VertexDataUpdateFlags.Colors32);
}
currentCharacter = (currentCharacter + 1) % characterCount;
yield return new WaitForSeconds(0.05f);
}
}
IEnumerator AnimateVertexPositions()
{
VertexCurve.preWrapMode = WrapMode.Loop;
VertexCurve.postWrapMode = WrapMode.Loop;
Vector3[] newVertexPositions;
//Matrix4x4 matrix;
int loopCount = 0;
while (true)
{
m_TextMeshPro.renderMode = TextRenderFlags.DontRender; // Instructing TextMesh Pro not to upload the mesh as we will be modifying it.
m_TextMeshPro.ForceMeshUpdate(); // Generate the mesh and populate the textInfo with data we can use and manipulate.
TMP_TextInfo textInfo = m_TextMeshPro.textInfo;
int characterCount = textInfo.characterCount;
newVertexPositions = textInfo.meshInfo[0].vertices;
for (int i = 0; i < characterCount; i++)
{
if (!textInfo.characterInfo[i].isVisible)
continue;
int vertexIndex = textInfo.characterInfo[i].vertexIndex;
float offsetY = VertexCurve.Evaluate((float)i / characterCount + loopCount / 50f) * CurveScale; // Random.Range(-0.25f, 0.25f);
newVertexPositions[vertexIndex + 0].y += offsetY;
newVertexPositions[vertexIndex + 1].y += offsetY;
newVertexPositions[vertexIndex + 2].y += offsetY;
newVertexPositions[vertexIndex + 3].y += offsetY;
}
loopCount += 1;
// Upload the mesh with the revised information
m_TextMeshPro.mesh.vertices = newVertexPositions;
m_TextMeshPro.mesh.uv = m_TextMeshPro.textInfo.meshInfo[0].uvs0;
m_TextMeshPro.mesh.uv2 = m_TextMeshPro.textInfo.meshInfo[0].uvs2;
m_TextMeshPro.mesh.colors32 = m_TextMeshPro.textInfo.meshInfo[0].colors32;
yield return new WaitForSeconds(0.025f);
}
}
IEnumerator AnimateVertexPositionsII()
{
Matrix4x4 matrix;
Vector3[] vertices;
int loopCount = 0;
// Create an Array which contains pre-computed Angle Ranges and Speeds for a bunch of characters.
VertexAnim[] vertexAnim = new VertexAnim[1024];
for (int i = 0; i < 1024; i++ )
{
vertexAnim[i].angleRange = Random.Range(10f, 25f);
vertexAnim[i].speed = Random.Range(1f, 3f);
}
m_TextMeshPro.renderMode = TextRenderFlags.DontRender;
while (loopCount < 10000)
{
m_TextMeshPro.ForceMeshUpdate();
vertices = m_TextMeshPro.textInfo.meshInfo[0].vertices;
int characterCount = m_TextMeshPro.textInfo.characterCount;
for (int i = 0; i < characterCount; i++)
{
// Setup initial random values
VertexAnim vertAnim = vertexAnim[i];
TMP_CharacterInfo charInfo = m_TextMeshPro.textInfo.characterInfo[i];
// Skip Characters that are not visible
if (!charInfo.isVisible)
continue;
int vertexIndex = charInfo.vertexIndex;
//Vector2 charMidTopline = new Vector2((vertices[vertexIndex + 0].x + vertices[vertexIndex + 2].x) / 2, charInfo.topRight.y);
Vector2 charMidBasline = new Vector2((vertices[vertexIndex + 0].x + vertices[vertexIndex + 2].x) / 2, charInfo.baseLine);
// Need to translate all 4 vertices of each quad to aligned with middle of character / baseline.
//Vector3 offset = charMidTopline;
Vector3 offset = charMidBasline;
vertices[vertexIndex + 0] += -offset;
vertices[vertexIndex + 1] += -offset;
vertices[vertexIndex + 2] += -offset;
vertices[vertexIndex + 3] += -offset;
vertAnim.angle = Mathf.SmoothStep(-vertAnim.angleRange, vertAnim.angleRange, Mathf.PingPong(loopCount / 25f * vertAnim.speed, 1f));
Vector3 jitterOffset = new Vector3(Random.Range(-.25f, .25f), Random.Range(-.25f, .25f), 0);
//matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 0, vertexAnim[i].angle), Vector3.one);
//matrix = Matrix4x4.TRS(jitterOffset, Quaternion.identity, Vector3.one);
matrix = Matrix4x4.TRS(jitterOffset * CurveScale, Quaternion.Euler(0, 0, Random.Range(-5f, 5f) * AngleMultiplier), Vector3.one);
vertices[vertexIndex + 0] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 0]);
vertices[vertexIndex + 1] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 1]);
vertices[vertexIndex + 2] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 2]);
vertices[vertexIndex + 3] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 3]);
vertices[vertexIndex + 0] += offset;
vertices[vertexIndex + 1] += offset;
vertices[vertexIndex + 2] += offset;
vertices[vertexIndex + 3] += offset;
vertexAnim[i] = vertAnim;
}
loopCount += 1;
m_TextMeshPro.mesh.vertices = vertices;
m_TextMeshPro.mesh.uv = m_TextMeshPro.textInfo.meshInfo[0].uvs0;
m_TextMeshPro.mesh.uv2 = m_TextMeshPro.textInfo.meshInfo[0].uvs2;
//m_TextMeshPro.mesh.colors32 = m_TextMeshPro.textInfo.meshInfo.vertexColors;
//Debug.Log("Vertex Attributes Modified.");
yield return new WaitForSeconds(0.1f * SpeedMultiplier);
}
}
private AnimationCurve CopyAnimationCurve(AnimationCurve curve)
{
AnimationCurve newCurve = new AnimationCurve();
newCurve.keys = curve.keys;
return newCurve;
}
IEnumerator AnimateVertexPositionsIII()
{
VertexCurve.preWrapMode = WrapMode.Clamp;
VertexCurve.postWrapMode = WrapMode.Clamp;
Vector3[] vertexPositions;
Matrix4x4 matrix;
//int loopCount = 0;
m_TextMeshPro.ForceMeshUpdate();
m_TextMeshPro.UpdateVertexData(); // Need to force the TextMeshPro Object to be updated.
float old_CurveScale = CurveScale;
AnimationCurve old_curve = CopyAnimationCurve(VertexCurve);
while (true)
{
//!m_TextMeshPro.hasChanged &&
if (old_CurveScale == CurveScale && old_curve.keys[1].value == VertexCurve.keys[1].value)
{
yield return null;
continue;
}
old_CurveScale = CurveScale;
old_curve = CopyAnimationCurve(VertexCurve);
//Debug.Log("Updating object!");
m_TextMeshPro.renderMode = TextRenderFlags.DontRender; // Instructing TextMesh Pro not to upload the mesh as we will be modifying it.
m_TextMeshPro.ForceMeshUpdate(); // Generate the mesh and populate the textInfo with data we can use and manipulate.
TMP_TextInfo textInfo = m_TextMeshPro.textInfo;
int characterCount = textInfo.characterCount;
Debug.Log(characterCount);
if (characterCount == 0) continue;
vertexPositions = textInfo.meshInfo[0].vertices;
//int lastVertexIndex = textInfo.characterInfo[characterCount - 1].vertexIndex;
float boundsMinX = m_TextMeshPro.bounds.min.x;
float boundsMaxX = m_TextMeshPro.bounds.max.x;
for (int i = 0; i < characterCount; i++)
{
if (!textInfo.characterInfo[i].isVisible)
continue;
int vertexIndex = textInfo.characterInfo[i].vertexIndex;
// Compute the baseline mid point for each character
Vector3 offsetToMidBaseline = new Vector2((vertexPositions[vertexIndex + 0].x + vertexPositions[vertexIndex + 2].x) / 2, textInfo.characterInfo[i].baseLine);
//float offsetY = VertexCurve.Evaluate((float)i / characterCount + loopCount / 50f); // Random.Range(-0.25f, 0.25f);
// Apply offset to adjust our pivot point.
vertexPositions[vertexIndex + 0] += -offsetToMidBaseline;
vertexPositions[vertexIndex + 1] += -offsetToMidBaseline;
vertexPositions[vertexIndex + 2] += -offsetToMidBaseline;
vertexPositions[vertexIndex + 3] += -offsetToMidBaseline;
// Compute the angle of rotation for each character based on the animation curve
float x0 = (offsetToMidBaseline.x - boundsMinX) / (boundsMaxX - boundsMinX); // Character's position relative to the bounds of the mesh.
float x1 = x0 + 0.0001f;
float y0 = VertexCurve.Evaluate(x0) * CurveScale;
float y1 = VertexCurve.Evaluate(x1) * CurveScale;
Vector3 horizontal = new Vector3(1, 0, 0);
//Vector3 normal = new Vector3(-(y1 - y0), (x1 * (boundsMaxX - boundsMinX) + boundsMinX) - offsetToMidBaseline.x, 0);
Vector3 tangent = new Vector3(x1 * (boundsMaxX - boundsMinX) + boundsMinX, y1) - new Vector3(offsetToMidBaseline.x, y0);
float dot = Mathf.Acos(Vector3.Dot(horizontal, tangent.normalized)) * 57.2957795f;
Vector3 cross = Vector3.Cross(horizontal, tangent);
float angle = cross.z > 0 ? dot : 360 - dot;
matrix = Matrix4x4.TRS(new Vector3(0, 0, y0), Quaternion.Euler(0, -angle, 0), Vector3.one);
vertexPositions[vertexIndex + 0] = matrix.MultiplyPoint3x4(vertexPositions[vertexIndex + 0]);
vertexPositions[vertexIndex + 1] = matrix.MultiplyPoint3x4(vertexPositions[vertexIndex + 1]);
vertexPositions[vertexIndex + 2] = matrix.MultiplyPoint3x4(vertexPositions[vertexIndex + 2]);
vertexPositions[vertexIndex + 3] = matrix.MultiplyPoint3x4(vertexPositions[vertexIndex + 3]);
vertexPositions[vertexIndex + 0] += offsetToMidBaseline;
vertexPositions[vertexIndex + 1] += offsetToMidBaseline;
vertexPositions[vertexIndex + 2] += offsetToMidBaseline;
vertexPositions[vertexIndex + 3] += offsetToMidBaseline;
}
// Upload the mesh with the revised information
m_TextMeshPro.mesh.vertices = vertexPositions;
m_TextMeshPro.mesh.uv = m_TextMeshPro.textInfo.meshInfo[0].uvs0;
m_TextMeshPro.mesh.uv2 = m_TextMeshPro.textInfo.meshInfo[0].uvs2;
m_TextMeshPro.mesh.colors32 = m_TextMeshPro.textInfo.meshInfo[0].colors32;
yield return new WaitForSeconds(0.025f);
}
}
IEnumerator AnimateUIVertexPositionsIII()
{
VertexCurve.preWrapMode = WrapMode.Clamp;
VertexCurve.postWrapMode = WrapMode.Clamp;
CanvasRenderer uiRenderer = m_TextMeshProUGUI.canvasRenderer;
Vector3[][] uiVertices = new Vector3[0][];
Matrix4x4 matrix;
//int loopCount = 0;
m_TextMeshPro.ForceMeshUpdate();
m_TextMeshPro.UpdateVertexData();
//m_TextMeshProUGUI.hasChanged = true; // Need to force the TextMeshPro Object to be updated.
float old_CurveScale = CurveScale;
AnimationCurve old_curve = CopyAnimationCurve(VertexCurve);
while (true)
{
//!m_TextMeshProUGUI.hasChanged &&
if (old_CurveScale == CurveScale && old_curve.keys[1].value == VertexCurve.keys[1].value)
{
yield return null;
continue;
}
old_CurveScale = CurveScale;
old_curve = CopyAnimationCurve(VertexCurve);
//Debug.Log("Updating object!");
m_TextMeshPro.renderMode = TextRenderFlags.DontRender; // Instructing TextMesh Pro not to upload the mesh as we will be modifying it.
m_TextMeshPro.ForceMeshUpdate(); // Generate the mesh and populate the textInfo with data we can use and manipulate.
TMP_TextInfo textInfo = m_TextMeshPro.textInfo;
int characterCount = textInfo.characterCount;
if (characterCount == 0) continue;
// Allocate new vertices
if (uiVertices.Length < textInfo.meshInfo.Length)
uiVertices = new Vector3[textInfo.meshInfo.Length][];
for (int i = 0; i < textInfo.meshInfo.Length; i++)
{
int length = textInfo.meshInfo[i].vertices.Length;
uiVertices[i] = new Vector3[length];
}
//int lastVertexIndex = textInfo.characterInfo[characterCount - 1].vertexIndex;
float boundsMinX = m_TextMeshPro.bounds.min.x;
float boundsMaxX = m_TextMeshPro.bounds.max.x;
for (int i = 0; i < characterCount; i++)
{
int first = textInfo.lineInfo[i].firstCharacterIndex;
int last = textInfo.lineInfo[i].lastCharacterIndex;
Vector3 centerOfLine = (textInfo.characterInfo[first].bottomLeft + textInfo.characterInfo[last].topRight) / 2;
Quaternion rotation = Quaternion.Euler(0, 0, Random.Range(-0.25f, 0.25f) * 1.0f);
for (int j = first; j <= last; j++)
{
if (!textInfo.characterInfo[i].isVisible)
continue;
// Get the index of the material used by the current character.
int materialIndex = textInfo.characterInfo[j].materialReferenceIndex;
// Get the index of the first vertex used by this text element.
int vertexIndex = textInfo.characterInfo[j].vertexIndex;
// Get the vertices of the mesh used by this text element (character or sprite).
Vector3[] sourceVertices = textInfo.meshInfo[materialIndex].vertices;
//float offsetY = VertexCurve.Evaluate((float)i / characterCount + loopCount / 50f); // Random.Range(-0.25f, 0.25f);
// Apply offset to adjust our pivot point.
uiVertices[materialIndex][vertexIndex + 0] = sourceVertices[vertexIndex + 0] - centerOfLine;
uiVertices[materialIndex][vertexIndex + 1] = sourceVertices[vertexIndex + 1] - centerOfLine;
uiVertices[materialIndex][vertexIndex + 2] = sourceVertices[vertexIndex + 2] - centerOfLine;
uiVertices[materialIndex][vertexIndex + 3] = sourceVertices[vertexIndex + 3] - centerOfLine;
// Compute the angle of rotation for each character based on the animation curve
float x0 = (centerOfLine.x - boundsMinX) /
(boundsMaxX - boundsMinX); // Character's position relative to the bounds of the mesh.
float x1 = x0 + 0.0001f;
float y0 = VertexCurve.Evaluate(x0) * CurveScale;
float y1 = VertexCurve.Evaluate(x1) * CurveScale;
Vector3 horizontal = new Vector3(1, 0, 0);
//Vector3 normal = new Vector3(-(y1 - y0), (x1 * (boundsMaxX - boundsMinX) + boundsMinX) - offsetToMidBaseline.x, 0);
Vector3 tangent = new Vector3(x1 * (boundsMaxX - boundsMinX) + boundsMinX, y1) -
new Vector3(centerOfLine.x, y0);
float dot = Mathf.Acos(Vector3.Dot(horizontal, tangent.normalized)) * 57.2957795f;
Vector3 cross = Vector3.Cross(horizontal, tangent);
float angle = cross.z > 0 ? dot : 360 - dot;
matrix = Matrix4x4.TRS(new Vector3(0, y0, 0), Quaternion.Euler(0, 0, angle), Vector3.one);
uiVertices[materialIndex][vertexIndex + 0] = matrix.MultiplyPoint3x4(uiVertices[materialIndex][vertexIndex + 0]);
uiVertices[materialIndex][vertexIndex + 1] = matrix.MultiplyPoint3x4(uiVertices[materialIndex][vertexIndex + 1]);
uiVertices[materialIndex][vertexIndex + 2] = matrix.MultiplyPoint3x4(uiVertices[materialIndex][vertexIndex + 2]);
uiVertices[materialIndex][vertexIndex + 3] = matrix.MultiplyPoint3x4(uiVertices[materialIndex][vertexIndex + 3]);
uiVertices[materialIndex][vertexIndex + 0] += centerOfLine;
uiVertices[materialIndex][vertexIndex + 1] += centerOfLine;
uiVertices[materialIndex][vertexIndex + 2] += centerOfLine;
uiVertices[materialIndex][vertexIndex + 3] += centerOfLine;
}
}
// Push changes into meshes
for (int i = 0; i < textInfo.meshInfo.Length; i++)
{
textInfo.meshInfo[i].mesh.vertices = uiVertices[i];
m_TextMeshPro.UpdateGeometry(textInfo.meshInfo[i].mesh, i);
}
yield return new WaitForSeconds(0.1f);
}
}
IEnumerator AnimateVertexPositionsIV()
{
Matrix4x4 matrix;
Vector3[] vertices;
int loopCount = 0;
// Create an Array which contains pre-computed Angle Ranges and Speeds for a bunch of characters.
VertexAnim[] vertexAnim = new VertexAnim[1024];
for (int i = 0; i < 1024; i++)
{
vertexAnim[i].angleRange = Random.Range(10f, 25f);
vertexAnim[i].speed = Random.Range(1f, 3f);
}
m_TextMeshPro.renderMode = TextRenderFlags.DontRender;
while (loopCount < 10000)
{
m_TextMeshPro.ForceMeshUpdate();
vertices = m_TextMeshPro.textInfo.meshInfo[0].vertices;
int characterCount = m_TextMeshPro.textInfo.characterCount;
for (int i = 0; i < characterCount; i++)
{
// Setup initial random values
VertexAnim vertAnim = vertexAnim[i];
TMP_CharacterInfo charInfo = m_TextMeshPro.textInfo.characterInfo[i];
// Skip Characters that are not visible
if (!charInfo.isVisible)
continue;
int vertexIndex = charInfo.vertexIndex;
Vector2 charMidTopline = new Vector2((vertices[vertexIndex + 0].x + vertices[vertexIndex + 2].x) / 2, charInfo.topRight.y);
// Vector2 charMidBasline = new Vector2((vertices[vertexIndex + 0].x + vertices[vertexIndex + 2].x) / 2, charInfo.baseLine);
// Need to translate all 4 vertices of each quad to aligned with middle of character / baseline.
Vector3 offset = charMidTopline;
// Vector3 offset = charMidBasline;
vertices[vertexIndex + 0] += -offset;
vertices[vertexIndex + 1] += -offset;
vertices[vertexIndex + 2] += -offset;
vertices[vertexIndex + 3] += -offset;
vertAnim.angle = Mathf.SmoothStep(-vertAnim.angleRange, vertAnim.angleRange, Mathf.PingPong(loopCount / 25f * vertAnim.speed, 1f));
Vector3 jitterOffset = new Vector3(Random.Range(-.25f, .25f), Random.Range(-.25f, .25f), 0);
matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 0, vertexAnim[i].angle), Vector3.one);
//matrix = Matrix4x4.TRS(jitterOffset, Quaternion.identity, Vector3.one);
//matrix = Matrix4x4.TRS(jitterOffset, Quaternion.Euler(0, 0, Random.Range(-5f, 5f)), Vector3.one);
vertices[vertexIndex + 0] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 0]);
vertices[vertexIndex + 1] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 1]);
vertices[vertexIndex + 2] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 2]);
vertices[vertexIndex + 3] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 3]);
vertices[vertexIndex + 0] += offset;
vertices[vertexIndex + 1] += offset;
vertices[vertexIndex + 2] += offset;
vertices[vertexIndex + 3] += offset;
vertexAnim[i] = vertAnim;
}
loopCount += 1;
m_TextMeshPro.mesh.vertices = vertices;
m_TextMeshPro.mesh.uv = m_TextMeshPro.textInfo.meshInfo[0].uvs0;
m_TextMeshPro.mesh.uv2 = m_TextMeshPro.textInfo.meshInfo[0].uvs2;
m_TextMeshPro.mesh.colors32 = m_TextMeshPro.textInfo.meshInfo[0].colors32;
//Debug.Log("Vertex Attributes Modified.");
yield return new WaitForSeconds(0.1f);
}
}
IEnumerator AnimateVertexPositionsV()
{
VertexCurve.preWrapMode = WrapMode.Loop;
VertexCurve.postWrapMode = WrapMode.Loop;
Vector3[] newVertexPositions;
//Matrix4x4 matrix;
int loopCount = 0;
while (true)
{
m_TextMeshPro.renderMode = TextRenderFlags.DontRender; // Instructing TextMesh Pro not to upload the mesh as we will be modifying it.
m_TextMeshPro.ForceMeshUpdate(); // Generate the mesh and populate the textInfo with data we can use and manipulate.
TMP_TextInfo textInfo = m_TextMeshPro.textInfo;
int characterCount = textInfo.characterCount;
newVertexPositions = textInfo.meshInfo[0].vertices;
for (int i = 0; i < characterCount; i++)
{
if (!textInfo.characterInfo[i].isVisible)
continue;
int vertexIndex = textInfo.characterInfo[i].vertexIndex;
float offsetY = VertexCurve.Evaluate((float)i / characterCount + loopCount / 50f); // Random.Range(-0.25f, 0.25f);
newVertexPositions[vertexIndex + 0].y += offsetY;
newVertexPositions[vertexIndex + 1].y += offsetY;
newVertexPositions[vertexIndex + 2].y += offsetY;
newVertexPositions[vertexIndex + 3].y += offsetY;
}
loopCount += 1;
// Upload the mesh with the revised information
m_TextMeshPro.mesh.vertices = newVertexPositions;
m_TextMeshPro.mesh.uv = m_TextMeshPro.textInfo.meshInfo[0].uvs0;
m_TextMeshPro.mesh.uv2 = m_TextMeshPro.textInfo.meshInfo[0].uvs2;
m_TextMeshPro.mesh.colors32 = m_TextMeshPro.textInfo.meshInfo[0].colors32;
yield return new WaitForSeconds(0.025f);
}
}
IEnumerator AnimateVertexPositionsVI()
{
Matrix4x4 matrix;
Vector3[] vertices;
int loopCount = 0;
// Create an Array which contains pre-computed Angle Ranges and Speeds for a bunch of characters.
VertexAnim[] vertexAnim = new VertexAnim[1024];
for (int i = 0; i < 1024; i++)
{
vertexAnim[i].angleRange = Random.Range(90f, 90f);
vertexAnim[i].speed = Random.Range(1f, 3f);
}
m_TextMeshPro.renderMode = TextRenderFlags.DontRender;
int direction = 1;
m_TextMeshPro.ForceMeshUpdate();
vertices = m_TextMeshPro.textInfo.meshInfo[0].vertices;
while (loopCount < 10000)
{
//m_TextMeshPro.ForceMeshUpdate();
//vertices = m_TextMeshPro.textInfo.meshInfo.vertices;
int characterCount = m_TextMeshPro.textInfo.characterCount;
for (int i = 0; i < characterCount; i++)
{
// Setup initial random values
VertexAnim vertAnim = vertexAnim[i];
TMP_CharacterInfo charInfo = m_TextMeshPro.textInfo.characterInfo[i];
// Skip Characters that are not visible
if (!charInfo.isVisible)
continue;
int vertexIndex = charInfo.vertexIndex;
Vector2 charMidTopline = new Vector2((vertices[vertexIndex + 0].x + vertices[vertexIndex + 2].x) / 2, charInfo.topRight.y);
// Vector2 charMidBasline = new Vector2((vertices[vertexIndex + 0].x + vertices[vertexIndex + 2].x) / 2, charInfo.baseLine);
// Need to translate all 4 vertices of each quad to aligned with middle of character / baseline.
Vector3 offset = charMidTopline;
// Vector3 offset = charMidBasline;
float angle = 0;
while (angle < 90)
{
vertices[vertexIndex + 0] += -offset;
vertices[vertexIndex + 1] += -offset;
vertices[vertexIndex + 2] += -offset;
vertices[vertexIndex + 3] += -offset;
matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 15 * direction, 0), Vector3.one);
vertices[vertexIndex + 0] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 0]);
vertices[vertexIndex + 1] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 1]);
vertices[vertexIndex + 2] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 2]);
vertices[vertexIndex + 3] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 3]);
vertices[vertexIndex + 0] += offset;
vertices[vertexIndex + 1] += offset;
vertices[vertexIndex + 2] += offset;
vertices[vertexIndex + 3] += offset;
m_TextMeshPro.mesh.vertices = vertices;
m_TextMeshPro.mesh.uv = m_TextMeshPro.textInfo.meshInfo[0].uvs0;
m_TextMeshPro.mesh.uv2 = m_TextMeshPro.textInfo.meshInfo[0].uvs2;
m_TextMeshPro.mesh.colors32 = m_TextMeshPro.textInfo.meshInfo[0].colors32;
angle += 15;
yield return null;
//vertexAnim[i] = vertAnim;
}
}
loopCount += 1;
direction *= -1;
//Debug.Log("Vertex Attributes Modified.");
yield return new WaitForSeconds(0.1f);
}
}
}
对几个比较关键的地方进行说明一下,留待后面温故
ForceMeshUpdate() && UpdateVertexData() 如果要制作在运行中能够看得见的动态效果,需要使用这两个函数中的一个,让其强制刷新
TextMeshPro和TextMeshProUGUI 是两个不一样的东西,但两者直接的操作方式很类似
TMP_TextInfo 记录了关于文字的一些信息,包括文字有多少行,每行有多少个文字等
TMP_CharacterInfo 记录了每个文字的一些信息,包括文字使用的材质编号,文字的顶点索引起始位置等信息
TMP_MeshInfo 记录了Mesh的顶点等信息
通过TMP_MeshInfo获取顶点数据,好像需要根据每个mesh使用的材质索引来获取,个人猜测可能是在TextMeshPro体系中支持了富文本,和图文混编的功能,需要对不一样的内容进行分开管理,所以产生了对于材质索引和顶点索引双依赖的顶点数组信息(具体情况等到后续空余时再来梳理一遍)