Unity 编辑器ScrollView滚动卡顿优化

原因

在使用 Unity 开发游戏的时候,经常会需要用到数据配置,方式可能是CSV、JSON等等。为了可以方便地查看修改数据,通常使用ScrollView实现在 Unity 编辑器里面以列表的形式查看数据。

当数据量大的时候,滚动视图会发现卡顿不断,测试代码如下:

using UnityEditor;
using UnityEngine;

public class TestListView : EditorWindow
{
    [MenuItem("Tool/Test")]
    private static void Init()
    {
        GetWindow<TestListView>();
    }

    private const int s_RowCount = 400;
    private const int s_ColCount = 30;

    private float m_RowHeight = 18f;
    private float m_ColWidth = 52f;
    private Vector2 m_ScrollPosition;

    void OnGUI()
    {
        OnDrawListView2();
    }

    private void OnDrawListView()
    {
        m_ScrollPosition = EditorGUILayout.BeginScrollView(m_ScrollPosition);
        for (int i = 0; i < s_RowCount; i++)
        {
            EditorGUILayout.BeginHorizontal();
            for (int j = 0; j < s_ColCount; j++)
            {
                EditorGUILayout.LabelField((i * 100 + j).ToString(), GUILayout.Width(m_ColWidth));
            }
            EditorGUILayout.EndHorizontal();
        }
        EditorGUILayout.EndScrollView();
    }
}

鼠标拖动滚动条滚动的时候,可以明显发现滚动条卡顿延迟跟着鼠标动:

解决

优化的思路就是只绘制当前可视的区域,自 Unity 5.6 开始已经提供TreeView控件,自带支持大数据集,
详见https://docs.unity3d.com/560/Documentation/ScriptReference/IMGUI.Controls.TreeView.BuildRoot.html

如果还没使用 Unity 5.6,那么可以参考它的实现方式。将Layout自动布局方式改成给定矩形来绘制,这样方便知道每行的高度和总的内容高度,再根据滚动条的坐标来计算获取当前显示的起始行和结束行,只绘制需要显示的行内容。

代码修改如下:

using UnityEditor;
using UnityEngine;

public class TestListView : EditorWindow
{
    [MenuItem("Tool/Test")]
    private static void Init()
    {
        GetWindow<TestListView>();
    }

    private const int s_RowCount = 400;
    private const int s_ColCount = 30;

    private float m_RowHeight = 18f;
    private float m_ColWidth = 52f;
    private Vector2 m_ScrollPosition;

    void OnGUI()
    {
        OnDrawListView2();
    }

    private void OnDrawListView2()
    {
        Rect totalRect = new Rect(0, 0, position.width, position.height);
        Rect contentRect = new Rect(0, 0, s_ColCount * m_ColWidth, s_RowCount * m_RowHeight);
        m_ScrollPosition = GUI.BeginScrollView(totalRect, m_ScrollPosition, contentRect);

        int num;
        int num2;
        GetFirstAndLastRowVisible(out num, out num2, totalRect.height);
        if (num2 >= 0)
        {
            int numVisibleRows = num2 - num + 1;
            IterateVisibleItems(num, numVisibleRows, contentRect.width, totalRect.height);
        }

        GUI.EndScrollView(true);
    }

    /// <summary>
    /// 获取可显示的起始行和结束行
    /// </summary>
    /// <param name="firstRowVisible">起始行</param>
    /// <param name="lastRowVisible">结束行</param>
    /// <param name="viewHeight">视图高度</param>
    private void GetFirstAndLastRowVisible(out int firstRowVisible, out int lastRowVisible, float viewHeight)
    {
        if (s_RowCount == 0)
        {
            firstRowVisible = lastRowVisible = -1;
        }
        else
        {
            float y = m_ScrollPosition.y;
            float height = viewHeight;
            firstRowVisible = (int)Mathf.Floor(y / m_RowHeight);
            lastRowVisible = firstRowVisible + (int)Mathf.Ceil(height / m_RowHeight);
            firstRowVisible = Mathf.Max(firstRowVisible, 0);
            lastRowVisible = Mathf.Min(lastRowVisible, s_RowCount - 1);
            if (firstRowVisible >= s_RowCount && firstRowVisible > 0)
            {
                m_ScrollPosition.y = 0f;
                GetFirstAndLastRowVisible(out firstRowVisible, out lastRowVisible, viewHeight);
            }
        }
    }

    /// <summary>
    /// 迭代绘制可显示的项
    /// </summary>
    /// <param name="firstRow">起始行</param>
    /// <param name="numVisibleRows">总可显示行数</param>
    /// <param name="rowWidth">每行的宽度</param>
    /// <param name="viewHeight">视图高度</param>
    private void IterateVisibleItems(int firstRow, int numVisibleRows, float rowWidth, float viewHeight)
    {
        int i = 0;
        while (i < numVisibleRows)
        {
            int num2 = firstRow + i;
            Rect rowRect = new Rect(0f, (float)num2 * m_RowHeight, rowWidth, m_RowHeight);
            float num3 = rowRect.y - m_ScrollPosition.y;
            if (num3 <= viewHeight)
            {
                Rect colRect = new Rect(rowRect);
                colRect.width = m_ColWidth;

                for (int j = 0; j < s_ColCount; j++)
                {
                    EditorGUI.LabelField(colRect, (num2 * 100 + j).ToString());
                    colRect.x += colRect.width;
                }
            }
            i++;
        }
    }
}

再次鼠标拖动滚动条滚动的时候,可以明显发现滚动条流畅许多:

编辑时的问题

因为不是绘制全部控件,那么当使用编辑框的时候,弹出的编辑控件不会跟随着滚动,如下所示:

那么就当滚动的时候,结束当前正在编辑的项吧,修改OnDrawListView2函数:

    private void OnDrawListView2()
    {
        Rect totalRect = new Rect(0, 0, position.width, position.height);
        Rect contentRect = new Rect(0, 0, s_ColCount * m_ColWidth, s_RowCount * m_RowHeight);

        // 鼠标滚动的时候,结束当前正在编辑的项
        if (Event.current.type == EventType.ScrollWheel)
        {
            if (totalRect.Contains(Event.current.mousePosition))
            {
                GUIUtility.keyboardControl = 0;
            }
        }

        EditorGUI.BeginChangeCheck();
        m_ScrollPosition = GUI.BeginScrollView(totalRect, m_ScrollPosition, contentRect);
        if (EditorGUI.EndChangeCheck())
        {
            // 滚动条滚动的时候,结束当前正在编辑的项
            GUIUtility.keyboardControl = 0;
        }

        int num;
        int num2;
        GetFirstAndLastRowVisible(out num, out num2, totalRect.height);
        if (num2 >= 0)
        {
            int numVisibleRows = num2 - num + 1;
            IterateVisibleItems(num, numVisibleRows, contentRect.width, totalRect.height);
        }

        GUI.EndScrollView(true);
    }

自动布局

如果还是想使用自动布局方式来绘制项的话,那么可以使用GUILayout.Space来占用不需要绘制的区域。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值