欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Unity 知识点 关于ForceRebuildLayoutImmediate的坑

程序员文章站 2022-07-02 09:59:19
...

萧熊猫先生于一个月前辞职了,为了完成心中的梦想(做个游戏),开始每天在房间里努力做着自己的游戏。(其实每天都在玩)

今天主要讲两个问题。众所周知,策划都是智障。(等等,我现在也算半个策划了……)​经常会有在自适应的layout里嵌套layout,然后在子级的文本和后面的图还要自适应,以及更多的自适应的情况……
Unity 知识点 关于ForceRebuildLayoutImmediate的坑
Unity 知识点 关于ForceRebuildLayoutImmediate的坑
如果是自己算正确的位置,显然不科学 ,费脑子不说,效率很低,debug过程会让人崩溃。unity官方想到了这个情况,所以提供了LayoutRebuilder.ForceRebuildLayoutImmediate(RectTransform)方法。可以让强制重新布局。
Unity 知识点 关于ForceRebuildLayoutImmediate的坑

不过unity是个铁憨憨啊,有的方法真的只是够用就行,就像5.0之后就再也没有重搞过编辑器工具一样……(美其名曰,重做这玩意有啥用,够用就行了)
这个方法真的就只是重新计算子组件的位置。并不会触发子组件内部的重新布局。

所以,要想对付这样的自适应里的自适​应,两种解决办法。
一个是每个需要重新布局的组件都执行一下方法,这样的话,有点繁琐了。

public void Test()
{
    LayoutRebuilder.ForceRebuildLayoutImmediate(layout1.GetComponent<RectTransform>());
    LayoutRebuilder.ForceRebuildLayoutImmediate(layout2.GetComponent<RectTransform>());
    LayoutRebuilder.ForceRebuildLayoutImmediate(layout3.GetComponent<RectTransform>());
}

另一个是对这个组件递归查找,所有带有LayoutGroup的组件都执行一遍重新布局,不过这就对消耗有点要求了。(毕竟GetComponent的消耗也不怎么好)

public void Test()
{
    List<Transform> transList = GetAllChildsByComponent(root.transform);
    foreach (Transform trans in transList)
    {
        RectTransform rectTrans = root.GetComponent<RectTransform>();
        if (null != rectTrans)
        {
            LayoutRebuilder.ForceRebuildLayoutImmediate(rectTrans);
        }
    }
}

public static List<Transform> GetAllChildsByComponent(Transform trans, List<Transform> transList = null)
{
    if (null == transList)
    {
        transList = new List<Transform>();
    }
    if (null != trans)
    {
        for (int i = 0; i < trans.childCount; i++)
        {
            Transform child = trans.GetChild(i);
            if (null != child.GetComponent<LayoutGroup>())
            {
                transList.Add(child);
            }
            GetAllChildsByComponent(child, transList);
        }
    }
    return transList;
}

当你搞完了这些,想试一下,发现还是没刷。(博主是不是骗人,直接埋了吧)​
……
这就涉及到第二个问题了,如果在你的组件的顶点有变化时就重新布局,可能不会起效。
以下为猜想:组件的content size fitter是在同一帧重新计算的顶点,而​本来应该触发的rebuild是在同样的下一帧。这时候显示不正确,但是rebuild认为逻辑上已经正确了。(没有找到文献或讲解,纯属个人猜想,如果有知情人,希望能告诉我,让我在这个问题上死个明白……)

这个的解决方法更加简单。也是两个。
第一个,有个前提,项目中有计时器管理器,延迟个一帧再执行就好了。
这个是我项目里的计时器,功能上是第二帧才会执行。

public void Test()
{
    Timer timer = new Timer("Test");
    if (null != timer)
    {
        timer.SetDelayer(0.01, () =>
        {
            LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>());
        });
    }
}

如果没有,那就用协程WaitForEndOfFrame或者在Update里​,确保在再下一帧执行就好了。
Update的方法在这里,不是万能,只是一种解决思路。

void Update()
 {
    if (null != layout)
    {
        if (!isRun)
        {
            isRun = !isRun;
        }
        else
        {
            LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>());
            isRun = false;
            layout = null;
        }
    }
}

以及协程的解决思路

public void Test()
{
    StartCoroutine(TestEnumerator(layout.GetComponent<RectTransform>()));
}

private IEnumerator TestEnumerator(RectTransform rectTrans)
{
    yield return new WaitForEndOfFrame();
    LayoutRebuilder.ForceRebuildLayoutImmediate(rectTrans);
}

以上为我今天花了三个小时才定位又解决的问题(其中包括扩展计时器和硬造出一个协程管理器),真是耽误我去补庆余年。希望大家看到这个坑,能绕着走。

新年快乐~