You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
436 lines
19 KiB
436 lines
19 KiB
// This code is part of the Fungus library (https://github.com/snozbot/fungus) |
|
// It is released for free under the MIT open source license (https://github.com/snozbot/fungus/blob/master/LICENSE) |
|
|
|
using UnityEngine; |
|
|
|
#if UNITY_2018_1_OR_NEWER |
|
|
|
namespace Fungus |
|
{ |
|
/// <summary> |
|
/// Samples and helpers for creating TMProLink animations |
|
/// </summary> |
|
namespace TMProLinkAnimEffects |
|
{ |
|
/// <summary> |
|
/// Used by BaseEffect and child classes to configure how locations and pivots are calculated |
|
/// before being passed to internal functions. |
|
/// </summary> |
|
public enum TMPLinkAnimatorMode |
|
{ |
|
PerCharacter, |
|
PerWord, |
|
PerSection, |
|
PerLine, |
|
} |
|
|
|
/// <summary> |
|
/// Use of this is not required, all that the TMProLinkAnimLookup requires is a matching signature of |
|
/// void delegate(TMProLinkAnimator context, int start, int length). The base class however is used to |
|
/// create all the sample effects as they all operate in a similar underlying fashion, with a custom mode |
|
/// but ultimately on a character by character basis, doing a relative translation and color modifcation. |
|
/// |
|
/// Much of this class and its sample child effects are static functions to more easily allow reuse by |
|
/// custom effects you may wish to make. |
|
/// </summary> |
|
public abstract class BaseEffect |
|
{ |
|
public TMPLinkAnimatorMode mode; |
|
protected TMProLinkAnimator CurrentContext { get; set; } |
|
protected int CurrentStart { get; set; } |
|
protected int CurrentLength { get; set; } |
|
|
|
public virtual void DoEffect(TMProLinkAnimator context, int start, int length) |
|
{ |
|
CurrentContext = context; |
|
CurrentStart = start; |
|
CurrentLength = length; |
|
|
|
MeshVertUpdateLoop(context, start, length, TransFunc, ColorFunc, mode); |
|
} |
|
|
|
public virtual Matrix4x4 TransFunc(int index) |
|
{ |
|
return Matrix4x4.identity; |
|
} |
|
|
|
public virtual Color32 ColorFunc(int index, Color32 col) |
|
{ |
|
return col; |
|
} |
|
|
|
#region static helpers |
|
|
|
/// <summary> |
|
/// Helper for mesh vertex updating within a found link, adapted from TMPRo examples VertexJitter. |
|
/// </summary> |
|
/// <param name="context"></param> |
|
/// <param name="start"></param> |
|
/// <param name="length"></param> |
|
/// <param name="transformFunc"></param> |
|
/// <param name="colorFunc"></param> |
|
/// <param name="mode"></param> |
|
static public void MeshVertUpdateLoop(TMProLinkAnimator context, int start, int length, System.Func<int, Matrix4x4> transformFunc, System.Func<int, Color32, Color32> colorFunc, TMPLinkAnimatorMode mode) |
|
{ |
|
var tmproComponent = context.TMProComponent; |
|
var textInfo = tmproComponent.textInfo; |
|
var end = start + length; |
|
var cachedMeshInfo = context.CachedMeshInfo; |
|
|
|
Matrix4x4 matrix = Matrix4x4.identity; |
|
Vector2 middle = Vector2.zero; |
|
Color32 col = Color.white; |
|
int wordIndex = -1; |
|
int lineIndex = -1; |
|
|
|
for (int i = start; i < end; i++) |
|
{ |
|
//required as TMPro is putting non visible invalid elements in the charinfo array assuming I will follow this rule |
|
// if we don't character 0 ends up getting an effect applied to it even though it shouldn't |
|
if (!textInfo.characterInfo[i].isVisible) continue; |
|
|
|
// Get the index of the material used by the current character. |
|
int materialIndex = textInfo.characterInfo[i].materialReferenceIndex; |
|
|
|
// Get the index of the first vertex used by this text element. |
|
int vertexIndex = textInfo.characterInfo[i].vertexIndex; |
|
|
|
// Get the cached vertices of the mesh used by this text element (character or sprite). |
|
Vector3[] sourceVertices = cachedMeshInfo[materialIndex].vertices; |
|
Color32[] vertexColors = cachedMeshInfo[materialIndex].colors32; |
|
|
|
if (i == start && mode == TMPLinkAnimatorMode.PerSection) |
|
{ |
|
matrix = transformFunc(start); |
|
middle = CalcMidFromChars(context, start, end); |
|
col = colorFunc(start, vertexColors[vertexIndex]); |
|
} |
|
|
|
// Determine the center point of each character at the baseline. |
|
//Vector2 charMidBasline = new Vector2((sourceVertices[vertexIndex + 0].x + sourceVertices[vertexIndex + 2].x) / 2, charInfo.baseLine); |
|
// Determine the center point of each character. |
|
if (mode == TMPLinkAnimatorMode.PerCharacter) |
|
{ |
|
middle = (sourceVertices[vertexIndex + 0] + sourceVertices[vertexIndex + 2]) / 2; |
|
matrix = transformFunc(i); |
|
col = colorFunc(i, vertexColors[vertexIndex]); |
|
} |
|
else if (mode == TMPLinkAnimatorMode.PerWord) |
|
{ |
|
var newWordIndex = CalcWordFromChar(i, textInfo.wordInfo); |
|
if (newWordIndex != -1 && wordIndex != newWordIndex) |
|
{ |
|
wordIndex = newWordIndex; |
|
|
|
middle = CalcMidFromChars(context, Mathf.Max(start, textInfo.wordInfo[wordIndex].firstCharacterIndex), Mathf.Min(end, textInfo.wordInfo[wordIndex].lastCharacterIndex)); |
|
matrix = transformFunc(i); |
|
col = colorFunc(i, vertexColors[vertexIndex]); |
|
} |
|
} |
|
else if (mode == TMPLinkAnimatorMode.PerLine) |
|
{ |
|
var newLineIndex = textInfo.characterInfo[i].lineNumber; |
|
if (newLineIndex != -1 && lineIndex != newLineIndex) |
|
{ |
|
lineIndex = newLineIndex; |
|
|
|
middle = CalcMidFromChars(context, Mathf.Max(start, textInfo.lineInfo[lineIndex].firstCharacterIndex), Mathf.Min(end, textInfo.lineInfo[lineIndex].lastCharacterIndex)); |
|
matrix = transformFunc(i); |
|
col = colorFunc(i, vertexColors[vertexIndex]); |
|
} |
|
} |
|
|
|
// Need to translate all 4 vertices of each quad to aligned with middle of character / baseline. |
|
// This is needed so the matrix TRS is applied at the origin for each character. |
|
Vector3 offset = middle; |
|
|
|
Vector3[] destinationVertices = textInfo.meshInfo[materialIndex].vertices; |
|
Color32[] destinationVertColors = textInfo.meshInfo[materialIndex].colors32; |
|
|
|
destinationVertices[vertexIndex + 0] = sourceVertices[vertexIndex + 0] - offset; |
|
destinationVertices[vertexIndex + 1] = sourceVertices[vertexIndex + 1] - offset; |
|
destinationVertices[vertexIndex + 2] = sourceVertices[vertexIndex + 2] - offset; |
|
destinationVertices[vertexIndex + 3] = sourceVertices[vertexIndex + 3] - offset; |
|
|
|
destinationVertices[vertexIndex + 0] = matrix.MultiplyPoint3x4(destinationVertices[vertexIndex + 0]); |
|
destinationVertices[vertexIndex + 1] = matrix.MultiplyPoint3x4(destinationVertices[vertexIndex + 1]); |
|
destinationVertices[vertexIndex + 2] = matrix.MultiplyPoint3x4(destinationVertices[vertexIndex + 2]); |
|
destinationVertices[vertexIndex + 3] = matrix.MultiplyPoint3x4(destinationVertices[vertexIndex + 3]); |
|
|
|
destinationVertices[vertexIndex + 0] += offset; |
|
destinationVertices[vertexIndex + 1] += offset; |
|
destinationVertices[vertexIndex + 2] += offset; |
|
destinationVertices[vertexIndex + 3] += offset; |
|
|
|
destinationVertColors[vertexIndex + 0] = col; |
|
destinationVertColors[vertexIndex + 1] = col; |
|
destinationVertColors[vertexIndex + 2] = col; |
|
destinationVertColors[vertexIndex + 3] = col; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Same as calcing a character mid but averaging over all characters in the given character range |
|
/// </summary> |
|
/// <param name="context"></param> |
|
/// <param name="start"></param> |
|
/// <param name="end"></param> |
|
/// <returns></returns> |
|
static public Vector2 CalcMidFromChars(TMProLinkAnimator context, int start, int end) |
|
{ |
|
Vector3 middle = Vector3.zero; |
|
var chInfo = context.TMProComponent.textInfo.characterInfo; |
|
|
|
for (int i = start; i < end; i++) |
|
{ |
|
int materialIndex = chInfo[i].materialReferenceIndex; |
|
int vertexIndex = chInfo[i].vertexIndex; |
|
Vector3[] sourceVertices = context.CachedMeshInfo[materialIndex].vertices; |
|
middle += (sourceVertices[vertexIndex + 0] + sourceVertices[vertexIndex + 2]) / 2; |
|
} |
|
|
|
return middle / (end - start); |
|
} |
|
|
|
/// <summary> |
|
/// Determine which TMPro World a given character index is within |
|
/// </summary> |
|
/// <param name="charIndex"></param> |
|
/// <param name="wordInfo"></param> |
|
/// <returns></returns> |
|
static public int CalcWordFromChar(int charIndex, TMPro.TMP_WordInfo[] wordInfo) |
|
{ |
|
for (int i = 0; i < wordInfo.Length; i++) |
|
{ |
|
//enforcing start letter results in punctuation attaching to the word to its left rather than its right. |
|
// which is more desirable for english at least |
|
if (charIndex >= wordInfo[i].firstCharacterIndex && wordInfo[i].lastCharacterIndex >= charIndex) |
|
{ |
|
return i; |
|
} |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
/// <summary> |
|
/// Determine which TMPro Line a given character index is within |
|
/// </summary> |
|
/// <param name="charIndex"></param> |
|
/// <param name="wordInfo"></param> |
|
/// <returns></returns> |
|
static public int CalcLineFromChar(int charIndex, TMPro.TMP_WordInfo[] wordInfo) |
|
{ |
|
for (int i = 0; i < wordInfo.Length; i++) |
|
{ |
|
//enforcing start letter results in punctuation attaching to the word to its left rather than its right. |
|
// which is more desirable for english at least |
|
if (charIndex >= wordInfo[i].firstCharacterIndex && wordInfo[i].lastCharacterIndex >= charIndex) |
|
{ |
|
return i; |
|
} |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
#endregion static helpers |
|
} |
|
|
|
/// <summary> |
|
/// Shake the element, by moving centre slightly and randomly rolling each update. |
|
/// </summary> |
|
public class ShakeEffect : BaseEffect |
|
{ |
|
public float rotScale; |
|
public Vector2 offsetScale = Vector2.one; |
|
|
|
public override Matrix4x4 TransFunc(int index) |
|
{ |
|
return ShakeTransformFunc(index, offsetScale, rotScale); |
|
} |
|
|
|
static public Matrix4x4 ShakeTransformFunc(int index, Vector2 positionOffsetScale, float rotDegScale) |
|
{ |
|
return Matrix4x4.TRS(new Vector3(Random.Range(-.25f, .25f) * positionOffsetScale.x, Random.Range(-.25f, .25f), 0) * positionOffsetScale.y, |
|
Quaternion.Euler(0, 0, Random.Range(-1f, 1f) * rotDegScale), |
|
Vector3.one); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Wiggle the position by over time using perlin noise to offset its centre. |
|
/// </summary> |
|
public class WiggleEffect : BaseEffect |
|
{ |
|
public float speed = 1; |
|
public Vector2 offsetScale = Vector2.one; |
|
|
|
public override Matrix4x4 TransFunc(int index) |
|
{ |
|
return WiggleTransformFunc(index, speed, offsetScale); |
|
} |
|
|
|
static public Matrix4x4 WiggleTransformFunc(int index, float speed, Vector2 wiggleScale) |
|
{ |
|
const int SAFE_PRIME_A = 11; |
|
const int SAFE_PRIME_B = 59; |
|
//add a pingpong |
|
var jitterOffset = new Vector3(Mathf.PerlinNoise(Time.time * speed + index * SAFE_PRIME_A, index * SAFE_PRIME_B), |
|
Mathf.PerlinNoise(Time.time * speed + index * SAFE_PRIME_B, index * SAFE_PRIME_A), |
|
0); |
|
jitterOffset *= 2; |
|
jitterOffset -= new Vector3(1, 1, 0); |
|
|
|
return Matrix4x4.TRS(jitterOffset * wiggleScale, |
|
Quaternion.identity, |
|
Vector3.one); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Use a sine wave by time to offset the height of the element. |
|
/// </summary> |
|
public class WaveEffect : BaseEffect |
|
{ |
|
public float speed, indexStep, scale; |
|
|
|
public override Matrix4x4 TransFunc(int index) |
|
{ |
|
return WaveTransformFunc(index, speed, indexStep, scale); |
|
} |
|
|
|
static public Matrix4x4 WaveTransformFunc(int index, float waveSpeed, float waveIndexStep, float waveScale) |
|
{ |
|
return Matrix4x4.TRS(new Vector3(0, Mathf.Sin(Time.time * waveSpeed + index * waveIndexStep) * waveScale, 0), |
|
Quaternion.identity, |
|
Vector3.one); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Use a sinewave to swing or pivot the element around its centre back n forth. |
|
/// </summary> |
|
public class PivotEffect : BaseEffect |
|
{ |
|
public float speed, degScale; |
|
|
|
public override Matrix4x4 TransFunc(int index) |
|
{ |
|
return PivotTransformFunc(index, speed, degScale); |
|
} |
|
|
|
static public Matrix4x4 PivotTransformFunc(int index, float pivotSpeed, float pivotDegScale) |
|
{ |
|
return Matrix4x4.TRS(Vector3.zero, |
|
Quaternion.Euler(0, 0, Mathf.Sin(Time.time * pivotSpeed + index) * pivotDegScale), |
|
Vector3.one); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Use a sine wave to animate the H,S,V elements individually, modifying them from their starting color. |
|
/// Use a sine wave to scale the element |
|
/// </summary> |
|
public class PulseEffect : BaseEffect |
|
{ |
|
public float speed = 1, HSVIntensityScale = 1, indexStep = 1, hueScale = 1, saturationScale = 1, valueScale = 1; |
|
public Vector3 scale = Vector2.zero; |
|
|
|
public override Color32 ColorFunc(int index, Color32 col) |
|
{ |
|
return HSVPulse(index, indexStep, speed, HSVIntensityScale, col, hueScale, saturationScale, valueScale); |
|
} |
|
|
|
public override Matrix4x4 TransFunc(int index) |
|
{ |
|
return ScalePulse(index, indexStep, speed, scale); |
|
} |
|
|
|
static public Color32 HSVPulse(int index, float indexStep, float speed, float colScale, Color32 startingColor, float hueScale, float saturationScale, float valueScale) |
|
{ |
|
float t = Mathf.Sin(Time.time * speed + index * indexStep) * colScale; |
|
Color.RGBToHSV(startingColor, out float h, out float s, out float v); |
|
|
|
var col = Color.HSVToRGB(Mathf.Repeat(h + t * hueScale, 1), |
|
Mathf.Clamp01(s + t * saturationScale), |
|
Mathf.Clamp01(v + t * valueScale)); |
|
return (Color32)col; |
|
} |
|
|
|
static public Matrix4x4 ScalePulse(int index, float indexStep, float speed, Vector3 scale) |
|
{ |
|
float t = Mathf.Sin(Time.time * speed + index * indexStep); |
|
return Matrix4x4.TRS(Vector3.zero, |
|
Quaternion.identity, |
|
Vector3.one + scale * t); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Bounce up and down on an endless parabolic curve. |
|
/// </summary> |
|
public class BounceEffect : BaseEffect |
|
{ |
|
public float indexStep = 1, speed = 1, scale = 1, fixedOffsetScale = 0.5f; |
|
|
|
public override Matrix4x4 TransFunc(int index) |
|
{ |
|
return Bounce(index, indexStep, speed, scale, fixedOffsetScale); |
|
} |
|
|
|
static public Matrix4x4 Bounce(int index, float indexStep, float speed, float scale, float fixedOffsetScale) |
|
{ |
|
float t = (Time.time * speed + index * indexStep) % 2.0f; |
|
|
|
t = -t * t + 2 * t; |
|
|
|
return Matrix4x4.TRS(Vector3.up * t * scale + Vector3.down * fixedOffsetScale * scale, |
|
Quaternion.identity, |
|
Vector3.one); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Create a staircase effect of the the elements. |
|
/// </summary> |
|
public class AscendEffect : BaseEffect |
|
{ |
|
public float totalStep; |
|
|
|
public override Matrix4x4 TransFunc(int index) |
|
{ |
|
return StepTransformFunc(index, index - CurrentStart, totalStep / CurrentLength); |
|
} |
|
|
|
static public Matrix4x4 StepTransformFunc(int index, int stepNum, float stepHeight) |
|
{ |
|
return Matrix4x4.TRS(Vector3.up * stepNum * stepHeight, |
|
Quaternion.identity, |
|
Vector3.one); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Cycle the colors of the elements by forcing color to a roling Hue and fixed SV color value. |
|
/// </summary> |
|
public class RainbowEffect : BaseEffect |
|
{ |
|
public float speed, indexStep, s, v; |
|
|
|
public override Color32 ColorFunc(int index, Color32 col) |
|
{ |
|
return CycleColor(index, speed, indexStep, s, v); |
|
} |
|
|
|
static public Color32 CycleColor(int index, float speed, float indexStep, float s, float v) |
|
{ |
|
float h = Time.time * speed + index * indexStep; |
|
var col = Color.HSVToRGB(h % 1.0f, s, v); |
|
return (Color32)col; |
|
} |
|
} |
|
} |
|
} |
|
|
|
#endif |