【Unity小技巧】Unity探究自制对象池和官方内置对象池(ObjectPool)的使用

文章目录

  • 前言
  • 不使用对象池
  • 使用官方内置对象池
    • 应用
  • 自制对象池
  • 总结
  • 源码
  • 参考
  • 完结

前言

对象池(Object Pool)是一种软件设计模式,用于管理和重用已创建的对象。在对象池中,一组预先创建的对象被维护在一个池中,并在需要时使用和回收。对象池的作用是提供一种高效地创建和销毁对象的方式,以减少系统开销和提高性能。

发明对象池的人绝对是个天才,游戏中我们常常会遇到,频繁创建和销毁大量相同对象的场景,例如敌人子弹
在这里插入图片描述

如果我们不做任何处理,只是单纯的创建和销毁,可能会导致内存泄露,性能下降和卡顿等问题

Instantiate(gameobject)Destroy(gameobject)

对象池的出现,减少了频繁,创建和销毁对象带来的成本,实现对象的循环和复用

在对象池设计理念中,我们不再单纯的创建和销毁,创建对象时,我们会将对象存入对象池中,需要使用对象时,我们从池子中获取对象,当不需要对象时,我们再将对象存入对象池中,以实现对象的循环复用,减少频繁创建销毁象带来的成本

幸运的是,从2021年3月版本后,unity官方为我门内置了对象池,接下来的教程我将以一个简单的例子,带大家熟悉了解使用官方的对象池

不使用对象池

制作一个金币预制体,挂载刚体、碰撞和Coin脚本
在这里插入图片描述
新增一个空对象,用于生成金币,挂载CoinPool脚本
在这里插入图片描述Coin脚本代码,金币接触地面销毁

private void OnCollisionEnter2D(Collision2D collision) // 碰撞检测
{Debug.Log(collision.gameObject.layer);if (collision.gameObject.layer == LayerMask.NameToLayer("Ground")){//销毁Destroy(gameObject);}
}

CoinPool脚本代码

public GameObject coin; //金币预制体
public float time; //金币生成间隔时间
void Start()
{StartCoroutine(go());
}IEnumerator go()
{while (true){//协程每time秒执行一次CreateCoin();yield return new WaitForSeconds(time);}
}//生成金币
private void CreateCoin()
{GameObject gb = Instantiate(coin, transform);//在当前对象处生成一个金币gb.transform.position = new Vector3(Random.Range(-80f, -67f), Random.Range(3f, 9f));//随机生成位置
}

效果,可以看到我们只是单纯的创建和销毁金币
在这里插入图片描述

使用官方内置对象池

一、命名空间

using UnityEngine.Pool;

二、构造方法

public ObjectPool(Func<T> createFunc, Action<T> actionOnGet = null, Action<T> actionOnRelease = null, Action<T> actionOnDestroy = null, bool collectionCheck = true, int defaultCapacity = 10, int maxSize = 10000
);

参数列表解释

每个参数等号右边代表默认值,即第一个参数为必填项。

1.Func createFunc

    需填入一个带T类型返回值的方法,即自定义新建对象时的操作

2.Action actionOnGet, Action actionOnRelease, Action actionOnDestroy

   分别为出池、进池、销毁的响应事件。填入自定义方法名即可,用于拓展相应操作,比如在actionOnDestroy中通过Destroy()方法将因为池满而不能入池的对象直接删除

3.bool collectionCheck

    是否在进池前检查对象是否已经存在池中

4.int defaultCapacity, int maxSize

    初始容量,最大容量

三、属性
1.int CountAll

    所有的对象数,三个属性都为只读初始值为0,经测试,已知每调用一次createFunc委托该值就会+1

2.int CountActive

    被启用的对象数,已知每调用一次Release()方法就会-1,Get()方法+1

3.int CountInactive

    未被启用的对象数,已知每调用一次Release()方法就会+1,Get()方法-1,最大值为池的最大容量

四、常用方法
1.T Get()

    出池,返回一个池中对象。如果池为空,则先调用createFunc,再出池

2.void Relese(T element)

    进池,将对象element加入池。如果池已达到最大容量,则不入池并触发actionOnDestroy事件

3.void Clear()

    清空池

应用

使用官方内置对象池修改前面的例子
Coin脚本

using UnityEngine;
using UnityEngine.Pool;
public class Coin : MonoBehaviour
{public ObjectPool<GameObject> pool;private void OnCollisionEnter2D(Collision2D collision) // 碰撞检测{Debug.Log(collision.gameObject.layer);if (collision.gameObject.layer == LayerMask.NameToLayer("Ground")){//销毁// Destroy(gameObject);pool.Release(gameObject);}}
}

CoinPool脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;public class CoinPool : MonoBehaviour
{public GameObject coin; //金币预制体public float time; //金币生成间隔时间public ObjectPool<GameObject> pool;public int poolMaxSize;//对象池最大容量void Start(){//是否在进池前检查对象是否已经存在池中,初始容量,最大容量pool = new ObjectPool<GameObject>(createFunc, actionOnGet, actionOnRelease, actionOnDestroy, true, 10, poolMaxSize);StartCoroutine(go());}IEnumerator go(){while (true){//协程每time秒执行一次CreateCoin();Debug.Log("总对象数:"+pool.CountAll);Debug.Log("启用的对象数:"+pool.CountActive);Debug.Log("未启用的对象数:"+pool.CountInactive);yield return new WaitForSeconds(time);}}//生成金币private void CreateCoin(){// GameObject gb = Instantiate(coin, transform);//在当前对象处生成一个金币GameObject gb = pool.Get();gb.transform.position = new Vector3(Random.Range(-80f, -67f), Random.Range(3f, 9f));//随机生成位置}// 需填入一个带T类型返回值的方法,即自定义新建对象时的操作public GameObject createFunc(){GameObject obj = Instantiate(coin, transform);obj.GetComponent<Coin>().pool = pool;//将pool和Coin的pool赋值为同一个return obj;}void actionOnGet(GameObject gameObject){gameObject.SetActive(true);//显示敌人Debug.Log(gameObject.name + "出池");}void actionOnRelease(GameObject gameObject){gameObject.SetActive(false);//隐藏敌人Debug.Log(gameObject.name + "进池");}void actionOnDestroy(GameObject gameObject){Debug.Log("池已满," + gameObject.name + "被销毁");Destroy(gameObject);}
}

效果,可以看到我们不再是单纯的创建和销毁金币,而是开启和关闭复用前面生成的金币
在这里插入图片描述

自制对象池

新增对象池脚本ObjectPool

using System.Collections.Generic;
using UnityEngine;public class ObjectPool
{private static ObjectPool instance; // 单例模式// /**//  * 我们希望不同的物体可以被分开存储,在这种情况下使用字典是最合适的//  * 所以声明一个字典objectPool作为对象池主体,以字符串类型的物体的名字作为key//  * 使用队列存储物体来作为value,这里使用队列只是因为入队和出队的操作较为方便,也可以换成其他集合方式//  * 然后实例化这个字典以备后续使用//  * /private Dictionary<string, Queue<GameObject>> objectPool = new Dictionary<string, Queue<GameObject>>(); // 对象池字典private GameObject pool; // 为了不让窗口杂乱,声明一个对象池父物体,作为所有生成物体的父物体public static ObjectPool Instance // 单例模式{get{if (instance == null){instance = new ObjectPool();}return instance;}}public GameObject GetObject(GameObject prefab) // 从对象池中获取对象{GameObject _object;if (!objectPool.ContainsKey(prefab.name) || objectPool[prefab.name].Count == 0) // 如果对象池中没有该对象,则实例化一个新的对象{_object = GameObject.Instantiate(prefab);PushObject(_object); // 将新的对象加入对象池if (pool == null)pool = new GameObject("ObjectPool"); // 如果对象池父物体不存在,则创建一个新的对象池父物体GameObject childPool = GameObject.Find(prefab.name + "Pool"); // 查找该对象的子对象池if (!childPool){childPool = new GameObject(prefab.name + "Pool"); // 如果该对象的子对象池不存在,则创建一个新的子对象池childPool.transform.SetParent(pool.transform); // 将该子对象池加入对象池父物体中}_object.transform.SetParent(childPool.transform); // 将新的对象加入该对象的子对象池中}_object = objectPool[prefab.name].Dequeue(); // 从对象池中取出一个对象_object.SetActive(true); // 激活该对象return _object; // 返回该对象}public void PushObject(GameObject prefab) // 将对象加入对象池中{//获取对象的名称,因为实例化的物体名都会加上"(Clone)"的后缀,需要先去掉这个后缀才能使用名称查找string _name = prefab.name.Replace("(Clone)", string.Empty);if (!objectPool.ContainsKey(_name))objectPool.Add(_name, new Queue<GameObject>()); // 如果对象池中没有该对象,则创建一个新的对象池objectPool[_name].Enqueue(prefab); // 将对象加入对象池中prefab.SetActive(false); // 将对象禁用}
}

Coin脚本

using UnityEngine;
using UnityEngine.Pool;
public class Coin : MonoBehaviour
{private void OnCollisionEnter2D(Collision2D collision) // 碰撞检测{// Debug.Log(collision.gameObject.layer);if (collision.gameObject.layer == LayerMask.NameToLayer("Ground")){//销毁// Destroy(gameObject);// pool.Release(gameObject);ObjectPool.Instance.PushObject(gameObject);}}
}

CoinPool脚本

using System.Collections;
using UnityEngine;public class CoinPool : MonoBehaviour
{public GameObject coin; //金币预制体public float time; //金币生成间隔时间void Start(){StartCoroutine(go());}IEnumerator go(){while (true){//协程每time秒执行一次CreateCoin();yield return new WaitForSeconds(time);}}//生成金币private void CreateCoin(){// GameObject gb = Instantiate(coin, transform);//在当前对象处生成一个金币// GameObject gb = pool.Get();GameObject gb = ObjectPool.Instance.GetObject(coin);gb.transform.position = new Vector3(Random.Range(-80f, -67f), Random.Range(3f, 9f));//随机生成位置}
}

效果
在这里插入图片描述

总结

Unity官方内置对象池和自己写对象池都有各自的优缺点,具体取决于你的需求和项目的规模。

如果你的游戏或应用程序很简单,并且对象池的需求较小,那么使用Unity官方内置的对象池是一个方便和快速的选择。Unity的对象池方法已经经过优化,并且与引擎的其他功能集成得很好,使用起来也非常简单。你可以直接使用ObjectPool类来创建和管理对象池,而无需自己编写额外的代码。

然而,当你的游戏或应用程序更复杂,并且需要更高级的对象池功能时,可能会需要自己编写对象池。自己编写对象池可以根据项目的具体需求进行定制化,以满足特定的性能要求。你可以实现自己的池管理系统、缓存策略和对象回收机制,以及其他高级功能,如对象优先级、预加载等。

总的来说,如果你的项目规模较小并且简单,使用Unity官方内置对象池是一个方便的选择。如果你的项目更加复杂或有特定的需求,编写自己的对象池可能更适合。最佳选择取决于你的项目需求、时间和资源限制,以及你对对象池功能的具体要求。

如果是我的话,我还是会选择自己写对象池脚本,因为这样可以保证一定的可控性、复用性和可扩展性。
在这里插入图片描述
好多钱,快去捡

源码

https://gitcode.net/unity1/objectpool
在这里插入图片描述

参考

【视频】https://www.bilibili.com/video/BV1Su411E7b2

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/44793.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里云ECS服务器和轻量应用服务器区别?怎么选择?

阿里云轻量应用服务器和云服务器ECS有什么区别&#xff1f;ECS是专业级云服务器&#xff0c;轻量应用服务器是轻量级服务器&#xff0c;轻量服务器使用门槛更低&#xff0c;适合个人开发者或中小企业新手使用&#xff0c;可视化运维&#xff0c;云服务器ECS适合集群类、高可用、…

浅析DIX与DIF(T10 PI)

文章目录 概述DIF与DIX端到端数据保护 DIFDIF保护类型 SCSI设备支持DIFStandard INQUIRY DataExtended INQUIRY Data VPD pageSPT字段GRD_CHK、APP_CHK、REF_CHK字段 READ CAPACITY(16)响应信息 SCSI命令请求读命令请求写命令请求 DIF盘格式化相关参考 概述 DIF与DIX DIF&…

【机器学习6】数据预处理(三)——处理类别数据(有序数据和标称数据)

处理类别数据 &#x1f331;简要理解处理类别数据的重要性☘️类别数据的分类☘️方便研究——用pandas创建包含多种特征的数据集&#x1f340;映射有序特征&#x1f340;标称特征标签编码&#x1f340;标称特征的独热编码&#x1f331;独热编码的优缺点 &#x1f331;简要理解…

ListNode相关

目录 2. 链表相关题目 2.1 合并两个有序链表&#xff08;简单&#xff09;&#xff1a;递归 2.2 删除排序链表中的重复元素&#xff08;简单&#xff09;&#xff1a;一次遍历 2.3 两链表相加&#xff08;中等&#xff09;&#xff1a;递归 2.4 删除链表倒数第N个节点&…

MySQL 索引为什么使用 B+ 树,而不使用红黑树 / B 树 ?

面试官问 &#xff1a;索引为什么使用 B 树&#xff0c;而不使用 B 树&#xff0c;不使用红黑树呢 首先 B 树和 B 树 都是多叉搜索树&#xff0c;然后我们先来观察一下 B 树和 B 树的数据结构&#xff1a; B 树的数据结构实现 >> B 树的数据结构实现 >> 【B 树相…

2023国赛数学建模思路 - 案例:FPTree-频繁模式树算法

文章目录 算法介绍FP树表示法构建FP树实现代码 建模资料 ## 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模式树算法&#xff0c…

代码随想录算法训练营day39 | 62. 不同路径,63. 不同路径 II

目录 62. 不同路径 63. 不同路径 II 62. 不同路径 类型&#xff1a;动态规划 难度&#xff1a;medium 思路&#xff1a; 应用二维数组的动态规划&#xff0c;到达某个方格的方法数目&#xff0c;为这个方格的上一个方格和左一个方格的方法数目和。 需要先初始化第一行和第一…

08.SpringBoot请求相应

文章目录 1 请求1.1 Postman1.2 简单参数1.2.1 原始方式1.2.2 SpringBoot方式1.2.3 参数名不一致 1.3 实体参数1.3.1 简单实体对象1.3.2 复杂实体对象 1.4 数组集合参数1.4.1 数组1.4.2 集合 1.5 日期参数1.6 JSON参数1.7 路径参数 2 响应2.1 ResponseBody注解2.2 统一响应结果…

GAN!生成对抗网络GAN全维度介绍与实战

目录 一、引言1.1 生成对抗网络简介1.2 应用领域概览1.3 GAN的重要性 二、理论基础2.1 生成对抗网络的工作原理2.1.1 生成器生成过程 2.1.2 判别器判别过程 2.1.3 训练过程训练代码示例 2.1.4 平衡与收敛 2.2 数学背景2.2.1 损失函数生成器损失判别器损失 2.2.2 优化方法优化代…

收集的一些比较好的git网址

1、民间故事 https://github.com/folkstory/lingqiu/blob/master/%E4%BC%A0%E8%AF%B4%E9%83%A8%E5%88%86/%E4%BA%BA%E7%89%A9%E4%BC%A0%E8%AF%B4/%E2%80%9C%E6%B5%B7%E5%BA%95%E6%8D%9E%E6%9C%88%E2%80%9D%E7%9A%84%E6%AD%A6%E4%B8%BE.md 2、童话故事 https://gutenberg.org/c…

12. Docker可视化工具

目录 1、前言 2、Docker UI 2.1、部署Docker UI 2.2、管理容器 3、Portainer 3.1、部署Portainer 3.2、管理容器 3.3、添加远程Docker 4、Shipyard 1、前言 Docker 提供了命令行工具来管理 Docker 的镜像和运行 Docker 的容器。我们也可以使用图形工具来管理 Docker。…

C# 观察者模式

一、概述 观察者模式是一种常用的设计模式&#xff0c;它属于行为型模式。在C#中&#xff0c;观察者模式通过定义一种一对多的依赖关系&#xff0c;使得当一个对象的状态发生变化时&#xff0c;所有依赖于它的对象都会得到通知并自动更新。这种模式可以实现松耦合&#xff0c;…

Ribbon负载均衡

Ribbon与Eureka的关系 Eureka的服务拉取与负载均衡都是由Ribbon来实现的。 当服务发送http://userservice/user/xxxhtt://userservice/user/xxx请求时&#xff0c;是无法到达userservice服务的&#xff0c;会通过Ribbon会把这个请求拦截下来&#xff0c;通过Eureka-server转换…

常见排序集锦-C语言实现数据结构

目录 排序的概念 常见排序集锦 1.直接插入排序 2.希尔排序 3.选择排序 4.堆排序 5.冒泡排序 6.快速排序 hoare 挖坑法 前后指针法 非递归 7.归并排序 非递归 排序实现接口 算法复杂度与稳定性分析 排序的概念 排序 &#xff1a;所谓排序&#xff0c;就是使一串记录&#…

排名前 6 位的数学编程语言

0 说明 任何对数学感兴趣或计划学习数学的人&#xff0c;都应该至少对编程语言有一定的流利程度。您不仅会更有就业能力&#xff0c;还可以更深入地理解和探索数学。那么你应该学习什么语言呢&#xff1f; 1.python 对于任何正在学习数学的人来说&#xff0c;Python都是一门很棒…

【Linux从入门到精通】动静态库的原理与制作详解

本篇文章主要是围绕动静态库的原理与制作进行展开讲解的。其中涉及到了inode的概念引入和软硬连接的讲解。会结合实际操作对这些抽象的概念进行解释&#xff0c;希望会对你有所帮助。 文章目录 一、inode 概念 二、软硬链接 2、1 软连接 2、2 硬链接 三、动静态库概念 3、1 静态…

编织梦想:SpringBoot AOP 教程与自定义日志切面完整实战

什么是 AOP AOP 是指通过预编译方式和运行期动态代理的方式&#xff0c;在不修改源代码的情况下对程序进行功能增强的一种技术。AOP 不是面向对象编程&#xff08;OOP&#xff09;的替代品&#xff0c;而是 OOP 的补充和扩展。它是一个新的维度&#xff0c;用来表达横切问题&a…

常见前端基础面试题(HTML,CSS,JS)(三)

JS 中如何进行数据类型的转换&#xff1f; 类型转换可以分为两种&#xff0c;隐性转换和显性转换 显性转换 主要分为三大类&#xff1a;数值类型、字符串类型、布尔类型 三大类的原始类型值的转换规则我就不一一列举了 数值类型&#xff08;引用类型转换&#xff09; Numbe…

设计模式之状态模式(State)的C++实现

1、状态模式的提出 在组件功能开发过程中&#xff0c;某些对象的状态经常面临变化&#xff0c;不同的状态&#xff0c;其对象的操作行为不同。比如根据状态写的if else条件情况&#xff0c;且这种条件变化是经常变化的&#xff0c;这样的代码不易维护。可以使用状态模式解决这…

如何在window下cmd窗口执行linux指令?

1.Git&#xff1a;https://git-scm.com/downloads(官网地址) 2.根据自己的实际路径,添加两个环境变量 3.重启电脑