TextMeshProの表示時の情報textInfoの活用

# 概要
uGUIでテキストを表示するUnityアセットのTextMeshPro
FontAsset&Materialを用意してコンポネントのInspectorいじれば必要十分な表示はできます。
![スクリーンショット 2023-12-01 10.59.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208498/d5ddd11d-065c-f86d-3800-85e477b19dcb.png)

リッチテキストの機能には活用すれば色の変化サイズの変化を一部品で対応できたり、
![スクリーンショット 2023-12-01 11.02.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208498/4e61243b-e24e-6d69-af7d-48f31119fecf.png)

スクリプトからリアクティブに変えるテキストの制御も文字列への事前な複雑な調整も要らなくなったりします。
改行禁止の制御とかけっこう便利です。
参考リンク：(公式ではドキュメントにはなぜか全部書かれてないのでまとめてる記事)
https://www.midnightunity.net/textmeshpro-richtext-tags/

問題はこのへんではリッチテキスト機能では対応しきれない複雑な表現をしたいときです。

その時TextMeshPro中に入ってる変数`textInfo`に参考にできる文字の配置情報など詳しい情報が入っていてその辺をスクリプトから活用すれば自由自在なカスタム表現ができるので、その辺について話していきます。

# textInfoに入っている内容
使う可能性が高い情報（だと自分が勝手に思ってる）ものだけ厳選して羅列します。

### 情報変数
```
int characterCount;                 //文字数
int lineCount;                      //行数
TMP_CharacterInfo[] characterInfo;  //文字情報リスト
TMP_LineInfo[] lineInfo;            //行情報リスト
TMP_MeshInfo[] meshInfo;            //メッシュ情報リスト
```

## textInfo.characterInfoに入っている内容
リッチテキスト用タグ(例：\<size=50>)などの表示されない文字列は含まれず純粋に表示される文字のみのリスト情報が入る
つまり「\<size=50>Big\</size>Text」の文字列の場合
textInfo.characterInfo[0]にはBが入り、
textInfo.characterInfo[4]にはTが入ってます。

### 情報変数

```
char character              //文字情報
int materialReferenceIndex  //この文字のメッシュを指すmeshInfoのindex
int vertexIndex             //この文字のmeshInfo内での最初の頂点Index
int index                   //何文字目か
int pointSize               //その文字のフォントサイズ
int lineNumber              //その文字が何行目か
Vector2 topLeft             //左上の位置
Vector2 topRight            //右上の位置
Vector2 bottomLeft          //左下の位置
Vector2 bottomRight         //右下の位置
```
`topLeft`などの位置情報はRectTransformのpivotの位置を基準としたLocalPositionの位置になります。（親のScaleの影響を受けない）


## textInfo.meshInfoに入っている内容
文字のメッシュごと、頂点ごとの位置や色などの情報が入っている。

```
public Vector3[] vertices;      //頂点の位置
Color32[] colors32;             //頂点の色
Material material;              //対象のマテリアル
```

textInfo.characterInfoから見るとmaterialReferenceIndexとvertexIndexを使用してmeshInfoから対応する文字情報の頂点にアクセスする

例えばi番目の文字にアクセスするには以下の通りである
```
//該当文字のメッシュ情報
var meshInfo = textInfo.meshInfo[textInfo.characterInfo[i].materialReferenceIndex];
//該当文字の左下の頂点の色情報
var startColor = meshInfo.colors32[textInfo.characterInfo[i].vertexIndex];
//該当文字の右下の頂点の色情報
var endColor = meshInfo.colors32[textInfo.characterInfo[i].vertexIndex + 3];
```
基本1文字4頂点のはずなのでvertexIndexは1つ目を指し、+3までが対象文字の頂点となる。
左下>左上>右上>右下の順

# 情報が計算されるタイミング
ここまで入ってる情報をざっと伝えましたが、アクセスするタイミングに注意が必要です。
テキストを代入したタイミングやAwakeなどのタイミングで`textInfo`にアクセスしても実は中身は空です。
情報が代入されるタイミングは描写直前です。
`GetTextInfo`の関数を呼べば強制的に計算はさせることができますが、uGUIのレイアウト調整(VerticalLayoutGroupなど)で正しい配置になったあとかどうかは保証できないのでuGUI部品の初期化時に呼んでも正しい情報にならないことは多いです。

よって`OnPreRenderText`のコールバックで呼ばれるタイミングならテキスト表示直前なので表示時の配置情報を得るタイミングとしてはベストに近い気はしてます。
ただし注意するべきはこのタイミングではレイアウトが完成したあとなのでuGUIの配置を変えるような処理をしようとすると以下のように起こられる
`
Trying to remove Test from rebuild list while we are already inside a rebuild loop. This is not supported.
`
よってuGUIの配置を変えるような処理はOnPreRenderTextから1フレームなどタイミングをずらす必要があり、工夫が必要です。

# 結局何ができるか


## textInfo.meshInfoをいじって文字を動かす

`OnPreRenderText`でtextInfo.meshInfoをいじれば表示される文字の位置や色、透明度などを調整することができる
注意すべきはtextInfoが再生成される(表示が変わったタイミング)といじった情報はリセットされるので
OnPreRenderTextでいじるのがフローとしては正しいタイミングに思える

アニメで動かす場合OnPreRenderTextでなくUpdateなどでtextInfo.meshInfo.meshを編集してUpdateGeometryを呼ぶと実現できます
実装コードや動画も上げられている記事も紹介しておきます
参考リンク：
https://coposuke.hateblo.jp/entry/2020/06/07/020330

句読点だけぴょんぴょん飛ぶアニメーションしたり楽しげな文字列にできたりする。

## textInfo.characterInfoを参考に装飾UIを配置
`character`が漢字だったら`topLeft`、`topRight`の間にルビを振る
`lineNumber`を見て1行の最後の文字だったらその右に装飾をつける
などTextMeshPro部品内で対応するのではなく文字の位置情報を使用して他のコンポネントを配置したりできると思います。
![スクリーンショット 2023-12-01 13.16.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208498/cd8d57d4-185d-c3f3-aa60-ff3a0737d66d.png)