Search Unity

"ForceRebuildLayoutImmediate" for all children of an object starting from the baby grandchildren up?

Discussion in 'UGUI & TextMesh Pro' started by mikejm_, Nov 14, 2021.

  1. mikejm_

    mikejm_

    Joined:
    Oct 9, 2021
    Posts:
    346
    I have been using a lot of Content Size Fitters and Horizontal/Vertical Layout Groups in my design. They work wonderfully even if you nest them through multiple levels of parent/children.

    The only tricky thing I figured out through trial and error is if you want multiple layers of these things to build and arrange right from the first frame, you must manually run a series of commands to ForceRebuildLayoutImmediate on them in sequential order starting from the smallest children up to the biggest parent. Otherwise there is often 1-2 frames where they are trying to arrange themselves and will jump around a bit.

    So for example if you had 4 GameObjects all of which had these types of elements on them in a hierarchy like this:

    Code (csharp):
    1. PARENT
    2. |------CHILD_A
    3. |------CHILD_B
    4. |--------------GRANDCHILD_A
    You would want to run on Start once all are instantiated:

    Code (csharp):
    1. LayoutRebuilder.ForceRebuildLayoutImmediate(GRANDCHILD_A.GetComponent<RectTransform>());
    2. LayoutRebuilder.ForceRebuildLayoutImmediate(CHILD_B.GetComponent<RectTransform>());
    3. LayoutRebuilder.ForceRebuildLayoutImmediate(CHILD_A.GetComponent<RectTransform>());
    4. LayoutRebuilder.ForceRebuildLayoutImmediate(PARENT.GetComponent<RectTransform>());
    or equally:

    Code (csharp):
    1. LayoutRebuilder.ForceRebuildLayoutImmediate(GRANDCHILD_A.GetComponent<RectTransform>());
    2. LayoutRebuilder.ForceRebuildLayoutImmediate(CHILD_A.GetComponent<RectTransform>());
    3. LayoutRebuilder.ForceRebuildLayoutImmediate(CHILD_B.GetComponent<RectTransform>());
    4. LayoutRebuilder.ForceRebuildLayoutImmediate(PARENT.GetComponent<RectTransform>());
    The main point is all children must be solved before a parent is solved, and thus all generations relative to PARENT must be solved in parallel.

    As I figured this out, I am wasting my time manually doing this and it seems there should be an easy or at least not too logically challenging way to do this automatically.

    All children should have RectTransforms. So then you could build an array of all children, and check to see which generation they are by seeing their relationship to PARENT, then solve each generation one at a time:

    You would have to build the script with a predetermined "depth" to solve. So if you have a "3" depth, it would look for all greatgrandchildren of PARENT and solve those first, then grandchildren of PARENT and solve, then children and solve, then finally solve PARENT last.

    So something like this might work:
    Code (csharp):
    1.  
    2. List<GameObject> grandkids = new List<GameObject>();
    3. List<GameObject> kids = new List<GameObject>();
    4. GameObject[] gameObjectArray = Parent.GetComponentsInChildren<RectTransform>().gameObject;
    5. for (int i=0; i<gameObjectArray.Length;i++){
    6.      if (gameObjectArray[i].transform.parent.parent.gameObject != null) {
    7.         if (gameObjectArray[i].transform.parent.parent.gameObject == PARENT){
    8.             grandkids.Add(gameObjectArray[i]);
    9.         }
    10.     }
    11.      else if (gameObjectArray[i].transform.parent.gameObject != null) {
    12.         if (gameObjectArray[i].transform.parent.gameObject == PARENT){
    13.             kids.Add(gameObjectArray[i]);
    14.         }
    15.     }
    16. }
    17. for (int i=0; i<grandkids.Count;i++){
    18.      LayoutRebuilder.ForceRebuildLayoutImmediate(grandkids[i].GetComponent<RectTransform>());
    19. }
    20. for (int i=0; i<kids.Count;i++){
    21.     LayoutRebuilder.ForceRebuildLayoutImmediate(kids[i].GetComponent<RectTransform>());
    22. }
    23. LayoutRebuilder.ForceRebuildLayoutImmediate(PARENT.GetComponent<RectTransform>());
    24.  
    Thus you can solve the lists of children by running ForceRebuildLayoutImmediate in order from youngest generation to oldest.

    I think that probably solves it. The only downside is you have to hardcode the depth of how many generations it sorts through. It would be even better if you could numerically specify how many generations it goes through like by an int you feed into the function.

    Any other thoughts or ideas? How would you do this? Thanks.
     
    Last edited: Nov 14, 2021
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,572
    Unless I'm missing something, assuming you're using uGui, I believe you shouldn't need to do this.

    I've been using 3 to 5 levels deep layouts in projects, this kind of thing is pretty much never needed.

    What you do is that you slap Vertical Layout Group or Horizontal Layout Group onto EVERYTHING, and place Layout Component on every child object.

    For example:
    upload_2021-11-14_11-52-30.png


    In this scenario "Root Layout" has Vertical Layout Group with "Control Child Size" and "Child Force Expand"
    upload_2021-11-14_11-51-24.png

    Its children are empty VSpan with settings like this:
    upload_2021-11-14_11-53-14.png
    A component with HLayout, and another VSpan.

    HLayout has two empties acting as spanner, and a child with VLayout, which just has 3 buttons, with Layout Component on each one of them.

    This thing will pretty much resize itself automatically, doesn't need any scripts, and you can specify minimum size to prevent excessive shrinking of some components.
    --------
    If that's not your situation then perhaps you might want to explain why you needed to force rebuild in the first place.
     
  3. mikejm_

    mikejm_

    Joined:
    Oct 9, 2021
    Posts:
    346
    The issue is not that it won't resize itself correctly.

    What I'm describing will not change the final positions or layouts of the elements.

    The issue is when you nest a few layers, slow your UI down to a very slow frame rate like 1 fps and you may see as I did in certain structures you have a 1 frame lag before everything is correctly positioned without doing the manual layout rebuild in order. During the lag frame not all elements will be correctly positioned.

    Doing the manual layout rebuild in order from children up on start solves this. There is no longer a "lag frame".

    I notice the lag happens once you have 2-3 layers deep of arrangements and at least one or two text elements at the most child level with control child size activated for them so their own boxes are restricted to the text size.
     
  4. mikejm_

    mikejm_

    Joined:
    Oct 9, 2021
    Posts:
    346
    "place Layout Component on every child object."

    Oh I didn't try that. I'll try that and see if it works. Thanks.
     
  5. mikejm_

    mikejm_

    Joined:
    Oct 9, 2021
    Posts:
    346
    Tried it still doesn't fix the problem. The first frame things are not where they should be and only on the second frame do they correctly position. I will try writing my script to simplify the necessary rebuild process to prevent this. If I have some time I'll share a minimum example that demonstrates the problem.
     
  6. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,572
    You'll need to provide an example if you want people to look into it.
     
  7. mikejm_

    mikejm_

    Joined:
    Oct 9, 2021
    Posts:
    346
    Yeah I will perhaps later. I didn't know this was something perhaps only I'm experiencing or seeing. I wrote the rebuilding script roughly as I described above to rebuild the generations and it's working just fine to solve it for now at least. I feed it an object and depth level and it works through to rebuild from children up so no bad positioned first frame. Least of my problems now then. I'll come back to it if I have time later. Thanks.
     
  8. mikejm_

    mikejm_

    Joined:
    Oct 9, 2021
    Posts:
    346
    FYI, you might be curious just to check out this thread:

    https://forum.unity.com/threads/content-size-fitter-refresh-problem.498536/

    I found numerous people who experienced the same as me and suggested some other script solutions. The solution by look001 seems good so I saved it though my own script is working just fine as well.