uGUIの自動レイアウトを根っこのほうから理解する
最初に言いたいこと:Vertical Layout Group / Horizontal Layout Group あたりをいきなり理解しようとすると難しいと思います!
uGUI の自動レイアウトわかりにくいですよね。だいぶ理解できた気がするので解説してみます。
自動レイアウトとは
まずは自動レイアウトを含まない通常のレイアウト処理について。これはおなじみの RectTransform によるものでこの挙動は比較的簡単に理解できるのではないかと思います。ポイントとしては動的な変化はあるものの Hierarchy 上の親の矩形をもとに子の矩形が一意に決まる処理です。
自動レイアウトはその範疇に収まらないレイアウトを行うために子の情報をもとに自分自身や子の RectTransform を編集する処理になります。RectTransform の値を編集するまでが自動レイアウトの機能でその後は通常のレイアウト処理になります。
具体的には「Text など内容によってサイズが変わるUIがはみ出さないように親のUIのサイズを広げる」といったような処理になります。
レイアウトエレメント
自動レイアウトは大きく2つのコンポーネントで成り立っており、ひとつがレイアウトエレメント、もう一方がレイアウトコントローラーです。
レイアウトエレメントは、
自身のサイズについての要望
です。要望ですのでこれ単体では特になにも変化しません。
要望のパラメーターは次の3つです。
Min Width/Height(最小サイズ) - 最悪これだけ必要
Preferred Width/Height(推奨サイズ)- 理想的にはこのサイズにしたい
Flexible Width/Height(追加サイズウェイト) - スペースが余ってたとき拡げるサイズの重み
Flexible だけ単位が異なり、複数のレイアウトエレメントで余ったスペースを埋めるときにこの値で重み付けをして広げるサイズを割り振ります。
Min=0,Prefferd=0で赤と青のFlexibleを変えて並べたときの挙動
左:(赤,青)=(1,1) 右:(赤,青)=(2,1)
余ったスペースを埋めるレイアウトエレメントがひとつしかないときは Flexible>0 であればどのような値でも100%スペースが埋まるように拡大されます。
複数のレイアウトエレメント
レイアウトエレメントとして扱われるのは LayoutElement Component だけでなく ILayoutElement を継承しているコンポーネント全てが対象です。Text や Image も含まれます。
同じゲームオブジェクトに複数のレイアウトエレメントがアタッチされている場合は次のルールに従います。
ILayoutElement.layoutPriority の値がもっとも高いものを、もっとも高いものが複数ある場合はその中で Min、Preferred、Flexble それぞれ最大のものを採用
またレイアウトエレメント付きのゲームオブジェクトは PreviewWindow に Layout Properties という項目が追加され、最終的な要望とソースのレイアウトエレメントを確認することができます。
実際触ってるとこれかなり便利です。
前述のとおり Text などもレイアウトエレメントなので意図せず親がこの要望を受けてしまう可能性があります。このようなときはLayoutElement.ignoreLayout で親にこのゲームオブジェクトのレイアウトエレメントを無視させることができます。
*後述しますが自分自身の要望を受けるレイアウトコントローラーもあります。その場合こちらのフラグは効きません。
ちなみに ignoreLayout プロパティは ILayoutEment ではなく ILayoutIgnore というインタフェースで定義されています。LayoutElement Component は ILayoutElement と ILayoutIgnore 両方を継承しているんですね
ちなみにちなみに ILayoutIgnore が複数アタッチされているゲームオブジェクトではひとつでも ignoreLayout==false があれば無視されません
レイアウトコントローラー
レイアウトエレメントが単体ではなにも効果のない要望であったのに対しレイアウトコントローラーは
RectTransform の編集者
です。要望のどのパラメータを使うか、そもそも要望を参照するかどうか、全てレイアウトコントローラー次第です。
要望はサイズに関するものだけですが、レイアウトコントローラーはサイズ以外の要素(RectTransform.anchoredPositionなど)も編集することもあります。
編集される要素はインスペクター上で入力不可となり、影響を与えているレイアウトコントローラーが表示されます。
親の HorizontalLayoutGroup の影響を受けている RectTransform
レイアウトコントローラーには大きく分けて2種類ありそれぞれ編集対象となる RectTransform が異なります。
ILayoutSelfController - 自身の RectTransform を編集する
・Content Size Fitter
・Aspect Ratio Fitter
ILayoutGroup - 子供たちの RectTransform を編集する
LayoutGroup - 全ての子が編集対象
・Horizontal Layout Group
・Vertical Layout Group
・Grid Layout Group
非 LayoutGroup - 特定の子が編集対象
・Scroll Rect
*リストは UnityEngine.UI に標準で含まれているコンポーネント
複数のレイアウトコントローラーの編集が衝突しないように Editor 上でのサポートがあります。
同じゲームオブジェクトに複数の LayoutGroup をアタッチしようとするとエラーが出る
ILayoutGroup の影響下なのに ILayoutSelfController をアタッチするとインスペクターに警告が表示される
気が利いていますね!
ここから実際に使えるコンポーネントの解説になります。
Content Size Fitter
自身のサイズをレイアウトエレメントの Min または Preferred にします。
レイアウトエレメントの要望を実体化するもっともシンプルなやつです。
Aspect Ratio Fitter
自身のサイズを指定したアスペクト比になるように編集します。レイアウトエレメントは見ません。
Horizontal / Vertical Layout Group
さぁ来ました。メインコンテンツです。
「Horizontal / Vertical Layout Group とやらを使いたい → いまいち挙動がわからない → 自動レイアウトとは?」と調べ始めるのがよくあるパターンではないかと予想します。しかしこれらは自動レイアウトの機能のなかでも特にややこしく言わばボスキャラです。この説明のためにここまでベースの仕組みを解説してきたと言っても過言ではありません。
図のように親の Gray にHorizontal Layout Group をアタッチして子の Red、Blue、Green、Yellow を並べる処理を例に解説します。
LayoutGroup なのですべての子の RectTransform を編集します。Inspector 上のパラメータはすべてその際に使われるものです。
上3つのパラメータは子の位置を決めるためのものです。
・ Padding - 上下左右の内側のパディング
・ Spacing - 子供間のスペース
・ ChildAlignment - 整列の基準点
ぱっと見でわかるとおりで特に問題ないかと思います。
次の3つがわかりにくい部分だと思います。
・ Control Child Size - 子のレイアウトエレメントからサイズを編集する
Padding と Spacing の幅を確保した上で各子供のサイズをレイアウトエレメントに合わせて変更します。
左:スペースが足りない状態でも最低限 Min のサイズは保証(はみ出す)
中央:スペースが余っていれば Perefferd まで拡大
右:さらに余っていれば Flexible の重みに応じて拡大
(図はRed Flexible=1f、Yellow Flexible=2f)
Control Child Size が ON のときにレイアウトエレメントがアタッチされていない子は Min=Prefered=Flexible=0 として扱われます。
Control Child Size が OFF のときはサイズの変更はなく位置だけ整列します。
・ Use Child Scale - 子の位置を求める際にスケールを考慮する
Red の localScale.x=2f のとき
左:Use Child Scale が OFF 右:Use Child Scale が ON
このフラグが立ってなくても子の表示にはスケールが適用されますので基本的には ON でいいと思います。逆に位置はそのままで一時的なスケールアニメーションなどをする場合は OFF にしておく必要があります。
・ Child Force Expand - 子の Flexible を強制的に有効化する
スペースが余っているときの挙動に影響します。
Control Child Size が ON のとき
子の Flexible が 1f 未満(未指定も含む)のものを強制的に 1f として扱います。結果、すべての子が Flexible の重みに応じて拡大されます。
Control Child Size が OFF のとき
すべての子の Flexible=1f としてレイアウトします。しかし Control Child Size が OFF なので子のサイズは変わりません。結果として Spacing を無視して端まで埋まるよう等間隔に子が整列します。
Red Flexible=2f 、ほかは未指定のとき
左:Control Child Size が ON 右:Control Child Size が OFF
自身のサイズも子に合わせたい
上記の計6つのパラメータの説明はしれっと「スペースが余ってたら」などとあらかじめ自身のサイズが決まっている前提になっています。しかし子を並べた結果に合わせて自身のサイズも変更したいケースも多いと思います。前述のとおり ILayoutGroup はあくまで「子の RectTransform を編集する」ので自身のサイズは編集されません。どうすればよいでしょう?
Horizontal Layout Group で子を並べた結果はみ出てしまう例。自身のサイズも合わせて拡大したい
実は標準で含まれている ILayoutGroup コンポーネントはすべて ILayoutElement も継承していまおり、並べた結果から計算した Min、Preferred、Flexible が取得できます。
Horizontal Layout Group がレイアウトエレメントとして動作している様子
ですので手っ取り早い方法としては Content Size Fitter をアタッチしてしまうのが簡単です。より複雑なケースでは更に親に LayoutGroup がいて Control Child Size を ON にしておくといったケースもあるかと思います。
Grid Layout Group
子を指定した Cell Size にしてグリッド状に並べます。子のレイアウトエレメントは無視されます。
Scroll Rect
ソースコードを見ると Scroll Rect も ILayoutGroup を継承しています。ただこれはスクロールバーのON/OFFなど特定の子の RectTransform を編集するためのようで、仕様的に都合が良かったので ILayoutGroup の機能に乗っかっている感じかと思います。あまり自動レイアウトの機能として意識しなくてもいいと思います。自動レイアウトのドキュメントにも載っていません。
さいごに
Horizontal/Vertical Layout Group の挙動がよくわからずなんだなんだと調べてるうちに自動レイアウト挙動をだいぶ見渡せるようになったので、せっかくなので記事にしてみました。同じように困った方の理解の助けになれば幸いです。
また、よりしっかり理解するためにオフィシャルドキュメントに目を通しておくのをおすすめします。本記事では触れていない処理順などもコンパクトにまとまって記載されています。