見出し画像

クセが強いUnityのテキスト周りで困ったこと

今回は、Unityでテキスト周りで困ったことを中心に書いていこうと思います。
私は普段webのフロントエンドエンジニアをしているのですが、Webに比較するとUnityでテキストを扱うにはとても苦労します。(ブラウザってホントよくできてる)
そもそもUnityはゲームエンジンだから、そんなゴリゴリテキストを使うことを想定していないかもしれません。

Unityでのテキスト

Unityでテキストを扱うに大きく二つのアプローチがあります

Unityの標準テキストコンポーネント
Unityの標準のテキスト。デフォルトではletter-spacingなどを調整することができず、スクリプトを書いたりして調整しないといけない。

TextMeshProアセット
昔は外部アセットだったが、Unityに取り込まれたもの。
letter-spacingやバウンディングボックス内にテキストを収めるなど細かい調整が可能なもの

どちらもとてもクセが強いのですが、テキスト周りの細かい設定をしていくのであれば、TextMeshProを使っていく必要があります。

TextMeshProで日本語を表示する

通常のテキストとは違い、すぐに使いたいフォントが使えるわけではなく設定が必要です。

・使いたいフォントファミリーを設定する
・アプリ内で使いたい文字を設定する
・アトラス画像のサイズを決める
・基準のフォントサイズを設定する

という設定をすることで以下のようなアトラス画像を作成します。

スクリーンショット 2020-05-17 13.49.39

TextMeshProの設定に関しては以下の記事を参考にさせていただきました

今回はユーザーの入力があり、ほぼ全ての日本語を網羅しないといけなかったので、

を参考に設定しています。
最初はこの文字が収まる程度にアトラス画像を8192x8192と設定していたのですが、Macでプレビューしたところテキストが表示されないということがありました。
色々な設定を変えていると、どうやらアトラス画像が大きすぎるようで、4096x4096にしてみるとテキストが表示されました。

しかし、アトラス画像を小さくするとその中に全ての文字が入りきらずに一部の文字が表示できないという状態に。。
アトラス画像を作成する際にはその画像に焼き込むフォントの基準サイズを設定できるので、そちらを調整することで全ての文字が入るようにしました。
ただし、アトラス画像に設定する基準のフォントサイズを小さくすればするほど、大きなサイズでフォントを使用するときに、拡大した時のフォルムがおかしくなってしまうので、ギリギリのラインを調整していく必要があります。(本当に辛い)

Unityだって絵文字を表示したい

スクリーンショット 2020-05-17 13.54.31

ユーザーインプットがあるので絵文字を使いたいのですが、案の定普通に表示させることはできません。
もうここまでテキスト周りで苦しめられたので、やっぱりね、としか思いません。

絵文字に関しては以下を参考にさせていただきました。

・Full Emoji Support Apiの導入
・emoji-dataから絵文字の画像一覧とjsonのセットをダウンロード
・emoji-dataをUnityで使えるように調整

これらの設定によりTextMeshProを使っていたところをTMP_EmojiTextUGUIに変更することで、ほぼ全ての絵文字を利用できるようになります。

絵文字のコピペはこちらが便利

テキスト周りでデザイン上凝った処理を行う

スクリーンショット 2020-05-17 14.00.12

デザイナーの人が聞いたら、こんなもの凝ってねぇよ!とか言われそうですが、Unityからすると凝っています。

cssで言えば、

span
{
display:inlne-block;
background-color:#ccc;
}

みたいな感じですぐ終わるのですが、Unityでは結構ヘビーなプログラムを組む必要があります。

・ユーザー入力を表示させるので改行位置がどこに来るかがわからない
・テキストボックスのバウンディングボックスではなく、表示エリアを判定する必要がある

というところで、コードは以下のようになります。

using System.Collections.Generic;
using TMPro;
using UnityEngine;

[RequireComponent (typeof (TextMeshProUGUI))]
public class TextRect : MonoBehaviour
{
   private TextMeshProUGUI text;
 
   void Start()
   {
       text = this.GetComponent<TextMeshProUGUI>();
       text.ForceMeshUpdate();
   }


   public List<TextRectInfo> GetTextRect()
   {
       text = this.GetComponent<TextMeshProUGUI>();
      var info = text.textInfo;
      text.ForceMeshUpdate();
      int lineCount = -1;

      float maxHeight = float.NegativeInfinity;
      float maxTop = float.NegativeInfinity;
      float y = 0;
      float x = 0;
      float width = 0;
      float height = 0;
      List<TMP_CharacterInfo> firstCharaInfoList = new List<TMP_CharacterInfo>();
      List<TMP_CharacterInfo> lastCharaInfoList = new List<TMP_CharacterInfo>();
      List<TextRectInfo> textRectInfoList = new List<TextRectInfo>();

      //charactorInfoリストにはなぜか空文字が入ってくる可能性があるので、charactrerCountでループする。
      for (int i = 0; i < info.characterCount; i++)
      {
          if (lineCount != info.characterInfo[i].lineNumber)
          {
              lineCount++;
              firstCharaInfoList.Add(info.characterInfo[i]);
          }

          if (!isDefined(lineCount, lastCharaInfoList))
          {
              lastCharaInfoList.Add(info.characterInfo[i]);
          }
          else
          {
              lastCharaInfoList[lineCount] = info.characterInfo[i];
          }

          height = info.characterInfo[i].topLeft.y - info.characterInfo[i].bottomLeft.y;
          if (height > maxHeight)
          {
              maxHeight = height;
          }

          y = info.characterInfo[i].topLeft.y;

          if (y > maxTop)
          {
              maxTop = y;
          }
      }
      for(int i = 0;i<firstCharaInfoList.Count;i++){
        TextRectInfo textRectInfo = new TextRectInfo();
         textRectInfo.height = maxHeight;
        textRectInfo.width = lastCharaInfoList[i].topRight.x - firstCharaInfoList[i].topLeft.x;
        textRectInfo.position.x = firstCharaInfoList[i].topLeft.x;
        textRectInfo.position.y = maxTop;
        textRectInfoList.Add(textRectInfo);
      }
      
      
      return textRectInfoList;
   }
   
   private bool isDefined(int index, List<TMP_CharacterInfo> list)
   {
       return index < list.Count;
   }

}


public class TextRectInfo
   {
       public float width;
       public float height;
       public Vector3 position;
   }

characterInfoにlineNumberというプロパティがあり、その文字が何行目に属しているのかがわかるので、ループで回して、その行の先頭と最後を判定し各行のテキストの表示領域を計算しています。

このテキスト表示領域の情報をもとに、それぞれのテキストの後ろに矩形を敷いてあげればこのデザインが実装できます。

まとめ

Unityのテキストはお世辞にも扱いやすいとはいえず、クセが強いです。しかし、詳細な情報を取れるAPIが用意されていたり、先人たちが知見を残していてくれるので、あきらめなければ色々なことが実現できると思います。

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