💬

【Unity】uGUIで長さが変わるテキストボックスを作りたい

に公開
2025/12/15

この記事はLivetoon Tech Advent Calendar 2025の13日目の記事です。
https://adventar.org/calendars/12157

はじめに

こんにちは。
株式会社Livetoonでインターンをしているmiuyeです。
普段は業務でUnityとかC#バックエンドとかNext.jsとかBlenderとかCLIP STUDIO PAINTとかをさわっています。なぜ?
https://kai0.onelink.me/Hogh/AdventCalendar2025

今回のアドベントカレンダーでは、LivetoonのAIキャラクターアプリのkaiwaに関わるエンジニアが、アプリの話からLLM・合成音声・インフラ監視・GPU・OSSまで、幅広くアドベントカレンダーとして書いて行く予定です。
是非、publicationをフォローして、記事を追ってみてください。

今回は、「kaiwa」のアプリ開発でも使っている、UnityでのUI実装、特にuGUIの話をします。

Unity uGUIでも可変長テキストボックスを実装したい!

アプリのUIを組んでいると、テキストの内容や長さに合わせて、吹き出しやボタンなどの親要素のサイズを自動で変えたくなる場面はよくあると思います。

「kaiwa」でも、キャラクターとのチャット機能があり、ここで可変長のテキストボックスが必要になりました。


うちの開発マネージャーがキャラクターに送ったチャット

しかし、Unity uGUIでこれを作ろうとすると、Content Size FitterやLayout Groupなどといった、自動レイアウト周りの挙動がいまいちわかりにくく……。思ったより素直に実装することができず、かなり泥沼にハマることになりました。

本記事では、そのときに試した、テキストの長さに応じてサイズが変わるテキストボックスを実現するためのいくつかのパターンと工夫をまとめて紹介します。

Unityの自動レイアウト

本題に入る前に、uGUIの自動レイアウトがどのように動作するのかを軽く整理しておきたいと思います。
詳しい仕様については、公式リファレンスや以下の記事で丁寧に紹介されているので、もし気になる人はそちらをご覧ください。

  • ILayoutElement

https://docs.unity3d.com/ja/2018.4/ScriptReference/UI.ILayoutElement.html

  • ILayoutController

https://docs.unity3d.com/ja/2018.4/ScriptReference/UI.ILayoutController.html

  • 解説記事

https://note.com/fuqunaga/n/ndef5b45863d4#S8OIF
https://qiita.com/lilacs/items/ccbb4049c4e93d69a5bf


自動レイアウトの大まかな流れ

uGUIにおける自動レイアウトは、ざっくり次のような流れで決まります。

1. Layout Element が「ほしいサイズ」を申告する

各子要素が、それぞれ自分の高さと幅についての要望を申告します。
その際に使われるのがILayoutElementであり、主要なプロパティは以下の通りです。

  • minWidth / minHeight(スペースが足りなくても最低限保持するサイズ)
  • preferredWidth / preferredHeight(十分なスペースがある際の推奨サイズ)
  • flexibleWidth / fexibleHeight(余ったスペースをどれだけ割り当てるかの度合い)

TextコンポーネントやLayoutElementコンポーネントなどがILayoutElementを実装しています。

2. Layout Controller が子を並べる

親オブジェクト側では、子の申告値を見て実際のサイズと位置を決めます。
これを行うのがILayoutContoroller(正確には、ILayoutControllerを継承したILayoutGroup)です。

  • HorizontalLayoutGroup
  • VerticalLayoutGroup
  • GridLayoutGroup

などのLayout Group系コンポーネントがILayoutGroupを実装しています。

3. 親のサイズを決め直す

最後に、ContentSizeFitterのようなコンポーネントが、子のサイズの情報をもとに自身(親)のRectTransformを決定します。
こういった、自身のRectTransformを操作するコンポーネントはILayoutSelfControllerを実装しています。


ここまでをまとめると、次のような流れで自動レイアウトが行われているわけですね。

子(TextなどのLayout Element)がサイズの要望を出す
→ ILayoutGroupが実装されたコンポーネントを持つ親が、子のRectTransformを決める
→ 親がILayoutSelfControllerが実装されたコンポーネントを持つ場合、
  子の合計サイズから自分自身のRectTransformを決める

とりあえず作ってみる

自動レイアウトの仕組みがなんとなくわかったところで、早速いくつかのパターンでテキストボックスを実装してみようと思います。

1. Textに直接Contetn Size Fitterをアタッチする



一番単純なパターン。テキストボックスかと言われると若干疑問ではある。

Textの子要素として適当なオブジェクトを配置しAnchorを調整すると、こんな感じのものも作れます。


さらに、Layout Elementをアタッチし、Min Widthを設定すると、固定の最小幅を決めることができます。

2. 親要素にLayout GroupとContent Size Fitterをアタッチする


Horizontal Layout Groupを適用

横に伸びます。
サイズが決まる流れは、まず、Textが書き換わった際に新たな要望サイズが親に送られ、Horizontal Layout GroupがTextのRectTransformを決定します。
そして、これによって決まった子要素のサイズをもとに、親であるTextBoxのRectTransformがContent Size Fitterによって決定されるという仕組みです。
なお、子のサイズの変更は、Control Child SizeがONのときのみ発生します。

ちなみに、Content Size FitterのHorizontal Fitを切ると、縦方向のみに伸びるボックスを作れます。


TextBoxのWidthを500に制限し、Horizontal FitをOFFに変更

以上の方法では、Content Size Fitterを使ってテキストボックスのサイズを変更していますが、拡大・縮小が起こる方向は、Pivotの位置によって決まります。
例えば、Pivotがオブジェクト中心(x:0.5, y:0.5)にあると、全方向にサイズの変更が起こってしまうので、伸び縮みする方向を限定したい場合、Pivotの位置をちゃんと設定しましょう。
Pivotの設定方法については以下の記事が参考になります。
https://shibuya24.info/entry/unity-ui-pivot

この実装には問題がある

ここまでの実装はいろいろな記事で紹介されている気がします。
が、これらは割と致命的な問題を抱えています。

最大幅を設定できない

今の実装だと、テキストの長さに応じてテキストボックスも無限に伸びていきます。

どこいくねーん
先ほどの縦方向に伸びるテキストボックスのようにすると、横幅が固定値になってしまい、やはり思ったようには動いてくれません。
というか、UnityにはUI要素のWidth/Heightの最大幅を制限するようなプロパティがないです。なんで?

テキストボックスの親要素にLayout Groupがついていると使えない

はじめの方で説明した通り、ILayoutGroupは子要素のRectTransformを制御し、ILayoutSelfControllerは子要素のサイズから自身のRectTransformを編集します。
つまり、もしテキストボックスのさらに親要素にHorizontal/Vertical Layout Groupがついている状態で、テキストボックス側にContent Size Fitterをつけてしまうと、ILayoutGroupとILayoutSelfControllerのサイズ計算が競合してしまい、画面のちらつきなどが起こることがあります。

実際、警告が出る

空のオブジェクトを配置して無理やり大きさを制限してみる

じゃあどうすんの、ということで、力技で解決します。
ヒエラルキーの構造は以下の通り。

Backgroundが背景となるパネルであり、Vertical Layout Groupがアタッチされています。そのため、先ほどのようにBackground直下のテキストボックスにContent Size Fitterをつける方法は使えません。

そこで今回は、

  • Content Size Fitterを使わず、Layout Groupのみでサイズの計算を行う
  • Layout Elementで幅を持たせた空オブジェクトを使い、強制的に幅を制限する

という方針を立てました。

作り方

まず、テキストボックスをラップする空オブジェクトContentを作成し、その子としてTextBoxを配置します。

この状態でテキストを入れると……。

どこいくねーん
これは、テキストはILayoutElementとして要望サイズを計算してはくれているものの、
TextBoxにILayoutGroupを実装したコンポーネントがついていないため、テキストのRectTransformがオブジェクト作成時のサイズのまま固定されている状態になっているからです。

さらに、TextBoxにもContentにもILayoutSelefControllerが実装されたコンポーネントがついていないため、テキストボックス自体の大きさも変化しません。

つまり、まだこの段階では、可変長のテキストボックスにはなっていないということです。

なので、ContentとTextBoxの両方にHorizontal Layout Groupをアタッチし、Controll Child SizeをONにしてあげましょう。

こうすると

  1. テキストが書き変わると、TextがILayoutElementとして新しい要望サイズを計算し、TextBoxに送る
  2. TextBoxのHorizontal Layout Groupが子のサイズ情報を集約し、そこから計算された自身のサイズの要望をLayout ElementとしてContentに提供する
  3. ContentがTextBoxのRectTransformを決定し、それによってTextのサイズが決まる
  4. その結果がContentの要望サイズとして集約され、BackgroundのVertical Layout Groupに伝わる

という流れで、最終的に全体のRectTransformが決まります。これは、Layout Group自体がレイアウト要素として振る舞うためです。

これでContent Size Fitterを使わずに可変長のテキストボックスを作れたわけですが、まだ最大幅の設定はすることができません。

そこで、Spaceという空オブジェクトをContentの子要素として追加します。
もちろんこのままだと何の意味もないので、SpaceにはLayout Elementをアタッチし、Min Widthを設定することで幅を持たせます。

とりあえず幅を500とかにしてみる

また、TextのText Wrapping ModeはNormalにしておき、最大幅を超える文章が入った際は折り返すようにします。

こうすると、TextのWidthが、画面幅のWidth - 空オブジェクトのMin Widthまでに制限されます。

わかりやすいように色付けしたもの(Bacground : 薄赤, Content : 赤, Space : 黒)

これで、Content Size Fitterを使わずに可変長のテキストボックスを作ることができました。やった〜!

いいわけないだろ

このためだけに空オブジェクトを配置するのは流石にイカれてるな……。
ということで、MaxWidthを実装してみます。

using UnityEngine;
using UnityEngine.UI;
using TMPro;

[RequireComponent(typeof(TextMeshProUGUI))]
public class BoundedText : MonoBehaviour, ILayoutElement
{
    [Tooltip("最大幅")]
    [SerializeField] float maxWidth = 500f;

    [SerializeField]TextMeshProUGUI targetText;

    private void Awake()
    {
        if (targetText == null)
        {
            targetText = GetComponent<TextMeshProUGUI>();
        }
    }
    public void CalculateLayoutInputHorizontal() {}
    public void CalculateLayoutInputVertical() {}

    public float minWidth => 0f;

    public float preferredWidth
    {
        get
        {
            float raw = targetText.GetPreferredValues(float.PositiveInfinity, float.PositiveInfinity).x;
            return Mathf.Min(raw, maxWidth);
        }
    }

    public float flexibleWidth => -1f;
    public float minHeight => 0f;

    public float preferredHeight
    {
        get
        {
            var size = targetText.GetPreferredValues(maxWidth, float.PositiveInfinity);
            return size.y;
        }
    }

    public float flexibleHeight => -1f;

    public int layoutPriority => 1;
}


これでInspectorから最大幅を設定できます。うれしい〜。

まとめ

  • Unity uGUIの自動レイアウトの仕組みだと、素直に可変長のテキストボックスを実装できない
  • Content Size Fitterを使うと手軽だけど、最大幅の制限や親側にLayoutGroupがある構造に対応できない
  • Layout Elementで幅を持たせた空オブジェクトを使うか、ILayoutElementを継承したスクリプトを使うと幅を制限できる

ただし、Layout Groupなどが増えると計算コストも当然高くなるため、使い過ぎには注意です。

uGUI、よくわからんな〜。

想定Q&A

Q. UI Toolkitを使えばよくない?

Production環境で使えると思うなら使ってください。

Q. そもそもなんでUnityでチャット画面とか作ってるんだよ

なんでだろうね。

Discussion