在使用 Unity 开发游戏的时候,经常会需要用到数据配置,方式可能是CSV、JSON等等。为了可以方便地查看修改数据,通常使用ScrollView
实现在 Unity 编辑器里面以列表的形式查看数据。
当数据量大的时候,滚动视图会发现卡顿不断,测试代码如下:
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自动布局方式改成给定矩形来绘制,这样方便知道每行的高度和总的内容高度,再根据滚动条的坐标来计算获取当前显示的起始行和结束行,只绘制需要显示的行内容。
代码修改如下:
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
函数:
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
来占用不需要绘制的区域。