うにてぃブログ

UnityやUnreal Engineの記事を書いていきます

【Unity】LayoutGroup が重いと言われている理由

LayoutGroup は便利ですが、公式にも重いため最適化するためには利用は避けようと記述されています

Some of the best optimization tips for Unity UI - Unity

理由としては上記リンクに書いている通りですがどういった処理をしているのか見ていきます

OnRectTransformDimensionsChange で Rect の変更を検知して
Root の LayoutGroup であればダーティフラグを立てます

※ 他にも ダーティフラグを立てる処理はあります

Root かどうか判定する際に parent の GetComponent を行います

// LayoutGroup
 
        protected override void OnRectTransformDimensionsChange()
        {
            base.OnRectTransformDimensionsChange();
            if (isRootLayoutGroup)
                SetDirty();
        }
 
        private bool isRootLayoutGroup
        {
            get
            {
                Transform parent = transform.parent;
                if (parent == null)
                    return true;
                return transform.parent.GetComponent(typeof(ILayoutGroup)) == null;
            }
        }

その後 LayoutRebuilder で UI の再構築処理が呼ばれ、そのときに自分の子供にいる LayoutGroup 一覧を取得します

       //LayoutRebuilder

        private void PerformLayoutControl(RectTransform rect, UnityAction<Component> action)
        {
            if (rect == null)
                return;
 
            var components = ListPool<Component>.Get();
            rect.GetComponents(typeof(ILayoutController), components);
            ・
            ・
            ・
                 for (int i = 0; i < rect.childCount; i++)
                    PerformLayoutControl(rect.GetChild(i) as RectTransform, action);
            ・
            ・
            ・
        private void PerformLayoutCalculation(RectTransform rect, UnityAction<Component> action)
        {
            if (rect == null)
                return;
 
            var components = ListPool<Component>.Get();
            rect.GetComponents(typeof(ILayoutElement), components);
            ・
            ・
            ・
                for (int i = 0; i < rect.childCount; i++)
                    PerformLayoutControl(rect.GetChild(i) as RectTransform, action);

その後また LayoutGroup 側で 無視する UI かどうかを判定するために ILayoutIgnorer を取得しています

// LayoutGroup
        public virtual void CalculateLayoutInputHorizontal()
        {
            m_RectChildren.Clear();
            var toIgnoreList = ListPool<Component>.Get();
            for (int i = 0; i < rectTransform.childCount; i++)
            {
                var rect = rectTransform.GetChild(i) as RectTransform;
                if (rect == null || !rect.gameObject.activeInHierarchy)
                    continue;

                rect.GetComponents(typeof(ILayoutIgnorer), toIgnoreList);

まだ他にも GetComponent は呼ばれていますが、UIの再構築のためにこれだけの GetComponent が呼ばれています
そのため要素の数が多ほど 重くなってしまいます

最適化したいのであれば公式に書いてあるとおり自前でレイアウトを構築する処理を必要があります

解決法:可能な限り Layout Group を避ける

プロポーショナルレイアウトを設計するときは、アンカーを使用します。なお、要素数が動的に変化する UI に関しては、 レイアウトを計算する独自のコードを記述し、すべての変更に対してそのコードは使わず、オンデマンドでのみ使用するようにしてください。