0
votes

I am having a problem with my layout groups and keeping my UI consistent. I have a menu that holds two empty elements Blue and Green boxes bellow that get filled at playtime depending on what the player does. The orange boxes are buttons that may add or remove both purple boxes or orange boxes depending on the button pressed. The Gray, Blue, and Green boxes all have Vertical Layout Group and Content Size fitter (vertical fit set to "Prefered Size"). So from my understanding, all children within these boxes should be stacked vertically and will expand and contract the box as needed.

The problem I am having is that the first time I press a button that adds a purple box and expands the blue box, the green box stays where it is. The second time I press a button the Green box shifts where it should be no matter what gets added to the blue box. Conversely if pressing a button for the first time removes purple boxes and contracts the blue box, the green box stays where it is until I press a button again then shifts where it should be.

enter image description here

If I manually change any of the Gray, Blue, or Green boxes attributes such as anchor pivot or scale the boxes rearrange themselves the way it should be.

Am I doing something wrong with Vertical Layout / Content Size fitter? I hope this is enough to go on. I can provide code if need be. I just don't know exactly what to share.

When the button is pressed call this method.

void AcceptUnlockButtonInput(Exit roomExit) {
    if (PlayerHasKeyForExit(roomExit)) {
        LogAction(roomExit.exitUnlockDescription);
        roomExit.attemptedToExit = false;
        roomExit.attemptedToUnlock = false;
    } else if (roomExit.attemptedToUnlock == false) {
        LogAction(roomExit.exitLockedDescription);
    }

    DisplayActionText();

    foreach(Transform child in inputContainer.transform) {
        Destroy(child.gameObject);
    }

    DisplayInputButtons();
    Canvas.ForceUpdateCanvases();
}

LogActrion() just simply adds a string to a list then DisplayActionTest() reads off that list and creats/adds gameObjects to the container.

public void DisplayActionText() {
    actionLog.ForEach(value => {
        var textObject = Instantiate(displayTextPrefab) as GameObject;
        var textMeshPro = textObject.GetComponentInChildren<TextMeshProUGUI>();
        textMeshPro.text = value;
        textObject.transform.SetParent(outputContainer.transform, false);
    });

    actionLog.Clear();
    Canvas.ForceUpdateCanvases();
}

Simply creats a button for each exit and adds the original method to the buttons listener.

public void DisplayInputButtons() {
    foreach (var exit in Exits) {
            var unlockButton = Instantiate(userActionButton) as GameObject;
            newButton.text = buttonText;
            newButton.transform.SetParent(buttonGroup.transform, false);
            unlockButton.onClick.AddListener(() => AcceptUnlockButtonInput(exit));
    }
}
5

5 Answers

2
votes

I have had the same problem with layout groups. From my understanding, layout groups don't update immediately when you put something in them.

Something what worked for me was:

LayoutRebuilder.ForceRebuildLayoutImmediate(recttransform of the layoutgroup);

You can also try disabling and enabling the layout group component:

IEnumerator UpdateLayoutGroup()
{
layoutGroupComponent.enabled = false;
yield return new WaitForEndOfFrame();
layoutGroupComponent.enabled = true;
}

If enabling and disabling the layoutgroup component doesn't work you can also try this:

IEnumerator UpdateLayoutGroup()
{
yield return new WaitForEndOfFrame();

layoutGroupComponent.enabled = false;

layoutGroupComponent.CalculateLayoutInputVertical();

LayoutRebuilder.ForceRebuildLayoutImmediate(recttransform of the layoutgroup);

layoutGroupComponent.enabled = true;
}
2
votes

I have had similar problems and I found this to be helpful: https://docs.unity3d.com/ScriptReference/Canvas.ForceUpdateCanvases.html

Sometimes when instantiating elements into a gameobject with a vertical layout group, the elements will only get into their right position after updating the canvas.

1
votes

I know there is an issue with the VLG not updating correctly. Changing any of its value after adding/ removing elements forces its layout to resize and may fix your issue (at least that's the way I do it).

Can you try : - Add/remove your element - VerticalLayoutLement.spacing += Random.Range(-1,1);

To be sure it rescales properly i usually do it in a coroutine with an"yield return new WaitForNewFrame()" before the spacing part.

1
votes

After trying a lot of tricks that I found on the web I felt a little frustrated because none of them worked for me. When I ran the scene all the elements in the vertical layout looked messed, but when I clicked on Pause and Continue they magically fitted perfectly in the right position, so I wrote this simple funcion to emulate it, that works fine in my app:

IEnumerator RefreshPrefab(GameObject prefabInstance)
{
    prefabInstance.SetActive(false);
    yield return new WaitForSeconds(0.1f);
    prefabInstance.SetActive(true);
}

And call it sending as parameter the parent prefab that contains the layout group:

StartCoroutine(RefreshPrefab(instantiatedPrefab));
1
votes

In my case there were multiple layout groups nested into each other, so the rebuild of the parent layout group did not work. I had to rebuild all in hierarchy, to properly update the layout.

LayoutGroup[] parentLayoutGroups = gameObject.GetComponentsInParent<LayoutGroup>();
foreach (LayoutGroup group in parentLayoutGroups) {
  LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)group.transform);
}