【问题标题】:Unity 3D: Vertical Layout Group not placing elements where they should beUnity 3D:垂直布局组未将元素放置在应有的位置
【发布时间】:2020-05-28 18:52:41
【问题描述】:

我的布局组和保持 UI 一致时遇到问题。我有一个菜单,其中包含两个空元素蓝色和绿色框,它们会在播放时根据玩家的行为填充。橙色框是可以添加或删除紫色框或橙色框的按钮,具体取决于按下的按钮。灰色、蓝色和绿色框都具有垂直布局组和内容大小调整器(垂直适合设置为“首选大小”)。因此,据我了解,这些盒子中的所有孩子都应该垂直堆叠,并根据需要扩展和收缩盒子。

我遇到的问题是,当我第一次按下添加紫色框并展开蓝色框的按钮时,绿色框保持在原来的位置。我第二次按下按钮时,无论将什么添加到蓝色框中,绿色框都会移动到它应该在的位置。相反,如果第一次按下按钮会移除紫色框并缩小蓝色框,则绿色框会保持在原位,直到我再次按下按钮然后移动到它应该在的位置。

如果我手动更改任何灰色、蓝色或绿色框属性,例如锚点枢轴或缩放框,它们会按照应有的方式重新排列。

我在垂直布局/内容大小调整器方面做错了吗?我希望这足以继续下去。如果需要,我可以提供代码。我只是不知道具体要分享什么。

当按钮被按下时调用此方法。

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() 只是简单地将一个字符串添加到列表中,然后DisplayActionTest() 读取该列表并将游戏对象创建/添加到容器中。

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();
}

只需为每个退出创建一个按钮,并将原始方法添加到按钮侦听器。

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));
    }
}

【问题讨论】:

    标签: c# unity3d user-interface


    【解决方案1】:

    我在布局组方面遇到了同样的问题。据我了解,当您将某些内容放入其中时,布局组不会立即更新。

    对我有用的是:

    LayoutRebuilder.ForceRebuildLayoutImmediate(recttransform of the layoutgroup);

    您也可以尝试禁用和启用布局组组件:

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

    如果启用和禁用 layoutgroup 组件不起作用,您也可以试试这个:

    IEnumerator UpdateLayoutGroup()
    {
    yield return new WaitForEndOfFrame();
    
    layoutGroupComponent.enabled = false;
    
    layoutGroupComponent.CalculateLayoutInputVertical();
    
    LayoutRebuilder.ForceRebuildLayoutImmediate(recttransform of the layoutgroup);
    
    layoutGroupComponent.enabled = true;
    }
    

    【讨论】:

    • 这些解决方案似乎都不起作用。即使替换正确的方法调用。 this.inputContainer.SetActive(true or false); this.inputContainer.gameObject.GetComponent&lt;VerticalLayoutGroup&gt;().CalculateLayoutInputVertical(); LayoutRebuilder.ForceRebuildLayoutImmediate(this.inputContainer.gameObject.GetComponent&lt;RectTransform&gt;()); 除非这些是错误的方法,我应该使用其他方法。
    【解决方案2】:

    我遇到过类似的问题,我发现这很有帮助: https://docs.unity3d.com/ScriptReference/Canvas.ForceUpdateCanvases.html

    有时在将元素实例化为具有垂直布局组的游戏对象时,元素只会在更新画布后进入正确的位置。

    【讨论】:

    • 这可行,但在我按下按钮后它会稍微调整容器。在大多数情况下应该没问题,但看起来有点奇怪。
    • “杂耍”是什么意思?您能否记录一下按下按钮时发生的情况? (我使用一个名为“ShareX”的程序来录制我的屏幕,然后上传到一个名为“streamable”的网站,以制作像这样的小型录音展示给同事)
    • 哎呀,我的意思是打字。 streamable.com/u4m9j 快结束时,当我按下解锁几次时,按钮组略微向下移动,然后重新弹回。这可能不是问题。如果您没有解锁出口的钥匙,我正在努力禁用该按钮。但我不知道这是否是 UI 现在唯一抖动的地方。
    • 奇怪!您能否详细说明按下按钮时脚本中会发生什么?
    • 好了,我用一些代码编辑了我的问题。我尽量为你浓缩。
    【解决方案3】:

    我知道 VLG 无法正确更新存在问题。添加/删除元素后更改其任何值会强制其布局调整大小并可能解决您的问题(至少我是这样做的)。

    你可以试试: - 添加/删除您的元素 - VerticalLayoutLement.spacing += Random.Range(-1,1);

    为了确保它正确地重新缩放,我通常在协程中使用“yield return new WaitForNewFrame()”在间距部分之前进行。

    【讨论】:

    • 我想过做这样的事情,但我的努力似乎没有奏效。我考虑过更改 Z 轴上的比例,因为这不会影响我的 UI,并且当我手动执行时它可以解决问题。我只是觉得这是一个草率的修复。但如果它有效,它就有效。
    【解决方案4】:

    在尝试了很多我在网上找到的技巧后,我感到有点沮丧,因为它们都不适合我。当我运行场景时,垂直布局中的所有元素看起来都很乱,但是当我点击暂停和继续时,它们神奇地完美地安装在正确的位置,所以我编写了这个简单的函数来模拟它,它在我的应用程序中运行良好:

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

    并将包含布局组的父预制件作为参数发送:

    StartCoroutine(RefreshPrefab(instantiatedPrefab));
    

    【讨论】:

      【解决方案5】:

      更新答案

      在我的例子中,有多个布局组相互嵌套,因此父布局组的重建不起作用。在第一次尝试中,我在层次结构中重建所有内容(旧答案),但遇到了大型列表的性能问题。然后我找到了一种更健壮、更高效的方法(在Answer from Adam-BucknerAuto-Layout Docs 的帮助下):

      1. 只有布局组层次结构的最顶层布局组有内容大小调整器,任何后代都不能有一个
      2. 所有垂直布局组都已选中控制子大小(宽度和高度)、使用子缩放(仅高度)、子力展开(仅宽度)
      3. 当布局受到代码影响时,由于添加/删除元素或设置组件处于活动状态,我使用以下代码(它使用 MarkLayoutForRebuild,可防止多次重新计算节点)

      (从有变化的元素开始,如果元素本身不是一个LayoutGroup,第一行可以省略)

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

      它真的很好用,我不再有元素跳来跳去。据我了解,内容大小调整器控制 RectTransform,但与也想要控制它的父 LayoutGroup 竞争(因此在编辑器中发出警告)。如果只有 LayoutGroups 相互嵌套,算法可以很容易地计算自下而上的大小,因此最底部的布局组从其子级获取它们的大小(由使用子级比例引起),然后由它们的父级布局应用到它组(由控制子大小引起)。 LayoutGroups 不会影响其所在元素的 RectTransform,只会影响其子元素。最后,顶部元素从层次结构中获得一个高度,然后将其应用于自身的内容大小调整器。

      旧的和低效的答案

      在我的例子中,有多个布局组相互嵌套,因此父布局组的重建不起作用。我必须在层次结构中重建所有内容,以正确更新布局。

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

      【讨论】:

      • 这看起来是个不错的解决方案。我还没有测试过,但看起来我可以工作。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多