見出し画像

【Unity】TextMeshProをいっぱい動かす

 こんにちは、オオブチです。今日は文字を動かします。キャラクタだけでなく演出として舞台が変形したり文字が現れたり消えたりする映像って派手で目立つじゃないですか。最近はARライブの配信で物理ステージ上にARで演出が出たり、3D作品の公開ができるプラットフォームにはVRゴーグルで見れる3DのMVなんかが置いてあったりして、文字とか図形がドバドバ出てきて迫力あります。今回トピックにするテキストアニメーションも例にもれず使い方次第ではとてもパワフルな演出だと思います。
 Unityで文字を扱う方法といえばTextMesh、UIのTextとか幾つか方法があります。しかしこれら2つはは既にLegacy扱いで将来的に消える機能かもしれませんし、今回扱うTextMeshProの方が機能も充実しています。今回はTextMeshProを元にして文字を動かすように色々とこねてみようと思います。まずは私が試してみた作例を動画で貼っておきます。

今回は3パターンぐらいの方法を紹介するので使えそうな要素があったら皆さんも試してみてください。
・スクリプトで動かすタイプ(動画右上)
・シェーダで動かすタイプ(動画左上)
・手付の汎用モーションも組み合わせる方法(動画真ん中下)


1.スクリプトで動かすタイプ

 TextMeshProはフォントのアトラスを参照し、一文字を一枚の板ポリに描画する方法で文字を出しているので、メッシュのもつ頂点情報を書き換えることができます。
 C#スクリプトから試しにTMP_MeshInfoを触って揺らしてみます。

using UnityEngine;
using TMPro;

[ExecuteInEditMode, RequireComponent(typeof(TMP_Text))]
public class TMP_wave : MonoBehaviour
{
    [SerializeField] private float amp;
    [SerializeField] private float speed;
    [SerializeField] private int length;

    private TMP_Text tmpText;
    private TMP_TextInfo tmpInfo;

    private void Start()
    {
        tmpText = this.GetComponent<TMP_Text>();
    }

    private void Update()
    {
        UpdateAnimation();
    }

    private void UpdateAnimation()
    {
        tmpText.ForceMeshUpdate(true);
        tmpInfo = tmpText.textInfo;

        var count = Mathf.Min(tmpInfo.characterCount, tmpInfo.characterInfo.Length);
        for (int i = 0; i < count; i++)
        {
            var charInfo = tmpInfo.characterInfo[i];
            if (!charInfo.isVisible)
                continue;

            int matIndex = charInfo.materialReferenceIndex;
            int vertIndex = charInfo.vertexIndex;

            Vector3[] verts = tmpInfo.meshInfo[matIndex].vertices;

            float ofs = 0.5f * i;
            float sinWave = Mathf.Sin((ofs + Time.realtimeSinceStartup * Mathf.PI * speed) / length) * amp;
            verts[vertIndex + 0].y += sinWave;
            verts[vertIndex + 1].y += sinWave;
            verts[vertIndex + 2].y += sinWave;
            verts[vertIndex + 3].y += sinWave;
        }

        for (int i = 0; i < tmpInfo.materialCount; i++)
        {
            if (tmpInfo.meshInfo[i].mesh == null) { continue; }

            tmpInfo.meshInfo[i].mesh.vertices = tmpInfo.meshInfo[i].vertices;
            tmpText.UpdateGeometry(tmpInfo.meshInfo[i].mesh, i);
        }
    }
}

MeshAPIみたいな感じで頂点情報を触って更新し続けるスクリプトです。基本的には頂点位置以外は触ることは無いと思いますが、わざとUVを触って奇妙な演出を作ることもできます。
汎用性はイマイチですが規則的に動かすなら使えるのかな、という感想。



2.Shaderで動かすタイプ

 TextMeshProが板ポリという事は、やはり描画にはマテリアルを使うのでココを改造することもできます。
 改造するとはいっても書き換えるべきところはいくつかしかないので、改変部分のみ紹介します。改変もとにしたのはTextMeshProで標準の設定になっているTextMeshPro/Mobile/Distance Fieldです。includeするcgincファイルのパスを整えて、Fragmentシェーダの中身を触ります。

Shader "Custom/TextMeshPro/Mobile/Distance Field" {

Properties {
    //追加
	[Header(Custom Properties)]
	_Param("param", float) = 1
	[Header(Defautl TMP Properties)]
	[HDR]_FaceColor     ("Face Color", Color) = (1,1,1,1)
	_FaceDilate			("Face Dilate", Range(-1,1)) = 0

	~~~~~(省略)~~~~~
}

SubShader {
	~~~~~(省略)~~~~~
	Pass {
		CGPROGRAM
		#pragma vertex VertShader
		#pragma fragment PixShader//この名前がFragmentShader
		#pragma shader_feature __ OUTLINE_ON
		#pragma shader_feature __ UNDERLAY_ON UNDERLAY_INNER

		#pragma multi_compile __ UNITY_UI_CLIP_RECT
		#pragma multi_compile __ UNITY_UI_ALPHACLIP

		#include "UnityCG.cginc"
		#include "UnityUI.cginc"
		#include "../~~~~~~~~~~/TMPro_Properties.cginc"//(ここのパスを整える)

		~~~~~(省略)~~~~~

		//追加
		float _Param;
		half4 _MainTex_TexelSize;

		// ここを改造する
		// PIXEL SHADER
		fixed4 PixShader(pixel_t input) : SV_Target
		{
			UNITY_SETUP_INSTANCE_ID(input);

			//改変ここから
			//作例:UVを階調化してみる場合
			int2 LimMin = int2(2, 10);
			int2 LimMax = 1 / _MainTex_TexelSize.xy;
			half2 lp = lerp(LimMin, LimMax, pow(saturate(sin(_Time.y * _Param) * 0.6 + 0.6), 2));
			half2 uv = floor(input.texcoord0.xy * lp) / lp;
			half d = tex2D(_MainTex, uv).a * input.param.x;
			//
			//ここまで


			//half d = tex2D(_MainTex, input.texcoord0.xy).a * input.param.x;
			half4 c = input.faceColor * saturate(d - input.param.w);

			~~~~~(省略)~~~~~

			#if UNITY_UI_ALPHACLIP
			clip(c.a - 0.001);
			#endif

			return c;
		}
		ENDCG
	}
}
//Propertyを追加したのでCustomEditorをオフって見えるように。
//CustomEditor "TMPro.EditorUtilities.TMP_SDFShaderGUI"
}

 今回動画ではUVをいじくって2パターン作りました。最初のやり方ではC#から頂点を動かしましたが頂点シェーダでも同じことはできそう。基本的にはUVとかテクスチャを元に変化を付けるならこの方法かな、と思います。


3.手付のAnimationClipで動かすタイプ

キーフレームを手で打ってタイムラインで流す古来からの方法です。時間かかりそう~~~~~~~という直感はありそうですが、便利な汎用モーションを作り溜めて置けば後はTimelineに並べて組み合わせたり調整したりすればOKなので使いまわしは良いかもしれません。今回手付したAnimationClipも個別に見ればキーの個数は一桁ぐらいしかないです。
今回は手付でAnimationClipを作ってパターンを出して、文字ごとに位置とか時間でオフセットを掛けたり、オーバーライドさせてみたりしました。
もちろんこれ単体だけでなく前に紹介した2つの方法や、紹介していない沢山の他の要素との併用もできます。

文字数が増えるとトラック数もどんどん増えるね(それはそう)

 ちなみに冒頭の動画の前半は基本のコンポーネントにキーを打つのみ、後半の二重になってる文字は標準のコンポーネント以外にもあれこれカスタムしてそれにキーを打ったりしてます。


まとめ

 多分一般的にはAEとかで時間をかけて映像にテキストアニメーションを作りこむのでしょうが、今回はそれに抗ってUnityで出来ることを試してみました。事前に作りこんだタイポ動画を図形に張っ付ける事もできはするけど今回はパス。
 どのやり方も一長一短ありますが、知っておけばリアルタイムで効果的に文字を出せるかもしれません。
 Unityでやるメリットはリアルタイムで絵が出る事なので、キャラクタの骨の位置をターゲットに文字を移動したりとか、なんらかジェネラティブな要素と組み合わせると強みになったりするんでしょうか、うーーーーん、、、
 Unityはそれ専用のツールではないので当然Aeの作り込みに軍配が上がる演出もありますが、演出としてカメラに合わせて文字にも奥行きやパースがついてほしい時には割と手軽にできていいかもしれません。
 今回私は標準のフォントしか使っていないので、皆さんが実用するときはお好みのフォントアセットを作って活用すれば更に雰囲気出ると思います。さようなら。


余談:TextMeshProの字間調整

 TextMeshProは機能が充実していると書きましたが、Legacyのテキスト機能と大きく差があるなと個人的に思っているのは字間の調整ができる事です。Legacyのは行間以外の設定項目がありませんでしたがTextMeshProでは細かく調整できます。
 具体的にはトラッキングとか行間、単語間の調整はSpacing Optionから、カーニングはリッチテキストのタグで調整できます。単位はどちらもemです。
 映像を作る人というより多分UIデザイナーとかが普段考慮する内容なのかな、と思ったので余談にしました。他にも使える機能やタグはあると思うので触ってみるといいかもしれません。

この記事が気に入ったらサポートをしてみませんか?