Unityで、ある要素を綺麗に並べるために、HorizontalLayoutGroupやVerticalLayoutGroupを使うも、うまくいかないことが多いので自分なりにまとめたい。なお、参考に貼った2つの記事が、かなりまとめてくれているので合わせてチェックしてみて欲しい。
自動レイアウトとは
そもそも自動レイアウトとは、一体何なのか。まずはそれを見ていく。
公式リファレンスを見る。
https://docs.unity3d.com/ja/2019.4/Manual/UIAutoLayout.html
公式を見ると、自動レイアウトシステムとは、後述するHorizontalLayoutGroupなどのLayoutGroupを使って、子要素をきれいに配置することや、テキストなどの中身の大きさに合わせて親のサイズも変更したりするようなことを意味する。
レイアウトを勝手に〜してくれる便利なシステムとおおまかに理解しておく。
ちなみに自動レイアウトを使う場合、UI要素が持つRectTransformはほぼいじらないと言ってよい。なぜなら自動で調整するというのが、この自動レイアウトの目的みたいなところだからだ。
というか、下のようにRectTransformはいじれなくなるというのが正しいところだ。「こっちが自動で調整してやってるのに、勝手に値を変更するな!」ってことだろうか。
自動レイアウトを使う場合、RectTransformの知識はほぼいらないが、もしRectTransformについて知りたい方はこちらも参照してほしい。記事長めなので空いてる時間にでも。
また、この文中でたくさん、レイアウト〇〇といった単語が出てくると思う。初めて、自動レイアウトについて学ぶ人は途中でわけわからなくなると思うので、わからなくなったらここを見て大雑把な単語の意味を理解しておくと、全体の把握に近づくかも。
- レイアウトコントローラー・・・自動レイアウトの概念の1つ。この中に、レイアウトグループがある感じ
- レイアウトエレメント・・・自動レイアウトの概念の1つ。レイアウトコントローラーを扱う時に、この要素を参照する。
- レイアウトグループ・・・レイアウトコントローラーの中に含まれるもの、HorizontalLayoutGroupやVerticalLayoutGroup、GridLayoutGroupのこと。
- レイアウトプロパティ・・・レイアウトエレメントを持っていると、インスペクタの下から表示できるもの。表示できなくても、レイアウトエレメントコンポーネントをつけると、レイアウトプロパティを表示することができる。
LayoutControllersとLayoutElements
自動レイアウト自体の概念がわかったところで、次に理解しなければならないのがこの2つ。LayoutControllersとLayoutElementsだ。
LayoutControllersにはHorizontalLayoutGroupなども含まれるのでまだ理解しやすいが、LayoutElementsが難しかった。
これが中々理解できなかったので、HorizontalLayoutGropとかを使っても中身の子要素がペシャンコになったりして、いまいち使いこなせなかった。
逆に言えば、LayoutElementsを理解することができれば、自動レイアウトを使いこなせるようになるはず。
ちなみにここからは、LayoutControllersはレイアウトコントローラー、LayoutElementsはレイアウトエレメント(レイアウト要素としている記事もある。)で文中では表記を統一したい。
レイアウトエレメント
レイアウトエレメントを一言で表すと、ILayoutElementインターフェースを実装しているコンポーネントが持つ要素のことだ。
インターフェースのことはここでは割愛する。
さて、いきなり何のことだかわからないと思うが、まずは適当なプロジェクトを作成して、UIからTextを作ってみて欲しい。
そして、Textのインスペクタ一番下にあるDefaultUIMaterialをクリックして、LayoutPropertiesに変更しよう。
こんな感じでインスペクタ上で、LayoutPropertiesが表示できたらこのコンポーネントはレイアウトエレメントを持っているということになる。
逆に言えば、LayoutPropertiesが表示できないなら、そのコンポーネントはレイアウトエレメントを持っていないと言ってよい。(正確に言えば、RectTransformを持つコンポーネントは全部もっているらしいが、上のように覚えた方が楽。)
試しに、UIからToggleを作成してインスペクタを見てみよう。LayoutPropertiesは表示されていないはずだ。
UIの中では、TextとImageがLayoutPropertiesを持っており、他は持っていない。(たぶん)
TextとImageのどちらも、最初に説明したILayoutElementインターフェースを実装している。
また、LayoutElementコンポーネントというものもあり、これをくっつけることで、そのコンポーネントがレイアウトエレメントを持っていなくても、無理矢理持たせることができる。
上で作ったToggleオブジェクトのインスペクタに、LayoutElementコンポーネントを追加してみよう。
LayoutElementコンポーネントをくっつけることで、一番下からLayoutPropertiesを選択できるようになるはずだ。
もちろんこのLayoutElementコンポーネントにもILayoutElementインターフェースがついている。というか、こいつがついているからLayoutPropertiesが表示される。レイアウトエレメントを持つためには、ILayoutElementインターフェースがついているコンポーネントをくっつければよいということだ。
ここまで、レイアウトエレメントについてざっと説明してきた。ただ、このレイアウトエレメントがついていることで、基本的に何かが変わるということはない。
ではなぜ、レイアウトエレメントについて説明してきたかと言うと、レイアウトコントローラーを使う時にレイアウトエレメントを参照する必要があるからだ。
レイアウトコントローラーを使わないのであれば、普段からレイアウトエレメントを意識する必要はまったくない。ただ、HorizontalLayoutGropやContentSizeFitterなどの、レイアウトコントローラーを使う時にこのレイアウトエレメントが重要になってくるのだ。
一旦ここまでまとめてみる。
- レイアウトエレメントはILayoutElementインターフェースを実装しているコンポーネントのこと。
- LayoutElementコンポーネントをくっつけることで、レイアウトエレメントを持っていなくても無理矢理持たせられる。
- レイアウトエレメントを持ってるからといって、特段何か意識する必要はない。レイアウトコントローラーを使う時に再度意識しよう。
レイアウトエレメントが持つ各プロパティは一旦置いといて、レイアウトコントローラーを見る。
レイアウトコントローラー
レイアウトコントローラーは、子の要素の配置を決めるHorizontalLayoutGroupなどのレイアウトグループと、ContentSizeFitterなどの自身のサイズを変更するものに大きく分けられる。
もっと、細かく言うとILayoutGroupインターフェースを実装しているコンポーネントをもつレイアウトグループと ILayoutSelfControllerインターフェースを実装しているものに分けられる。
文章で説明するとわけわからなくなりそうだが、これはこちらの参考記事の中段あたりに図でまとまっているのでぜひ、見てみてほしい。
https://note.com/fuqunaga/n/ndef5b45863d4#hpQYV
つまりは、レイアウトコントローラーは大きく分けて、自身のサイズを変化するものと子要素の位置と大きさを変更するものの2つに分類されるということだ。ではそれぞれ見ていこう。
自身のサイズを変更するもの
まずレイアウトコントローラーの中で自身のサイズを変更するものからみていく。自身のサイズを変更するコンポーネントはILayoutSelfControllerインターフェースを実装しており、2つある。
「自身のサイズを変更するって普通にRectTransformをいじるだけじゃダメなの?」と思う方もいると思うが、自動レイアウトを使う場合は、最初に言った通りRectTransformを基本的にはいじらない。
ContentSizeFitter
まずは、ContentSizeFitter。おそらくこれは結構使用頻度が高いんじゃないだろうか?このコンポーネントをつけると、自身のレイアウトエレメントの値に応じてサイズを変更させることができる。
テキストで試してみよう。まずは普通のテキストを作成する。そして、テキストのレイアウトプロパティを開いて、PreferredWidthとPreferredHeightを確認しよう。そこにはテキストの大きさに応じて値が設定されているはずだ。
そして、Textの文字数を変更してから、もう一度レイアウトプロパティを確認してみよう。
このようにPreferredの各値は自動で変更される。テキストの場合は、テキストの文字数によって変わる。
そしたら、このTextにContentSizeFitterコンポーネントをくっつけてみよう。
ContentSizeFitterのプロパティには、HorizontalFitとVerticalFitの2つの値がある。HorizontalFitのUnconstrainedをクリックしてみよう。
Unconstrainedをクリックすると、選択肢が2つ現れる。PreferredSizeとMinSizeだ。この項目どこかで見なかっただろうか?そう、これがレイアウトプロパティに表示されていたものだ。
ここで、MinSizeを選んでみよう。どうなるか想像がつくだろうか。そう、水平方向にペシャンコになる。なぜなら、レイアウトプロパティのMinSizeが0になっていたからだ。
もう一度、HorizontalFitを選択して今度はPreferredSizeに変更してみよう。
ここまでRectTransformは全くいじっていない。ちなみに、RectTransformの値を見てみると、Widthが157になっている。そして、サイズ(Width)を手動で変更できなくなっている。
つまり、レイアウトプロパティの値によってRectTransformの値が変更されているということだ。
この設定にしておくと、文字サイズを変更すると勝手にWidthが変更されていく。
これを、後に説明するレイアウトグループと組み合わせれば、子のテキストのサイズに合わせて親の大きさを変更させることができる。
AspectRatioFitter
次はAspectRatioFitter。これは比率を設定したら、その比率に応じて勝手に大きさを変更してくれるものだ。何回も繰り返すが、RectTransformはいじらないので覚えておこう。
では、こちらもTextにAspectRatioFitterをくっつけていく。
AspectRatioFitterは2つのプロパティを持っている。AspectModeとAspectRatioだ。初期はAspectModeをNoneになっており、Noneだと特に何も変わらない。Modeを変更すると、AspectRatioで設定した比率に応じてサイズが変更されているというわけだ。AspectRatioは自分で好きに設定できるぞ。
AspectModeを変更してみる。modeはいくつかあるが、そんなに難しくない。最初は「Width Controls Height」から。
Width Controls Height
これは、Widthの大きさに応じて高さを変更するもの、もちろん比率はAspectRatioに合わせて。
変更したらオブジェクトを動かしてみる。すると、Widthは動くが高さはマウスで自由に変更することができない。
インスペクターを確認してみるとHeightの値を変更できないようになっている。Widthを広げると、AspectRatioの比率に応じて勝手に高さを変更するものだからだ。
Height Controls Width
次に、AspectModeをHeightControlsWidthに変更する。
今度はさっきと逆でHeightに合わせてWidthを変更するもの。つまり、Widthの値をRectTransformから変更することはできない。
Fit In Parent
次はFitInParent。これは簡単で親のサイズまで、AspectRatioの比率を維持しながら広がるものだ。
Envelope Parent
最後はEnvelopParent。これも簡単で親のサイズを超える、というか親を囲むように大きくなる。
これでILayoutSelfControllerインターフェースを引き継いだもの終わりだ。次は、メインテーマであるレイアウトグループだ。
レイアウトグループ
レイアウトグループの使い方は本当に難しいが、理解できれば逆に便利なものになるので、ざっくり概念を理解して練習して使ってみよう。
レイアウトグループは主に以下の3つだ。
- HorizontalLayoutGroup・・・子の要素を水平に並べるもの
- VerticalLayoutGroup・・・子の要素を垂直に並べるもの
- GridLayoutGroup・・・子の要素をグリッド上に並べるもの。格子状って言った方がわかりやすいかな?
ここで注意してほしいの点が2つ。
1点目は、レイアウトグループは子の大きさも変更するという点だ。これは上でもいったが、レイアウトグループを使う時に子のレイアウトエレメントを参照する。
2点目は、レイアウトグループ自身もレイアウトエレメントを持つということだ。つまり、これらのコンポーネントをくっつけたものは、他のレイアウトグループや、ContentSizeFitterなどによってサイズを変更することができるのだ。これは後でみていくので頭の片隅にでも入れておいてほしい。
まずはそれぞれの要素についてみていこう。
HorizontalLayoutGroup
HorizontalLayoutGroupは、このコンポーネントをつけたオブジェクトの子要素を水平に並べる機能+子の大きさも変更するものだ。
まずは適当にImageオブジェクトを3つほど作成して、色を変更しておく。
次にHorizontalLayoutGroupをアタッチするPanelを用意して適当なサイズにしておく。
PanelにHorizontalLayoutGroupコンポーネントをつける。
Panelの子として、Imageを入れる。
すると、3つのImageオブジェクトが水平方向に均等に並ぶはずだ。
子のImageのRectTransformを確認してみると、「HorizontalLayoutGroupによっていくつかの値は制御されますよ。」と書かれている。
ではここから詳しく、HorizontalLayoutGroupを見ていきたいので、初期でチェックがついていたChildForceExpandを外しておく。
チェックを外すと子要素がぎゅっと団子状に密集する。
1つずつプロパティを見ていこう。これを理解すればVerticalLayoutGroupも理解できる。
PaddingとSpacingとChildAlignmentは簡単なのでざっと見てみる。
Paddingの左の▶︎ボタンを押すと、4つほど項目が記入できる。ここで、子の要素の位置のPaddingを調節できる。Paddingは親からどれくらいの距離を空けるかみたいなイメージで理解すればいいと思う。
ちなみに基準はChildAlignmentに表示されている場所。デフォルトでは、左上になっている。
Spacingは子要素の隙間をどれくらい空けるかという意味だ。
ChildAlignmentは、子がどこを基準にして並びはじめるかや、Paddingの時の基準となる。
次にこの3つの要素を説明していく。ここをいじることで、子の大きさが変更される。そしてこの時何を基準に大きさが変更されるかというと、再三言っているように子のレイアウトエレメントを基に大きさが変更される。
子要素のなかに、Textオブジェクトも追加しておく。
ControlChildSizeは子のレイアウトエレメントをみて、子の大きさを決める。試しにチェックをつけてみる。
すると、Imageオブジェクトが消えてしまった。
これは、Imageオブジェクトのレイアウトプロパティの各値が0に設定されているからだ。
Imageのレイアウトプロパティを見てみると、各値が0になっている。なので、サイズが0になる=Imageがつぶれたようになるというわけだ。
一方で、Textオブジェクトのレイアウトプロパティを見てみると、PrefferdWidthやHeightに値が設定されている。なので、ControlChildSizeのチェックをつけたときに、その値になったというわけだ。
ImageオブジェクトにLayoutElementコンポーネントをつけてあげて、サイズを変更してみる。
レイアウトエレメントのMinWidthとMinHeightをそれぞれ100にする。
そのままレイアウトプロパティを確認すると、MinWidthとMinHeightの値がそれぞれ100になっており、SourceのところにLayoutElementと書いてある。つまりこれは、LayoutElementコンポーネントによって、Imageがもつレイアウトエレメントを上書きしたということだ。
オブジェクトを確認してみると、Imageのサイズが100×100に戻っている。
では、2つ目のイメージにもLayoutElementコンポーネントをつけてあげる。ついでに今度は、Prefferdの値もそれぞれ設定してあげる。
Imageオブジェクトを確認してみると、そこではPrefferdの値が優先されている。ここでMinとPrefferedの違いを説明しておくと、Minは最低限描画が保証される値、Prefferdは親に余裕があればその値を描画するものだ。
Minは親がどんなに小さくなっても、そのサイズだけは保証される値だ。
PreferredHeightを500にしてみる。一方親の高さは360だ。この場合はどうなるだろうか。
2つめのImageのサイズを200に戻したら、3つめののImageにもLayoutElementコンポーネントをつけて、こちらはFlexibleも設定してあげよう。
FlexibleはMinやPreferredと違って割合で計算して描画するらしい。ここらへんよくわかってない。
Flexibleを設定したImageの画像は、描画できるならPreferredの値を超えて表示される。余裕があれば目一杯広がると覚えておけばよいだろう。
ここで一番最初にHorizontalLayoutGroupをつけた時を思い出して欲しい。初期では、ChildForceExpandだけONになっている。
なので、子のサイズも親の範囲の中で最大限広がると思うかもしれないが、あくまでこれはControlChildSizeがONになっている時だけだ。これがOFFの時は、レイアウトプロパティを参照しないので、子のサイズは変更されない。
この場合、子のサイズは変更されずに、均等に広がるように配置される。
この違いはちょっと覚えにくいが、理解しておくとどこかで応用が効きそうだ。また、大体の記事ではChildForceExpandはOFFに設定しているので慣れない内はOFFにしておくのもありだと思う。
UseChildScaleは、そんなに難しくない。これは子がScaleをいじっている場合に関係がでてくる。再度各Imageはサイズ100に戻しておく。
そしてImageの一つのScaleを変更する。
Scaleを変化すると、その要素の位置がずれてしまう。そこで、UseChildeScaleをONにすると、Scaleの値を考慮して要素を並べてくれる。
そもそも、uGUIでScaleの値を変化させることが自分はあまりないから、まだ使い道はわかってない。Scaleをいじる人はONにしておけば問題ないだろう。
ここまでHorizontalLayoutGroupの各要素について、ざっと見てみたがどうだろうか?なんとなく理解してもらえたらうれしい。
初心者の内はとりあえず、ControlChildSizeをオンにさえしなければ、子のサイズが自動で変化することはないので、覚えておきたい。
一旦まとめる。
- Padding、Spacing、ChildAlignmentは子の要素の位置関係や間隔などを変化させる。
- ControlChildSizeをONにすると、子のレイアウトエレメントを基に子の大きさを変化させる。
- UseChildScaleをONにすると、子のスケールを考慮して位置を変化させる
- ChildForceExpandは、強制的に子の大きさを広げる。ただし、ControlChildSizeがOFFの場合には、均等に配置される。
VerticalLayoutGroup
VerticalLayoutGroupは、水平が垂直になっただけで、後のプロパティはHorizontalLayoutGroupと変わらないので省略する。Horizontalが理解できれば大丈夫だ。
GridLayoutGroup
GridLayoutGroupは子要素をグリッドに並べるものだ。格子状と言った方がわかりやすいだろうか。
一番の特徴は、レイアウトエレメントの値は考慮しないということだ。GridLayoutGroupのインスペクタ上だけで完結するので、だいぶわかりやすいはず。
さっそく親要素にGridLayoutGroupをつけてみよう。
GridLayoutGroupをつけると、例によってその子要素のRectTransformは操作できなくなる。
HorizontalLayoutGroupでも表れたプロパティもあるので、割愛する部分も含めるが見ていこう。
まずはPadding。これはHorizontalLayoutGroupでも出てきたもので、子の要素の位置を調節する。
次のCellSizeが重要で、これで子の大きさを指定する。
Cellは一つの箱みたいなイメージ(英語だと細胞の意味)で、その箱の大きさをいくつにしますかって感じだ。Excel使ったことあれば、あの白いマス一つがセルと呼ばれるもので、あの大きさを変更するといった意味合いになる。
SpacingはCellとCellの間隔を決められる。
次のプロパティの説明をわかりやすくするために、要素を増やして番号をつけておく。なお、Paddingなどはもとの状態に戻している。
StartCornerはCellの最初の要素をどこから始めるかというもので、初期はUpperLeftの状態。
選択肢は4つある。以下の通りだ。
UpperRight
LowerLeft
LowerRight
StartAxisは、子要素がどっち方向に進むかをきめるものだ。Horizontalは水平方向に、Verticalは垂直方向に進む。
ChildAlignmentは、HorizontalLayoutGroupでも登場したプロパティなので割愛する。
最後にConstraintだが、これは正直よくわかっていない。どうやら、ContentSizeFitterなどと組み合わせる時にここをいじるそう。また理解したら追記したい。
公式のGridLayoutGroupのマニュアルを載っけておくので詳しくはそちらを参照してください。
公式マニュアルhttps://docs.unity3d.com/jp/current/Manual/script-GridLayoutGroup.html
レイアウトグループもレイアウトエレメントを持つ
ここまで、レイアウトグループについて見てきたが、もう一つ大事なことを。
レイアウトグループはレイアウトエレメントも持っているということだ。
上で操作していた、GridLayoutGroupコンポーネントをくっつけているオブジェクトのインスペクタをみると、確かにレイアウトプロパティが表示される。また、MinやPreferredのSourceの部分を見ると、GridLayoutGroupと書かれている。
つまり、レイアウトグループを持つコンポーネントを、さらにその親のレイアウトグループで操作できたり、ContentSizeFitterなどをつけて大きさを調節することができるということだ。
※上に書いたようにGridLayoutGroupにContentSizeFitterをつける時は、ContentSizeFitterのconstrainを操作した方がいろいろうまくいくらしいので、調べてみてください。
試しに、HorizontalLayoutGroupがついているコンポーネントにContentSizeFitterをくっつけてみて、値をPreferredに設定してみよう。
こんな感じでレイアウトグループもレイアウトエレメントを持っているということを覚えておこう。
まとめ
これで自動レイアウトについては大体まとめられたと思う。
ここまで読んでくれた方おつかれさまでした。かなり量があると思うので、暇な時につまみながら読んでください。なるべく、わかりやすく書いたつもりですが、誤字ってる部分や間違っている部分もあるかもしれないのでリライトしていきます。(気力があればですが、、)
参考に貼った2つの記事と、Unityのマニュアルなんかを合わせて読めばだいぶ理解が深まるのではないかと思います。
自動レイアウトでいらつかなくなって、少しでも役に立ってくれれば幸いです。