Unity中的资源管理-对象池技术(1)

2023-11-12

本文分享Unity中的资源管理-对象池技术(1)

接下来几天, 作者会按照自己的理解写几篇关于Unity中的资源管理相关的文章.

大概会涉及到:

  • 对象池: 分为普通类GameObject(主要是预制)的对象池

  • 引用计数技术

  • Unity中的资源基本概念, 分类, 基本使用: 包含Resources, AssetDatabase

  • Unity中的Ab包: 包含Ab包的介绍, 生成, 加载和卸载

  • Unity中使用Profiler进行内存分析

  • 一整套资源管理方案

今天分享对象池技术的第一部分: 普通类的对象池.

什么是对象池以及为什么需要对象池

在正式开始之前, 我们需要搞明白为什么需要对象池.

对象的实例化可能是一个比较繁重的工作, 比如预制的实例化, 需要将在背后做很多事情, 如加载其依赖的资源, 实例化这些资源, 给资源对象赋值, 给预制对象赋值, 最终得到预制的实例化对象. 针对同一个预制, 如果需要多次使用和销毁其实例化对象, 会造成资源浪费和卡顿, 我们可以在不使用的时候将其实例化对象保存下来而不是直接销毁, 在下次使用时直接给予缓存的对象而不需要进行实例化, 这样可以充分利用资源, 也可以加速进程.

简单的说对象池就是将不需要使用的多个对象存下来, 下次需要使用时直接给予而不再进行实例化的技术.

在我们游戏开发中最常见的对象池技术的实例就是子弹的使用.

想象一下, 如果不使用对象池, 我们就会频繁的创建和销毁对象, 这对性能是很大的影响.

对象池是一个典型的空间换时间的技术, 为了玩家流畅的体验, 我们一般会在进入场景前预加载和预创建好一定数量的对象存放在对象池中, 然后在游戏过程中直接拿来使用即可.

简单的对象池实现

首先, 我们先实现一个满足基本需求的简单对象池: 可以产生对象, 回收对象, 销毁对象.

public class SimpleObjectPool<T> where T : class, new() {
    protected readonly Stack<T> m_ObjPool;

    protected int m_MaxCount;
    protected int m_OnceReleaseCount;

    public SimpleObjectPool(int initCapacity = 5, int maxCount = 500, int onceReleaseCount = 10) {
        m_ObjPool = new Stack<T>(initCapacity);

        m_MaxCount = maxCount;
        m_OnceReleaseCount = onceReleaseCount;
    }

    public T Spawn() {
        var obj = m_ObjPool.Count <= 0 ? CreateObj() : m_ObjPool.Pop();
        return obj;
    }

    public void Recycle(T obj) {
        if (m_ObjPool.Count >= m_MaxCount) {
            ReleaseOverflowObj();
        }

        m_ObjPool.Push(obj);
    }

    private T CreateObj() {
        var obj = new T();
        return obj;
    }

    /// <summary>
    /// 移除多余对象
    /// </summary>
    private void ReleaseOverflowObj() {
        Debug.Log("[ReleaseOverflowObj] 已达最大回收数量: " + m_ObjPool.Count);

        var removeCount = Math.Min(m_OnceReleaseCount, m_ObjPool.Count);
        while(--removeCount >= 0) {
            var obj = m_ObjPool.Pop();
        }

        Debug.Log("[ReleaseOverflowObj] 当前池中数量: " + m_ObjPool.Count);
    }
}

我们使用泛型来创建对象池, 这样可以很好的兼容大部分类.

第一行public class SimpleObjectPool<T> where T : class, new(), 其中where T : class, new()代表对于容纳的结构要有两个约束: 结构T必须是类(class), 并且拥有无参构造函数(new()).

我们使用栈来当做对象的容器, 因为我们会频繁的添加和删除, 而栈对于这两个操作的复杂度都是O(1)级别的.

常见的另一个实现是使用数组, 数组的每个位置代表一个插槽, 待回收的对象插入插槽, 并返回索引, 需要使用时依靠索引来获取, Xlua的对象池就是使用这种方式.

SpawnRecycle接口分别代表产生和回收对象, 逻辑比较简单.

我们的实现还有一个最大的容量限制, 达到最大容量后才会销毁配置数量的对象.

我们需要更多

上面的实现在一些简单的场景是够用了, 但是在日常开发中我们经常需要知道对象的生成和销毁时机, 比如在真正销毁时做一些清理操作.

下面我们添加一些新的功能: 在对象的产生, 回收, 销毁时通知调用方, 并且可以根据调用方提供的方式来创建对象.

public class ObjectPool<T> : IDisposable where T : class, new() {
    public delegate T NewObj();

    protected readonly Stack<T> m_ObjPool;
    protected int m_MaxCount;
    protected int m_OnceReleaseCount;

    protected UnityAction<T> m_BeforeSpawn, m_AfterRecycle, m_AfterRelease;
    protected NewObj m_NewObjDelegate;

    public ObjectPool(int initCapacity = 5, int maxCount = 500, int onceReleaseCount = 10,
                      UnityAction<T> beforeSpawn = null, UnityAction<T> afterRecycle = null, UnityAction<T> afterRelease = null, 
                      NewObj newObj = null) {

        m_ObjPool = new Stack<T>(initCapacity);

        m_MaxCount = maxCount;
        m_OnceReleaseCount = onceReleaseCount;

        m_BeforeSpawn = beforeSpawn;
        m_AfterRecycle = afterRecycle;
        m_AfterRelease = afterRelease;
        m_NewObjDelegate = newObj;
    }

    public void Dispose() {
        if (m_AfterRelease != null) {
            var array = m_ObjPool.ToArray();
            foreach(var obj in array) {
                m_AfterRelease(obj);
            }
        }

        m_ObjPool.Clear();

        m_BeforeSpawn = m_AfterRecycle = m_AfterRelease = null;
        m_NewObjDelegate = null;
    }

    private T CreateObj() {
        var obj = m_NewObjDelegate != null ? m_NewObjDelegate() : new T();
        return obj;
    }

    /// <summary>
    /// 移除多余对象
    /// </summary>
    private void ReleaseOverflowObj() {
        Debug.Log("[ReleaseOverflowObj] 已达最大回收数量: " + m_ObjPool.Count);

        var removeCount = Math.Min(m_OnceReleaseCount, m_ObjPool.Count);
        while(--removeCount >= 0) {
            var obj = m_ObjPool.Pop();
            m_AfterRelease?.Invoke(obj);
        }

        Debug.Log("[ReleaseOverflowObj] 当前池中数量: " + m_ObjPool.Count);
    }

    public T Spawn() {
        var obj = m_ObjPool.Count <= 0 ? CreateObj() : m_ObjPool.Pop();
        m_BeforeSpawn?.Invoke(obj);

        return obj;
    }

    public void Recycle(T obj) {
        if (m_ObjPool.Count >= m_MaxCount) {
            ReleaseOverflowObj();
        }

        m_ObjPool.Push(obj);
        m_AfterRecycle?.Invoke(obj);
    }
}

可以看到, 我们增加了几个委托, 并在对应的位置调用:

  • UnityAction<T> m_BeforeSpawn: 产生对象之前通知
  • UnityAction<T> m_AfterRecycle: 回收对象之前通知
  • UnityAction<T> m_AfterRelease: 销毁对象之前通知
  • NewObj m_NewObjDelegate: 生成对象

最后实现接口IDisposable, 在对象池销毁时清理这些委托, 养成良好的清理委托的习惯, 避免在如Xlua中使用时造成无法正常垃圾回收.

顺便说一句: m_AfterRecycle?.Invoke(obj);的等价于if (m_AfterRecycle != null) m_AfterRecycle(obj);

使用示例

使用很简单, 也就是需要的时候申请, 不需要的时候回收而已.

public class Animal {
    public int id;
    public string name;

    public void Reset() {
        id = 0;
        name = string.Empty;
    }
}

public class ObjectPoolTest {
    public static void Test() {
        var objectPool = new ObjectPool<Animal>(1, 10, 5,
            animal => {
                Debug.Log(string.Format("before spawn code: {0} ", animal.GetHashCode()));
            }, 
            animal => {
                Debug.Log(string.Format("after recycle code: {0}, name: {1}", animal.GetHashCode(), animal.name));

                animal.Reset();
            },
            animal => {
                Debug.Log(string.Format("after release code: {0}", animal.GetHashCode()));
            });

        var index = 1;
        var lst = new List<Animal>();
        for(var i = 0; i < 20; i++) {
            var animal = objectPool.Spawn();
            animal.id = i;
            animal.name = "name: " + index++;

            lst.Add(animal);
        }

        for(var i = 18; i > 0; i--) {
            var animal = lst[i];
            objectPool.Recycle(animal);

            lst.Remove(animal);
        }

        for(var i = 0; i < 2; i++) {
            var animal = objectPool.Spawn();
            animal.id = i;
            animal.name = "name: " + index++;

            lst.Add(animal);
        }

        foreach(var animal in lst) {
            objectPool.Recycle(animal);
        }

        objectPool.Dispose();
    }
}

我们产生了一些对象, 然后使用, 然后回收, 然后又产生了一些对象, 最后回收所有对象.

整个过程会达到最大数量的对象, 然后一直回收利用. 这里就不贴输出了, 有兴趣的同学可以自行尝试.

最后

今天分享了普通类的对象池, 下一篇文章我们将使用对象池的技术来管理Unity中的GameObject对象.

好了, 今天就这样, 希望对大家有所帮助.

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

Unity中的资源管理-对象池技术(1) 的相关文章

  • 数据模板绑定垃圾邮件输出窗口出现错误:找不到管理 FrameworkElemen

    我有问题 System Windows Data 错误 2 找不到目标元素的管理 FrameworkElement 或 FrameworkContentElement BindingExpression 无路径 数据项 空 目标元素是 So
  • 在 C/C++ 中获得正模数的最快方法

    通常在我的内部循环中 我需要以 环绕 方式索引数组 因此 例如 如果数组大小为 100 并且我的代码要求元素 2 则应该给它元素 98 高级语言 例如 Python 可以简单地使用my array index array size 但由于某
  • 为什么在创建矩阵类时使用向量不好?

    对于我的矩阵类 我做了 template
  • 更改 Qt OpenGL 窗口示例以使用 OpenGL 3.3

    我正在尝试更改 Qt OpenGL 示例以使用更现代的 opengl 版本 330 似乎合适 所以我做了 在 main cpp 上设置版本和配置文件 设置着色器版本 更改着色器以使用统一 它现在构建没有任何错误 但我只看到一个空白窗口 我错
  • VS 程序在调试模式下崩溃,但在发布模式下不崩溃?

    我正在 VS 2012 中运行以下程序来尝试 Thrust 函数查找 include cuda runtime h include device launch parameters h include
  • 读取 C# 中的默认应用程序设置

    我的自定义网格控件有许多应用程序设置 在用户范围内 其中大部分是颜色设置 我有一个表单 用户可以在其中自定义这些颜色 并且我想添加一个用于恢复默认颜色设置的按钮 如何读取默认设置 例如 我有一个名为的用户设置CellBackgroundCo
  • 防止 boost::asio::io_context 在空轮询调用时停止

    此代码调用发布的句柄 boost asio io context ioc boost asio post ioc std cout lt lt lol lt lt std endl ioc poll 而这并没有 boost asio io
  • 信号处理程序有单独的堆栈吗?

    信号处理程序是否有单独的堆栈 就像每个线程都有单独的堆栈一样 这是在 Linux C 环境中 来自 Linux 手册页signal 7 http kernel org doc man pages online pages man7 sign
  • GCC 和 ld 找不到导出的符号...但它们在那里

    我有一个 C 库和一个 C 应用程序 尝试使用从该库导出的函数和类 该库构建良好 应用程序可以编译 但无法链接 我得到的错误遵循以下形式 app source file cpp text 0x2fdb 对 lib namespace Get
  • 找不到 assimp-vc140-mt.dll ASSIMP

    我已经从以下位置下载了 Assimp 项目http assimp sourceforge net main downloads html http assimp sourceforge net main downloads html Ass
  • 动态生成的控件 ID 返回为 NULL

    我可以在 Page PreInit 函数中创建动态控件 如何检索控件及其 ID 我的 C 代码用于创建动态控件之一 var btn new WebForms Button btn Text btn ID Addmore btn Click
  • fprintf() 线程安全吗?

    我正在为野人就餐问题的某些变量编写一个 C 解决方案 现在 我创建线程 每个线程都将 FILE 获取到同一个调试文件 在线程内我正在使用 fprintf 进行一些打印 打印的语句不受任何类型的互斥锁等保护 我没有在调试文件中观察到任何交错行
  • 单例模式和 std::unique_ptr

    std unique ptr唯一地控制它指向的对象 因此不使用引用计数 单例确保利用引用计数只能创建一个对象 那么会std unique ptr与单例执行相同 单例确保只有一个实例属于一种类型 A unique ptr确保只有一个智能指针到
  • std::forward_as_tuple 将参数传递给 2 个构造函数

    我想传递多个参数以便在函数内构造两个对象 以同样的方式std pair
  • 检查 RoutedEvent 是否有任何处理程序

    我有一个自定义 Button 类 当单击它时 打开特定窗口 它总是执行相同的操作 我添加了一个可以在按钮的 XAML 中分配的 Click 事件 就像常规按钮一样 当它被单击时 我想执行 Click 事件处理程序 如果已分配 否则我想执行默
  • 将二进制数据从 C# 上传到 PHP

    我想将文件从 Windows C 应用程序上传到运行 PHP 的 Web 服务器 我知道 WebClient UploadFile 方法 但我希望能够分块上传文件 以便我可以监控进度并能够暂停 恢复 因此 我正在读取文件的一部分并使用 We
  • 无法在内存位置找到异常源:cudaError_enum

    我正在尝试确定 Microsoft C 异常的来源 test fft exe 中 0x770ab9bc 处的第一次机会异常 Microsoft C 异常 内存位置 0x016cf234 处的 cudaError enum 我的构建环境是 I
  • 如何分析组合的 python 和 c 代码

    我有一个由多个 python 脚本组成的应用程序 其中一些脚本正在调用 C 代码 该应用程序现在的运行速度比以前慢得多 因此我想对其进行分析以查看问题所在 是否有工具 软件包或只是一种分析此类应用程序的方法 有一个工具可以将 python
  • cout 和字符串连接

    我刚刚复习了我的 C 我尝试这样做 include
  • 将 char[][] 转换为 char** 会导致段错误吗?

    好吧 我的 C 有点生疏了 但我想我应该用 C 来做我的下一个 小 项目 这样我就可以对其进行抛光 并且我已经有不到 20 行的段错误了 这是我的完整代码 define ROWS 4 define COLS 4 char main map

随机推荐

  • python数据清洗 —— re.split()划分字符串

    需求 对于一行字符串 route views6 routeviews org 141694 2a0c b641 24f fffe 7 184891 CN apnic OTAKUJAPAN AS Otaku Limited CN 要将其划分成
  • CMD 命令行实现 Windows 下复制文件到文件夹下的所有文件夹

    目录 前言 1 学习 xcopy 2 展示命令行 前言 提示 这里可以添加本文要记录的大概内容 整件事情真是花了我大半天的时间 几个小时啊 终于从错误中尝试出了正确的做法 赶紧分享一下 1 学习 xcopy Win R 调出运行 键入 cm
  • ElasticSearch简介

    ElasticSearch是Java开发并且是当前最流行的开源的企业级搜索引擎 能够达到近实时搜索 稳定可靠快速安装使用方便 客户端支持Java net各种编程语言 ElasticSearch通Lucene的比较 Lucene只能在Java
  • MAC安装LLVM指导

    首先克隆llvm github工程代码 下载有时出现中断失败 git clone https github com llvm llvm project git 安装依赖软件 安装 ninja 也可以通过编译方式安装 brew install
  • 微信公众号内下载pdf等文件,受微信所限制,安卓和IOS不同处理方式(最最最优版)

    继上一篇文章微信公众号内下载pdf等文件 受微信所限制 安卓和IOS不同处理方式 后觉得还有更好的解决办法 这次真的找到更加优化版本 一定需要后台配合才行 后台接口返回Blob 后端设置response setHeader Content
  • OPC通信从入门到精通_1_OPC基础知识及简单C#程序编写(OPCDA,OPCUA简介;OPC通信数据流框架图;C#程序编写)

    文章目录 1 OPC基础知识 OPCDA OPCUA 1 1 OPC基础知识 1 2 OPC通信读写方式 2 OPC通信仿真 2 1 上位机与PLC通过ModbusTCP直接通信 2 2 OPC通信介绍及实例 2 2 1 OPC通信与Mod
  • TCP报文格式

    TCP报文格式 文章目录 TCP报文格式 TCP首部 三次握手 四次挥手 TCP首部 源端口和目的端口 各占16bit 序号 SEQ序号 给发送的每个数据包标上序号 确认号 ACK序号 是指即将接收的数据包序号 注意 这里指的是序号不是标志
  • Linux 之软中断softirq

    版权声明 本文为博主原创文章 未经博主允许不得转载 https blog csdn net huangweiqing80 article details 83274095 softirq驱动开发人员一般都不会用到 到内核代码中会用到soft
  • python requests get请求_Python接口自动化之requests请求封装

    今天距2021年253天 这是ITester软件测试小栈第114次推文 在上一篇Python接口自动化测试系列文章 Python接口自动化之Token详解及应用 介绍token基本概念 运行原理及在自动化中接口如何携带token进行访问 以
  • docker内存

    docker container 动态修改内存限制 docker update help docker update m 4096m memory swap 1 ubuntu test docker update m 4096m memor
  • FPGA中的output or inout port xxx must be connected to a structural net expression错误

    主模块的output不能加reg 只在子模块的output 加reg 关于子模块调用 有两种调用方式 第一种是位置对应 如 bcd accbcd in8 out71 ou72 out73 如上图所示 第二种是信号名对应方式 此时不必按顺序
  • 数据结构---栈&&队列

    目录 什么是数据结构 什么是算法 Algorithm 生活中的数据结构和算法 数组结构 栈结构 stack 栈结构的实现 十进制转二进制 队列结构 Queue 队列的应用 对列类的创建 击鼓传花面试题 优先级队列 优先级队列的实现 什么是数
  • C语言中排序函数的用法

    C语言中没有预置的sort函数 如果在C语言中 遇到有调用sort函数 就是自定义的一个函数 功能一般用于排序 一 可以编写自己的sort函数 如下函数为将整型数组从小到大排序 void sort int a int l a为数组地址 l为
  • TensorFlow和Caffe、MXNet、Keras等其他深度学习框架的对比

    转 http www leiphone com news 201702 T5e31Y2ZpeG1ZtaN html 雷锋网按 本文作者黄文坚 PPmoney 大数据算法总监 TensorFlow 实战 作者 本文节选自 TensorFlow
  • SQL Server日期格式的多种转换方法

    MSSQL Server的日期字段是datetime 其默认格式是yyyy mm dd hh mm ss mmm 如在查询分析器里面执行 select getdate 会得到如下结果 2006 03 30 22 09 33 763 但对于我
  • 双因素方差分析(R)

    目录 原理 双因素等重复试验的方差分析 假设前提和模型设定 离差平方和分解 检验统计量和拒绝域 例题 应用 双因素无重复试验的方差分析 假设前提和模型设定 离差平方和分解 检验统计量和拒绝域 例题 应用 原理 在单因素方差分析的基础上 双因
  • 微信小程序 WXBizDataCrypt 解密 报错

    在使用微信官方WXBizDataCrypt js解密encryptedData获取敏感数据的时候 偶尔会报错 DeprecationWarning Buffer is deprecated due to security and usabi
  • 字符串的分割、截取

    文章目录 分割 截取的简单使用 简单分割 简单截取 升级版分割 截取的使用 url分割 id截取 或者截取其他由某种规则拼接起来的串儿 结尾 分割 截取的简单使用 简单分割 老师上课的时候大概会举类似的例子把 String str a b
  • 时序数据库InfluxDB介绍

    时序数据库InfluxDB介绍 1 什么是InfluxDB 2 那么时序数据有什么特点呢 3 对于时序数据 我们总结了以下特点 4 业务方常见需求 5 时序数据库为了解决什么问题 6 InfluxDB的优势 实时数据库与时序数据库 DBen
  • Unity中的资源管理-对象池技术(1)

    本文分享Unity中的资源管理 对象池技术 1 接下来几天 作者会按照自己的理解写几篇关于Unity中的资源管理相关的文章 大概会涉及到 对象池 分为普通类和GameObject 主要是预制 的对象池 引用计数技术 Unity中的资源基本概