実現したいこと
HorizontalLayoutGroup でオブジェクトを横に並べる際に
「階層下すべてのオブジェクトを含む幅」でレイアウトしたいです。
具体的には、添付画像のように
Red -> Yellow -> Pink の階層がある状態で
「Red を Yellow, Pink をすべて含んだ幅」で並べたいです。
※「階層下すべてを含む PreffedSize」のイメージです。
可能でしょうか?
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/06/16 14:31
2019/06/17 01:14
2019/06/18 06:04
2019/06/18 06:55
2019/06/23 09:46
回答2件
0
ベストアンサー
おそらく順当な方法は、sakura_hanaさんの案のように各種レイアウトグループを入れ子にして、スペーサー役の不可視オブジェクトを適切に配置して目的のレイアウトになるようにする手ではないかと思います。
それでもあえて「階層構造は変えない」という縛りでやってみようかと思い実験してみました。
階層構造を変えないことに加えて、並べたい色板の見た目が勝手に変化しないようにするためRectTransform
のWidthとHeight...つまりsizeDelta
がレイアウト処理の前後で変化してはいけないという縛りも必要かと思います。
ですが、レイアウトグループはControl Child Sizeがオフの場合には「子オブジェクトのRectTransform
のsizeDelta
を見てレイアウトを決定する」、オンの場合には「子オブジェクトの持つレイアウトエレメントが提示したサイズを見てレイアウトを決定し、さらにその子オブジェクトのRectTransform
のsizeDelta
をそのサイズに書き換える」といったような動作をするようでした。
Control Child Sizeがオフだと直接の子のsizeDelta
しか見ないためRedの子であるYellow以下の存在が無視されてしまうし、かといってオンにした上でRedにYellow以下の子孫を考慮したレイアウトサイズを提出するレイアウトエレメントを付けたとしても、併せてsizeDelta
を書き換えてしまうという特性のために、Redが伸び縮みしてしまうことになるでしょう。
nabyさんのContent Size Fitterを使う案もいいかもしれませんが、やはり「階層構造を変えない」という縛りが効いてきて、Content Size FitterもsizeDelta
を書き換える特性があるので一筋縄にはいかなそうな気がします。
いろいろ触ってみてとりあえず出てきた案としては...
- レイアウトグループの直接の子であるGreen、Red、Purpleには「自身を含む子孫階層の
RectTransform
を囲むバウンディングボックスのサイズをMinおよびPreferredとして提出するレイアウトエレメント」を付ける。孫、ひ孫であるYellowやPinkはそのままでよい。 - 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
総合スコア10811
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/06/23 09:43
0
Content Size Fitterで子階層からサイズの更新を受けることができます。
https://docs.unity3d.com/ja/current/Manual/script-ContentSizeFitter.html
投稿2019/06/16 14:24
総合スコア126
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。