🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
UI

UIはUser Interfaceの略であり、人間がコンピュータとやりとりをするためのシステムです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

2回答

6012閲覧

Unity の HorizontalLayoutGroup で、階層下の全部を含む幅でレイアウトしたい。

sagat64

総合スコア8

UI

UIはUser Interfaceの略であり、人間がコンピュータとやりとりをするためのシステムです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2019/06/16 12:10

編集2019/06/16 12:18

実現したいこと

HorizontalLayoutGroup でオブジェクトを横に並べる際に
「階層下すべてのオブジェクトを含む幅」でレイアウトしたいです。

具体的には、添付画像のように
Red -> Yellow -> Pink の階層がある状態で
「Red を Yellow, Pink をすべて含んだ幅」で並べたいです。

※「階層下すべてを含む PreffedSize」のイメージです。

可能でしょうか?

イメージ説明

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sagat64

2019/06/16 14:31

コメントありがとうございます! >Yellowの両隣の空白と、Pinkの左端の空白は必須ですか? 必須です。 >その場合、ここはどのように設けるつもりですか? 任意のサイズに対応できるものとします。 >親子関係はどこまで変えていいですか? 変えないでください。
sakura_hana

2019/06/17 01:14

もし階層内に他のオブジェクトを追加するのも不可だと、そもそも空白が作れないように思います。 空白を作るとしたらLayoutGroupのPaddingを設定するか、同階層に空オブジェクトを配置するかになりますが、Paddingは横幅自体が縮まるのでダメ。空オブジェクトを配置した場合でもYellowの右側の空白が作れません(出来たとしても「Yellowの幅=Yellowの左端からPinkの左端までを司る空白の幅+Pinkの幅」となる為)。 個人的には「Redの左辺の位置」と「Pinkの右辺の位置」を算出してその差を計算で求めるしか無いように思います。
sagat64

2019/06/18 06:04

コメントありがとうございます! >もし階層内に他のオブジェクトを追加するのも不可だと、そもそも空白が作れないように思います。 Red, Yellow, Pink はただの親子関係です(=レイアウトを使いません)。 →RectTransform で移動して、空白を作ります。 "Red, Yellow, Pink のサイズ(=階層下すべてを含んだサイズ)で HorizontalLayout をするにはどうしたらよいか。" というのが今回の主旨です。
sakura_hana

2019/06/18 06:55

了解です、誤解してました。 とすると結局のところ「頑張って自分で計算する(RectTransfromのパラメータを参照、子孫要素の中で一番右端の辺の位置と、赤の左辺の位置を算出、その差を求める)」しか私には思い付かないのですが、その方法が分からないということでいいんでしょうか。(多分出来るんじゃない?レベルなので私も確実ではないです。こちらの手法についてはリファレンス等見ながら自分で調べてもらった方が早いかもしれません)
sagat64

2019/06/23 09:46

>子孫要素の中で一番右端の辺の位置と、赤の左辺の位置を算出、その差を求める はい。そのやり方がよくわからず、壁になっていました。。 コメントありがとうございます! m(_ _)m
guest

回答2

0

ベストアンサー

おそらく順当な方法は、sakura_hanaさんの案のように各種レイアウトグループを入れ子にして、スペーサー役の不可視オブジェクトを適切に配置して目的のレイアウトになるようにする手ではないかと思います。
それでもあえて「階層構造は変えない」という縛りでやってみようかと思い実験してみました。

階層構造を変えないことに加えて、並べたい色板の見た目が勝手に変化しないようにするためRectTransformのWidthとHeight...つまりsizeDeltaがレイアウト処理の前後で変化してはいけないという縛りも必要かと思います。
ですが、レイアウトグループはControl Child Sizeがオフの場合には「子オブジェクトのRectTransformsizeDeltaを見てレイアウトを決定する」、オンの場合には「子オブジェクトの持つレイアウトエレメントが提示したサイズを見てレイアウトを決定し、さらにその子オブジェクトのRectTransformsizeDeltaをそのサイズに書き換える」といったような動作をするようでした。
Control Child Sizeがオフだと直接の子のsizeDeltaしか見ないためRedの子であるYellow以下の存在が無視されてしまうし、かといってオンにした上でRedにYellow以下の子孫を考慮したレイアウトサイズを提出するレイアウトエレメントを付けたとしても、併せてsizeDeltaを書き換えてしまうという特性のために、Redが伸び縮みしてしまうことになるでしょう。

nabyさんのContent Size Fitterを使う案もいいかもしれませんが、やはり「階層構造を変えない」という縛りが効いてきて、Content Size FitterもsizeDeltaを書き換える特性があるので一筋縄にはいかなそうな気がします。

いろいろ触ってみてとりあえず出てきた案としては...

  1. レイアウトグループの直接の子であるGreen、Red、Purpleには「自身を含む子孫階層のRectTransformを囲むバウンディングボックスのサイズをMinおよびPreferredとして提出するレイアウトエレメント」を付ける。孫、ひ孫であるYellowやPinkはそのままでよい。
  2. Layoutには「Control Child Sizeがオンの状態と同様の動作をした上で、後で子オブジェクトのsizeDeltaを元のサイズに戻すレイアウトグループ」を付ける。

といったものです。

子オブジェクトに付けるレイアウトエレメントとして下記スクリプトを用意し...

C#

1using UnityEngine; 2using UnityEngine.UI; 3#if UNITY_EDITOR 4using UnityEditor; 5#endif 6 7[AddComponentMenu("Layout/Union Layout Element")] 8public class UnionLayoutElement : LayoutElement 9{ 10 private static readonly Vector3[] Corners = new Vector3[4]; 11 12 public Rect UnionRect { get; private set; } 13 14 /// <inheritdoc /> 15 public override float minWidth => this.UnionRect.width; 16 17 /// <inheritdoc /> 18 public override float preferredWidth => this.UnionRect.width; 19 20 /// <inheritdoc /> 21 public override float minHeight => this.UnionRect.height; 22 23 /// <inheritdoc /> 24 public override float preferredHeight => this.UnionRect.height; 25 26 /// <inheritdoc /> 27 public override void CalculateLayoutInputHorizontal() 28 { 29 this.UpdateSize(); 30 } 31 32 /// <inheritdoc /> 33 protected override void OnEnable() 34 { 35 base.OnEnable(); 36 this.UpdateSize(); 37 } 38 39 private void UpdateSize() 40 { 41 // 自身のrectを子孫のrectの四隅をすべて含む大きさに広げ、それをunionRectとする 42 // 子孫は回転しているかもしれないので、最大最小の二隅ではなく四隅を使うことにしました 43 var rectTransform = this.transform as RectTransform; 44 var unionRect = rectTransform.rect; 45 var childCount = rectTransform.childCount; 46 for (var i = 0; i < childCount; i++) 47 { 48 var childRectTransform = rectTransform.GetChild(i) as RectTransform; 49 if (childRectTransform != null) 50 { 51 unionRect = EncapsulateRecursively(unionRect, rectTransform, childRectTransform); 52 } 53 } 54 55 this.UnionRect = unionRect; 56 } 57 58 // pointが領域内に収まるようにrectを拡張する 59 // (Bounds.Encapsulateと似たようなことをする) 60 private static Rect Encapsulate(Rect rect, Vector2 point) 61 { 62 rect.min = Vector2.Min(rect.min, point); 63 rect.max = Vector2.Max(rect.max, point); 64 return rect; 65 } 66 67 // rectTransformおよびその子孫から再帰的に四隅を取り出し、それら座標を 68 // rootRectTransformの座標系に直してrectに結合する 69 private static Rect EncapsulateRecursively(Rect rect, RectTransform rootRectTransform, RectTransform rectTransform) 70 { 71 var childCount = rectTransform.childCount; 72 for (var i = 0; i < childCount; i++) 73 { 74 var childRectTransform = rectTransform.GetChild(i) as RectTransform; 75 if (childRectTransform != null) 76 { 77 rect = EncapsulateRecursively(rect, rootRectTransform, childRectTransform); 78 } 79 } 80 81 rectTransform.GetWorldCorners(Corners); 82 for (var i = 0; i < 4; i++) 83 { 84 rect = Encapsulate(rect, rootRectTransform.InverseTransformPoint(Corners[i])); 85 } 86 87 return rect; 88 } 89} 90 91#if UNITY_EDITOR 92[CustomEditor(typeof(UnionLayoutElement), true)] 93[CanEditMultipleObjects] 94public class UnionLayoutElementEditor : Editor 95{ 96 private SerializedProperty ignoreLayout; 97 private SerializedProperty layoutPriority; 98 99 /// <inheritdoc cref="Editor" /> 100 public override void OnInspectorGUI() 101 { 102 this.serializedObject.Update(); 103 var unionLayoutElement = this.target as UnionLayoutElement; 104 EditorGUILayout.PropertyField(this.ignoreLayout); 105 if (!this.ignoreLayout.boolValue) 106 { 107 EditorGUILayout.Space(); 108 EditorGUILayout.LabelField("Min/Preferred Width", unionLayoutElement.minWidth.ToString()); 109 EditorGUILayout.LabelField("Min/Preferred Height", unionLayoutElement.minHeight.ToString()); 110 } 111 112 EditorGUILayout.PropertyField(this.layoutPriority); 113 this.serializedObject.ApplyModifiedProperties(); 114 } 115 116 protected void OnEnable() 117 { 118 this.ignoreLayout = this.serializedObject.FindProperty("m_IgnoreLayout"); 119 this.layoutPriority = this.serializedObject.FindProperty("m_LayoutPriority"); 120 } 121} 122#endif

親オブジェクトに付けるレイアウトグループとして下記スクリプトを用意し...

C#

1using System.Collections.Generic; 2using System.Linq; 3using UnityEngine; 4using UnityEngine.UI; 5 6[AddComponentMenu("Layout/Size Preserving Horizontal Layout Group")] 7public class SizePreservingHorizontalLayoutGroup : HorizontalLayoutGroup 8{ 9 private readonly List<(RectTransform, Vector2, Vector2, Vector2)> children = 10 new List<(RectTransform, Vector2, Vector2, Vector2)>(); 11 12 // スクリーンショットを拝見しますと、Purpleの垂直位置がずれているようです 13 // ですので垂直位置を調整できるようにするべきかと思い、とりあえず以前 14 // https://teratail.com/questions/194411 で申し上げたように 15 // SetLayoutVerticalをオーバーライドして整列を効かなくしました 16 // もし垂直位置固定が必要でしたら、下記オーバーライドは削除してしまってください 17 /// <inheritdoc /> 18 public override void SetLayoutVertical() 19 { 20 } 21 22 /// <inheritdoc /> 23 public override void SetLayoutHorizontal() 24 { 25 // 子オブジェクトにUnionLayoutElementを付け、sizeDelta、pivotと 26 // レイアウト矩形の左上に対してRectTransformの左上がどれだけ 27 // ずれているかを表すオフセットを覚えておく 28 this.children.Clear(); 29 var childCount = this.transform.childCount; 30 for (var i = 0; i < childCount; i++) 31 { 32 var child = this.transform.GetChild(i) as RectTransform; 33 if (child == null) 34 { 35 continue; 36 } 37 38 var unionLayoutElement = child.GetComponent<UnionLayoutElement>(); 39 if (unionLayoutElement == null) 40 { 41 unionLayoutElement = child.gameObject.AddComponent<UnionLayoutElement>(); 42 } 43 if (unionLayoutElement.ignoreLayout) 44 { 45 continue; 46 } 47 var sizeDelta = child.sizeDelta; 48 var pivot = child.pivot; 49 var rectTopLeft = (Vector2.up - pivot) * sizeDelta; 50 var unionRect = unionLayoutElement.UnionRect; 51 var unionRectTopLeft = new Vector2(unionRect.xMin, unionRect.yMax); 52 var offset = rectTopLeft - unionRectTopLeft; 53 this.children.Add((child, sizeDelta, pivot, offset)); 54 } 55 56 foreach (var (child, _, _, offset) in this.children) 57 { 58 // pivotは一旦左端に変更する 59 var pivot = child.pivot; 60 pivot.x = 0.0f; 61 child.pivot = pivot; 62 63 // もし子オブジェクトが孫オブジェクトを持っていれば 64 // それらの位置をオフセット分だけずらしておく 65 var grandChildCount = child.childCount; 66 for (var i = 0; i < grandChildCount; i++) 67 { 68 var grandChild = child.GetChild(i) as RectTransform; 69 if (grandChild != null) 70 { 71 grandChild.anchoredPosition += offset; 72 } 73 } 74 } 75 76 // Control Child Sizeをオンにしてからレイアウトを決定させ... 77 this.childControlWidth = true; 78 base.SetLayoutHorizontal(); 79 this.childControlWidth = false; 80 81 // レイアウトに修正を加える 82 this.m_Tracker.Clear(); 83 foreach (var (child, sizeDelta, pivot, offset) in this.children) 84 { 85 this.m_Tracker.Add( 86 this, 87 child, 88 DrivenTransformProperties.Anchors | DrivenTransformProperties.AnchoredPositionX); 89 90 // pivotの変位分だけ位置を調整し... 91 child.anchoredPosition += (pivot - child.pivot) * sizeDelta; 92 93 // sizeDelta、pivotを復元し... 94 child.sizeDelta = sizeDelta; 95 child.pivot = pivot; 96 97 // 孫オブジェクトの配置を復元する 98 child.anchoredPosition += Vector2.Scale(offset, Vector2.right); 99 var grandChildCount = child.childCount; 100 for (var i = 0; i < grandChildCount; i++) 101 { 102 var grandChild = child.GetChild(i) as RectTransform; 103 if (grandChild != null) 104 { 105 grandChild.anchoredPosition -= offset; 106 } 107 } 108 } 109 } 110 111 /// <inheritdoc /> 112 protected override void OnEnable() 113 { 114 this.childControlWidth = false; 115 base.OnEnable(); 116 } 117}

下図のようにLayoutにSizePreservingHorizontalLayoutGroupをアタッチしました。なお、レイアウト処理時に子オブジェクトがUnionLayoutElementを持っていなかった場合、その時にアタッチすることにしました。ご参考になりますでしょうか?

結果

投稿2019/06/21 16:51

Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sagat64

2019/06/23 09:43

パーフェクトな回答です。 ありがとうございます。 >// 自身のrectを子孫のrectの四隅をすべて含む大きさに広げ、それをunionRectとする はい。こういうことがしたかったのです。 ・・が、よくわからず困っていました。 uGUI の拡張はこうやってやるんですね。 >// 子孫は回転しているかもしれないので、最大最小の二隅ではなく四隅を使うことにしました 回転にも対応できるとは。。 >// pointが領域内に収まるようにrectを拡張する Vector2.Min, Max なんてできるんですね。 >// ですので垂直位置を調整できるようにするべきかと思い >// SetLayoutVerticalをオーバーライドして整列を効かなくしました そのとおりです。ありがとうございます。 自分で、uGUI をひととおり使っていましたが・・ なるほど拡張はこうやってやるんですね。 コード書いて動かして、 GIF アニメまで付けて回答していただけるのは かなりの手間だと推察しました。 とても勉強になりました。 心から感謝いたします。
guest

0

Content Size Fitterで子階層からサイズの更新を受けることができます。

https://docs.unity3d.com/ja/current/Manual/script-ContentSizeFitter.html

投稿2019/06/16 14:24

naby

総合スコア126

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問