Unity 编辑器ScrollView滚动卡顿优化

2023-11-06

原因

在使用 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来占用不需要绘制的区域。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Unity 编辑器ScrollView滚动卡顿优化 的相关文章

随机推荐

  • 重装Ubuntu18.04双系统

    重装Ubuntu18 04双系统 1 检查电脑设备 2 Ubuntu18 04 下载 3 下载UltraISO 3 1 win10遇到无法连接虚拟磁盘服务解决方法 4 安装Ubuntu系统 1 检查电脑设备 https www cnblog
  • RSA的数学运算步骤

    原创文章 绝非抄袭 叙述一下我学了很久的一个RSA公钥加密 很多地方在用的一种安全的加密方法 以前只知道那种老式电报的加密 两边各拿一个对照表 什么数字对什么字 倒是很好理解 算是对称加密 在学校的时候就讨论过很长时间的公钥加密 一直理解不
  • Huffman-哈夫曼编码算法详解

    1 概述 背景 哈夫曼编码是广泛地用于数据文件压缩的十分有效的编码方法 其压缩率通常在20 90 之间 哈夫曼编码算法用字符在文件中出现的频率表来建立一 个用0 1串表示各字符的最优表示方式 给出现频率高的字符较短的编码 出现频率较低的字符
  • Linux系统之ifconfig命令的基本使用

    Linux系统之ifconfig命令的基本使用 一 ifconfig命令介绍 1 ifconfig简介 2 ifconfig注意事项 3 ifconfig命令特点 二 ifconfig命令的使用方法 1 查看ifconfig的帮助信息 2
  • LeetCode 之 剑指 Offer 11. 旋转数组的最小数字(Java)

    文章目录 LeetCode 之 剑指 Offer 11 旋转数组的最小数字 Java 一 题目 二 解题思路 三 代码 LeetCode 之 剑指 Offer 11 旋转数组的最小数字 Java 一 题目 剑指 Offer 11 旋转数组的
  • 机器视觉之霍夫变换提取直线

    机器视觉之霍夫变换提取直线 import cv2 as cv import numpy as np img cv imread home local EUROPRO guoliang wang OpenCV opencv samples d
  • 使用Visual Studio Profiler分析程序性能

    Profiler 是 Visual Studio 中集成的性能分析工具 它主要用于分析程序中所有函数调用所占的耗费时间 它提供了Sample和Instrumentation两种检测方式 能够生成简洁易读的报告 它只适用于Team Suit版
  • 怎样修改linux时区

    怎样修改linux时区 如果你的 Linux 系统时区配置不正确 必需要手动调整到正确的当地时区 NTP 对时间的同步处理只计算当地时间与 UTC 时间的偏移量 因此配置一个 NTP 对时间进行同步并不能解决时区不正确的问题 所以大家在用了
  • vue项目,ie11 浏览器报 Promise 未定义的错误

    报错 description Promise 未定义 message Promise 未定义 name ReferenceEr number 2146823279 stack ReferenceEr 解决办法 1 npm install b
  • AIDL实现跨APP通信(双向通信)

    某天 接到了一人任务要在两个app互相传递一些消息 上网查了以后发现有几种方式 第一是广播 第二是AIDL 由于初出茅庐全不会就选择了AIDL 下面简单粗暴的开始贴代码好了 也是得到了网上的一些借鉴 若有雷同 嗯你懂的 1 我们需要先建立A
  • SVN出现:原始内容“XXXX“不存在

    缘由 偶然一次在工作期间 策划的svn出现了 原始内容 XXXX 不存在 的提示 无论是用清理 还是还原都无效 由于是第一次遇到所以记录一下解决过程 分析 从提示上看大致可知是某个文件不存在了 估计是异常操作svn导致一些文件丢失 据网上资
  • Python中的for循环

    Python中的for循环 在Python编程语言中 for循环是一种常用的迭代结构 用于遍历可迭代对象 iterable 它允许我们按照指定的方式重复执行一系列操作 直到满足退出条件 在本文中 我们将详细介绍for循环的使用方式 并提供相
  • 快速排序的优化及其与qsort的比较

    快速排序是用的最多的一种排序方法 编程珠玑 在第十一章 排序 中讨论了快速排序的几个版本 其实版本的不同 都是体现在partition函数上 partition版本1 其实就是 算法导论 给出的版本 很好理解 int partition1
  • 在main函数实现两个数的输入并用函数实现两个数的值相换(两种方法实现)

    在main函数实现两个数的输入并用函数实现两个数的值相换 1 方法一 void f int x int y 定义 声明函数 int t 临时变量 t x x y y t 关于汉诺塔这个是最简单的算法 include
  • python lambda函数排序_python笔记-lambda函数、sorted函数、map函数

    1 lambda函数 又称匿名函数 示例如下 def f x return x 2 print f 4 16 g lambda x x 2 print g 4 16 2 map函数 print map lambda x x 2 range
  • 分布式任务调度(XXL-JOB)

    什么是分布式任务调度 任务调度顾名思义 就是对任务的调度 它是指系统为了完成特定业务 基于给定时间点 给定时间间隔或者给定执行次数自动执行任务 通常任务调度的程序是集成在应用中的 比如 优惠卷服务中包括了定时发放优惠卷的的调度程序 结算服务
  • 减小android button的大小

    当我们仅需要按钮的大小恰当包裹其中文本的大小 尽可能减小多余的空白空间 第一种方式 我们可以直接设置button属性 minHeight和minWidth android minHeight 0dp android minWidth 0dp
  • 工业通信网络结构

    工业通信网络结构 工业通信网络包含所谓的工业现场总线 局域网 公网等概念 这些概念在物理环境下并不是界线分明的 是一种主观上的分类 在物理环境下 目前工业网络结构都是设备与双绞线之间的通信 可以互相通信的设备与双绞线的集合称为网络 当网络中
  • 【深度学习常见问题——Attention基础知识梳理】

    这里写目录标题 1 注意力机制计算 2 d k sqrt d k dk
  • Unity 编辑器ScrollView滚动卡顿优化

    原因 在使用 Unity 开发游戏的时候 经常会需要用到数据配置 方式可能是CSV JSON等等 为了可以方便地查看修改数据 通常使用ScrollView实现在 Unity 编辑器里面以列表的形式查看数据 当数据量大的时候 滚动视图会发现卡