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

news2024/7/6 1:26:42

文章目录

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

前言

对象池(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.coloradmin.cn/o/894290.html

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

相关文章

七夕节送礼物清单,总有一款他/她会喜欢!

马上就要到一年一度的七夕节了&#xff0c;你想好送给对方什么礼物了吗&#xff1f;送礼不一定是贵的好&#xff0c;但一定要表达出自己心意&#xff0c;也有人说&#xff0c;七夕不适合单身狗&#xff0c;其实是错的&#xff0c;单身狗正好可以趁七夕这个浪漫的节日&#xff0…

B站发布财报,正式会员数达2.14亿

KlipC报道&#xff1a;B站公布了截至2023年6月30日的未经审计的财务报告&#xff0c;据数据显示&#xff0c;B站总营收达同比增长8%达53.04亿元人民币&#xff0c;毛利润同比增长66%&#xff0c;其中广告业务收入同比增长36%达16亿人民币。财报发布后&#xff0c;B站美股盘前一…

深度云化时代,什么样的云网络才是企业的“心头好”?

科技云报道原创。 近年来企业上云的快速推进&#xff0c;对云网络提出了更多需求。 最初&#xff0c;云网络只是满足互联网业务公网接入。 随着移动互联网的发展&#xff0c;企业对云上网络安全隔离能力和互访能力、企业数据中心与云上网络互联、构建混合云的能力&#xff0…

骨传导耳机游泳能戴吗?骨传导游泳耳机哪个牌子好?

溽热的夏日&#xff0c;如果能够跳入水中畅游一番&#xff0c;那真的是再好不过了&#xff0c;既能强身健体&#xff0c;又能降温解暑。公共的游泳场馆人声鼎沸&#xff0c;像我这种“社恐”患者&#xff0c;如果在场馆中要待好几个小时&#xff0c;难免会觉得时间漫长&#xf…

韩国半导体巨头库存飙升,存储器市场面临挑战 | 百能云芯

最新财务报告揭示&#xff0c;韩国两大半导体巨头三星和SK海力士面临巨大的库存压力。截至今年6月底&#xff0c;两家公司的半导体库存金额已经飙升至超过50兆韩元&#xff0c;创下历史新高。这不仅显示了存储器市场库存过剩的严峻形势&#xff0c;也暗示着产业复苏步伐不容乐观…

Linux系统调试——valgrind内存泄露检测

代码可能存在内存泄露怎么办&#xff1f; 使用valgrind可以对代码进行内存泄露检测。 valgrind下载安装 下载&#xff1a;https://www.valgrind.org/downloads/ 安装&#xff1a; 1、tar –jxvf valgrind-3.21.0.tar.bz2 2、cd valgrind-3.21.0 3、./configure --prefix/ho…

GBU814-ASEMI逆变器专用整流桥GBU814

编辑&#xff1a;ll GBU814-ASEMI逆变器专用整流桥GBU814 型号&#xff1a;GBU814 品牌&#xff1a;ASEMI 芯片个数&#xff1a;1 封装&#xff1a;GBU-4 恢复时间&#xff1a;&#xff1e;50ns 工作温度&#xff1a;-55C~150C 浪涌电流&#xff1a;200A 正向电流&…

华为AR路由器配置双出口静态IP双链路负载

适用于&#xff1a;有多个以太WAN口的机型。 业务需求&#xff1a; 电信运营商1分配的接口pppoe拨号上网获取地址为116.24.65.19/32。 联通运营商2分配的接口pppoe拨号上网获取地址为10.59.7.238/32。 实现通过pppoe拨号获取动态IP双上行接入Internet&#xff0c;链路1和链…

[USACO1.5] 八皇后 Checker Challenge

题目描述 一个如下的 6 x 6 的跳棋棋盘&#xff0c;有六个棋子被放置在棋盘上&#xff0c;使得每行、每列有且只有一个&#xff0c;每条对角线&#xff08;包括两条主对角线的所有平行线&#xff09;上至多有一个棋子。 上面的布局可以用序列 2 4 6 1 3 5 来描述&#xff0c;第…

由于找不到msvcp120.dll怎么解决?哪个解决方案操作简单

我最近我的电脑遇到了一个问题&#xff0c;运行某些软件的时候出现了一个msvcp120.dll的错误提示。这个错误导致我无法正常运行一些程序&#xff0c;非常影响我的工作和娱乐体验。经过一番搜索和尝试&#xff0c;我最终成功修复了这个问题&#xff0c;现在我把修复方法分享给大…

【学习笔记之vue】使用Nginx运行vue

下载http://nginx.org/en/download.html 下一个稳定版本 加入环境变量 加入path中 在nginx解压路径下输入cmd 或者双击 之后erro显示&#xff1a;80端口冲突 下面我们将80默认启动端口改成90 双击nginx.exe&#xff0c;在浏览器中输入地址http://localhost:90/&#…

物联网设备的分类和功能阐述

物联网是将各种物理设备和传感器通过互联网进行连接和交互的网络&#xff0c;物联网的核心思想是让各种设备能够通过互联网实现智能化、自动化和远程控制。物联网设备是指连接到物联网中的各种设备&#xff0c;可以通过传感器、处理器、通信模块等技术实现与互联网的连接和数据…

中英双语对话大语言模型:ChatGLM-6B

介绍 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#xff0c;用户可以在消费级的显卡上进行本地部署&#xff08;INT4 量化级别下最低只需 6GB 显存&#xff09;。…

iOS16.0:屏幕旋转

此文写于2022年08月03日&#xff0c;距离iOS16正式版推出还有一个多月的时间&#xff0c;iOS16 beta版本有很多API的修改&#xff0c;今天讨论的是屏幕旋转&#xff0c;基于Xcode 14.0 beta4。 之前的屏幕旋转会报错&#xff1a; [Orientation] BUG IN CLIENT OF UIKIT: Settin…

计蒜客T1170——人民币支付

超级水&#xff0c;不解释&#xff0c;代码的处理方式减低了繁琐程度&#xff0c; #include <iostream> using namespace std;int main(int argc, char** argv) {int num0;cin>>num;int money[6]{100,50,20,10,5,1};for(int i0;i<5;i){int count0;countnum/mone…

⛳ Java - IO

目录 ⛳ Java - IO&#x1f3a8; 一、概述&#x1f463; 二、File类2.1、File 对象的构造函数&#xff1a;2.2、分隔符2.3、File类的获取功能2.4、File 类的重命名功能2.5、File类的判断功能2.6、File类的创建功能2.7、File类的删除功能2.8、功能练习代码 &#x1f69c; 三、IO…

JUC学习笔记(一)

1. JUC概述及回顾 1.1. JUC是什么&#xff1f; 在 Java 5.0 提供了 java.util.concurrent(简称JUC)包&#xff0c;在此包中增加了在并发编程中很常用的工具类。此包包括了几个小的、已标准化的可扩展框架&#xff0c;并提供一些功能实用的类&#xff0c;没有这些类&#xff0…

vue3 ref的使用、问题及源码分析;引用型变量和原始类型变量的复制值

文章目录 ref定义及作用用法源码 实验一 修改原变量和ref后的值原始数据类型对象类型总结 实验二 props的ref ref定义及作用 可以将 ref 看成 reactive 的一个变形版本&#xff0c;这是由于 reactive 内部采用 Proxy 来实现&#xff0c;而 Proxy 只接受对象作为入参&#xff0…

Mybatis的学习笔记

一、Mybatis的使用&#xff1a; 1.1 Mybatis的介绍&#xff1a; Mybatis是一款优秀的持久层&#xff08;负责将数据保存到数据库额那一层&#xff09;框架&#xff0c;用于简化JDBC的开发。 框架的概念是&#xff1a;框架是一个半成品的软件&#xff0c;是一套可重用的、通用…

Vue2入门学习汇总

1.介绍及安装 1.1 介绍 Vue是一套构建用户界面的渐进式框架。Vue只关注视图层&#xff0c;采用自底向上增量开发的设计。Vue的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。 学习vue之前主要掌握的知识&#xff1a;HTML、CSS、JavaScript、TypeScript …