Optimizing Unity UI

This video has some excellent tips and it helped me make some simple optimizations to my UI in Space Engineers Calculator.

The short story:
I added sub-canvases and replaced Unity’s vertical layout group with my own implementation.

The long story:
My application consists of mostly Unity UI. Most of the application runs reasonably well except for the item list.

ItemList
The item list

The item list includes approximately 300 items which the user can filter by the category drop down or a text search box. When one of the larger categories like “Large Blocks” is selected, the list can be choppy/slow when the user is scrolling. I’ve tried a few times to increase the performance but it never felt as smooth as I would like.

Problem 1: My application was built with one canvas.
Why is this a problem?: Whenever a UI component is set to dirty it’ll sometimes tell its canvas to update everything.
Solution: Group related UI components together. If you have several UI components that update together, keep them on the same canvas. If you have a lot of static UI content (e.g., static background) keep it in another canvas. This way you don’t waste resources by updating your static background just because your dynamic content is part of the same canvas.

My application is split up between several main panels so logically I created a sub-canvas for each of these panels and I created one specifically for my item list.

Problem 2: I use Unity’s (Horizontal|Vertical) layout groups everywhere.
Why is this a problem?: In the video it is recommended not to use them. Watch the video for the full reason.
Solution: I removed the vertical layout group on my 300 item list, replaced it with my own version and immediately saw an improvement. I still have other UI elements using Unity’s layout groups but they don’t suffer the same performance issues as the item list did so I’m less inclined to update them.

My vertical layout code has two methods.

  1. Get the children of this game object.
  2. Layout the children whenever it needs to be updated. I’m utilizing Unity’s method: SetInsetAndSizeFromParentEdge to add the children starting from the top of the parent. It’s simple but it works.
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
namespace scg
{    
    public class LayoutVertical : MonoBehaviour
    {
        [SerializeField]
        List<RectTransform> children;
        RectTransform transform;

        [SerializeField]
        float childrenSize = 100;

        [SerializeField]
        float padding = 0;

        [SerializeField]
        bool adjustParentSize = false;

        [SerializeField]
        CanvasScaler canvasScaler;
        
        void Start()
        {
            transform = GetComponent<RectTransform>();
        }

        /// <summary>
        /// Called to collect all the children in the list.
        /// </summary>
        public void GetChildrenRects()
        {
            if (children == null)
            {
                children = new List<RectTransform>();
            }
            if (children.Count > 0)
            {
                children.Clear();
            }
            if (transform == null)
            {
                Debug.Log("Parent transform is null.");
            }
            else
            {
                for (int i = 0; i < transform.childCount; i++)
                {
                    children.Add(transform.GetChild(i).GetComponent<RectTransform>());
                }
            }
        }

        /// <summary>
        /// This is called when we need to refresh the positioning of the children.
        /// </summary>
        public void CalculatePosition()
        {
            if (children == null || children.Count <= 0)
            {
                Debug.Log("No Children to calculate position for.");
                return;
            }
            float offset = 0;
            foreach (RectTransform child in children)
            {
                if (child.gameObject.activeSelf)
                {

                    child.sizeDelta = new Vector2(transform.rect.width, child.rect.height);
                    child.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Top, offset, childrenSize);
                    offset += childrenSize + padding;
                }
            }
            if (adjustParentSize)
            {
                transform.sizeDelta = new Vector2(0, offset);
            }            
        }
    }
}

Remember that Unity’s UI source is available so if you ever wonder how they do something or want to change the behavior of a UI component you can.
Link: Unity-Technologies / UI – Bitbucket

Here’s a link to a guide for optimizing Unity UI (5.3).

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s