Unity编辑器ScrollView滚动卡顿优化

发表于2017-05-21
评论1 4.1k浏览

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using UnityEditor;
using UnityEngine;
 
public class TestListView : EditorWindow
{
    [MenuItem("Tool/Test")]
    private static void Init()
    {
        GetWindow();
    }
 
    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自动布局方式改成给定矩形来绘制,这样方便知道每行的高度和总的内容高度,再根据滚动条的坐标来计算获取当前显示的起始行和结束行,只绘制需要显示的行内容。

代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
using UnityEditor;
using UnityEngine;
 
public class TestListView : EditorWindow
{
    [MenuItem("Tool/Test")]
    private static void Init()
    {
        GetWindow();
    }
 
    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);
    }
 
    ///
    /// 获取可显示的起始行和结束行
    ///
    /// 起始行
    /// 结束行
    /// 视图高度
    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);
            }
        }
    }
 
    ///
    /// 迭代绘制可显示的项
    ///
    /// 起始行
    /// 总可显示行数
    /// 每行的宽度
    /// 视图高度
    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函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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来占用不需要绘制的区域。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引