Unity编辑器ScrollView滚动卡顿优化
发表于2017-05-21
为什么要对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
来占用不需要绘制的区域。