自分メモ用
UnityでTextMeshProのテキストをバウンドさせるためのスクリプトのサンプル
出来上がったもの
サンプルコード
using System;
using TMPro;
using UnityEngine;
[RequireComponent(typeof(TextMeshProUGUI)), DisallowMultipleComponent]
public class BoundText : MonoBehaviour
{
private TextMeshProUGUI textMeshPro = null;
private float elapseTime = 0;
[SerializeField]
private float boundTime = 0.2f;
[SerializeField]
private float boundHeight = 20.0f;
[SerializeField]
private float afterTime = 0.5f;
private Func<float, float> CalcFunc = null;
// Awake is called when an instance of the script is loaded
private void Awake()
{
this.textMeshPro = this.GetComponent<TextMeshProUGUI>();
}
// Start is called before the first frame update
private void Start()
{
elapseTime = 0.0f;
CalcFunc = GetBoundFunc(boundHeight, boundTime);
}
// Update is called once per frame
private void Update()
{
if (Time.deltaTime < 1)
{
Animation(elapseTime, boundTime);
float deltaTime = Time.deltaTime;
elapseTime += deltaTime;
}
if (elapseTime > (boundTime + afterTime))
{
Destroy(this.gameObject);
}
}
private void Animation(float time, float maxTime)
{
textMeshPro.ForceMeshUpdate(true);
TMP_TextInfo textInfo = textMeshPro.textInfo;
float[] preHeights = new float[4];
for (int i = 0; i < textInfo.characterInfo.Length; i++)
{
TMP_CharacterInfo charInfo = textInfo.characterInfo[i];
if (!charInfo.isVisible) continue;
int mIndex = charInfo.materialReferenceIndex;
int vIndex = charInfo.vertexIndex;
Vector3[] vectors = textInfo.meshInfo[mIndex].vertices;
float height = CalcFunc(time - i * boundTime * 0.5f);
vectors[vIndex + 0].y += height;
vectors[vIndex + 1].y += height;
vectors[vIndex + 2].y += height;
vectors[vIndex + 3].y += height;
}
for (int i = 0; i < textInfo.materialCount; i++)
{
if (textInfo.meshInfo[i].mesh == null) continue;
textInfo.meshInfo[i].mesh.vertices = textInfo.meshInfo[i].vertices;
textMeshPro.UpdateGeometry(textInfo.meshInfo[i].mesh, i);
}
}
private Func<float, float> GetBoundFunc(float maxHeight, float maxTime)
{
float v0 = 4 * maxHeight / maxTime;
float g = 2 * v0 / maxTime;
return GetBoundFuncFromInitSpeed(g, v0);
}
private Func<float, float> GetBoundFuncFromInitSpeed(float g, float v0)
{
return (float t) =>
{
float h = v0 * t - (g * Mathf.Pow(t, 2.0f) * 0.5f);
return h < 0 ? 0 : h;
};
}
}
コード解説
概要
このスクリプトをアタッチしたTextMeshProは、オブジェクト生成後、1文字ずつ文字がバウンドしていき、全部バウンドを終えると自信を削除(Destroy)する。
詳細
クラス属性
[RequireComponent(typeof(TextMeshProUGUI)), DisallowMultipleComponent]
public class BoundText : MonoBehaviour
-
RequireComponent(typeof(TextMeshProUGUI))
TextMeshProUGUIコンポーネントを持つGameObjectにのみアタッチできる。 -
DisallowMultipleComponent
多重にアタッチはできない
パラメタ
[SerializeField]
private float boundTime = 0.2f;
[SerializeField]
private float boundHeight = 20.0f;
[SerializeField]
private float afterTime = 0.5f;
- boundTime
1文字が跳ねて落ちるまでの時間 - boundHeight
1文字が跳ねる高さ - afterTime
全部跳ね終わった後しばらく表示している時間
Awake
private void Awake()
{
this.textMeshPro = this.GetComponent<TextMeshProUGUI>();
}
スクリプト内で参照するTextMeshProコンポーネントの事前取得
Start
private void Start()
{
elapseTime = 0.0f;
CalcFunc = GetBoundFunc(boundHeight, boundTime);
}
スクリプト内で使用する変数の初期化
- elapseTime
スクリプト開始(オブジェクト生成)からの起動時間をカウントする変数
経過時間に伴うバウンドの高さを計算することに使用 - CalcFunc
バウンドする高さを計算するための関数を生成
時間以外のパラメタは事前生成したほうがいいと思ったのでここで生成している
private Func<float, float> GetBoundFunc(float maxHeight, float maxTime)
{
float v0 = 4 * maxHeight / maxTime;
float g = 2 * v0 / maxTime;
return GetBoundFuncFromInitSpeed(g, v0);
}
private Func<float, float> GetBoundFuncFromInitSpeed(float g, float v0)
{
return (float t) =>
{
float h = v0 * t - (g * Mathf.Pow(t, 2.0f) * 0.5f);
return h < 0 ? 0 : h;
};
}
- maxHeight
バウンドする高さ - maxTime
バウンドしている時間
基本的な計算式は、重力加速度($ y = v_0t - \frac{1}{2}gt^2 $)と同じ
maxHeightとmaxTimeから初速$ v_0 $と重力加速度$g$を算出している。
計算式は下記ページを参考にしている
https://ja.wikipedia.org/wiki/%E3%83%9C%E3%83%BC%E3%83%AB%E3%81%AE%E8%B7%B3%E3%81%AD%E8%BF%94%E3%82%8A%E9%81%8B%E5%8B%95
Update
private void Update()
{
if (Time.deltaTime < 1)
{
Animation(elapseTime, boundTime);
float deltaTime = Time.deltaTime;
elapseTime += deltaTime;
}
if (elapseTime > (boundTime + afterTime))
{
Destroy(this.gameObject);
}
}
if (Time.detailTIme < 1)
は、初回Updateの時に大きな数になることがあったのでセーフティで挿入
Animation(elapseTime, boundTime);
で、文字の高さを変更
elapseTime += deltaTime
で、経過時間を加算
private void Animation(float time, float maxTime)
{
textMeshPro.ForceMeshUpdate(true);
TMP_TextInfo textInfo = textMeshPro.textInfo;
for (int i = 0; i < textInfo.characterInfo.Length; i++)
{
TMP_CharacterInfo charInfo = textInfo.characterInfo[i];
if (!charInfo.isVisible) continue;
int mIndex = charInfo.materialReferenceIndex;
int vIndex = charInfo.vertexIndex;
Vector3[] vectors = textInfo.meshInfo[mIndex].vertices;
float height = CalcFunc(time - i * boundTime * 0.5f);
vectors[vIndex + 0].y += height;
vectors[vIndex + 1].y += height;
vectors[vIndex + 2].y += height;
vectors[vIndex + 3].y += height;
}
for (int i = 0; i < textInfo.materialCount; i++)
{
if (textInfo.meshInfo[i].mesh == null) continue;
textInfo.meshInfo[i].mesh.vertices = textInfo.meshInfo[i].vertices;
textMeshPro.UpdateGeometry(textInfo.meshInfo[i].mesh, i);
}
}
TextMeshProの文字位置変更については下記ページを参考
https://coposuke.hateblo.jp/entry/2020/06/07/020235
メインは下記
float height = CalcFunc(time - i * boundTime * 0.5f);
vectors[vIndex + 0].y += height;
vectors[vIndex + 1].y += height;
vectors[vIndex + 2].y += height;
vectors[vIndex + 3].y += height;
ここで高さを計算し各文字にいれている
- i * boundTime * 0.5f
部分で、文字ごとに次々バウンドするようにしている。
(i
が文字位置なので、文字位置ごとにバウンド時間の半分の時間ズラして表示する)