【Unity小技巧】Unity自制对象池和官方内置对象池的使用

news2024/12/23 4:17:41

文章目录

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

前言

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

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

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://www.bilibili.com/video/BV1Su411E7b2

完结

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

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

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

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

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

相关文章

(stm32)低功耗模式

低功耗模式 执行哪个低功耗模式的程序判断流程 标志位设置操作一定要在WFI/WFE之前&#xff0c;调用此指令后立即进入睡眠判断流程 模式对比 睡眠模式 停止模式 待机模式

“绿心之眼”串联起“三大建筑”

本报记者 赵鹏 实习记者 池阳 通讯员 董浩程 步入夏末秋初的城市绿心森林公园&#xff0c;远远便看到在“三大建筑”间有块地面已悄然隆起&#xff0c;如同破土“种子”般正加速“萌发”。这枚生命之“种”泛着钢铁的颜色&#xff0c;一根根钢管编织起来&#xff0c;用叶脉的纹…

c语言每日一练(9)

前言&#xff1a;每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;暑假时三天之内必有一更&#xff0c;到了开学之后&#xff0c;将看学业情…

定位服务器CPU爆满的具体原因

1、查询CPU消耗的进程 使用top命令查看系统的CPU和内存使用情况 CPU一列是线程占用百分比 2、具体查看某个占分比大的进程 以为PId:7355为例&#xff0c; 执行top -Hp 7355&#xff0c;线程按照CPU使用率排序。 3、将线程PID转化为16进制 执行printf %x 7391&#xff0c;将…

三维模型OSGB格式轻量化重难点分析

三维模型OSGB格式轻量化重难点分析 在三维模型应用中&#xff0c;为了适应移动设备的硬件和网络限制等问题&#xff0c;OSGB格式轻量化处理已经成为一个重要的技术手段。但是&#xff0c;在实际应用中&#xff0c;OSGB格式轻量化仍然存在着一些重难点问题。下面将对这些问题进行…

视频云存储/安防监控/视频汇聚EasyCVR平台新增设备经纬度选取

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、…

Spring Clould 部署 - Docker

视频地址&#xff1a;微服务&#xff08;SpringCloudRabbitMQDockerRedis搜索分布式&#xff09; 初识Docker-什么是Docker&#xff08;P42&#xff0c;P43&#xff09; 微服务虽然具备各种各样的优势&#xff0c;但服务的拆分通用给部署带来了很大的麻烦。 分布式系统中&…

两个案例熟悉String的基本操作

1、第一个案例 Java语言规范要求完全相同的字符串字面量&#xff0c;应该包含同样的Unicode字符序列&#xff08;包含同一份码点序列的常量&#xff09;&#xff0c;并且必须是指向同一个String类实例。 package string; public class StringTest4 {public static void main(St…

【3Ds Max】挤出命令的简单使用(实现二维变三维)

简介 在3ds Max中&#xff0c;"挤出"&#xff08;Extrude&#xff09;是一种常用的建模操作&#xff0c;用于在平面或曲面上创建立体几何形状。以下是使用3ds Max中的挤出命令的基本步骤&#xff1a; 创建基本几何形状&#xff1a; 在3ds Max中创建一个基本的几何形…

免费开源的vue+express搭建的后台管理系统

此项目已开源 前端git地址&#xff1a;exp后台管理系统前端: exp后台管理系统前端 后端git地址&#xff1a;express后台管理系统: express后台管理系统 安装运行 npm i yarn i 前端: npm run dev | yarn dev 后端: npm run start | yarn start 主要技术栈 前端后端名称版本名…

Ant Design Pro 前端脚手架 配置混合导航

Ant Design Pro脚手架 点击查看阅读 混合导航&#xff1a; 顶部导航和侧边栏导航实现联动效果&#xff0c;点击不同的顶部导航按钮会显示对应的子菜单项。 实现点&#xff1a; 1. 路由的配置 菜单展示 我们可以在 route 中进行 menu 相关配置&#xff0c;来决定当前路由是否…

传递给Java方法的参数必须完成初始化

说明 在调用Java方法的时候&#xff0c;传递给方法的参数必须经过初始化&#xff0c;否则会编译报错。 指向对象的变量&#xff0c;指向一个对象实例就是初始化&#xff1b;赋值为null也是初始化。 代码示例 没有初始化的变量传递给方法编译报错&#xff0c;初始化的编译通过…

QTableWidget使用

QTableWidget介绍 QTableWidget是Qt框架中的一个表格控件&#xff0c;用于显示二维表格数据。它是基于QTableView和QStandardItemModel的封装&#xff0c;提供了更简单的接口和功能。 QTableWidget主要具有以下特点&#xff1a; 二维数据表示&#xff1a;QTableWidget以行和列…

ruoyi-cloud 服务间的调用,OpenFeign的使用

1. 在公共包内添加实体类 2.在 com.ruoyi.common.core.constant 添加如下代码 package com.ruoyi.common.core.constant; public class ServiceNameConstants {/*** 药材服务的serviceid &#xff08;生产者 nacos内注册应用名&#xff09;*/public static final String DRUG_…

AI极客日报0818 - AI帮助中风患者重新行走

&#x1f440;AI 日报合集 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; 有了人工智能&#xff0c;似乎没有什么是我们解决不了的。人工智能的一项新突破让瘫痪的中风幸存者能够通过“智能裤子”再次行走。让我们深入了解…… 今日要点: **&#x1f456;惊艳&#xff01…

QT实现天气预报

1. MainWindow类设计的成员变量和方法 public: MainWindow(QWidget* parent nullptr); ~MainWindow(); protected: 形成文本菜单来用来右键关闭窗口 void contextMenuEvent(QContextMenuEvent* event); 鼠标被点击之后此事件被调用 void mousePressEvent(QMouseEv…

系统架构设计师之软件架构风格

系统架构设计师之软件架构风格

学习心得02:QT6

以前也多少接触过QT。只是因为工作并不需要深入了解&#xff0c;所以是简单试用。现在有时间了&#xff0c;专门买了本书&#xff0c;从头到尾看了一番。因为是补充知识&#xff0c;所以范例、操作也没有实际操作。 QT使用的语言是C。比较特殊的地方是信号和槽。

11----图片

在Markdown中&#xff0c;可以通过简单的语法插入图片。 一、普通的添加图片&#xff1a;![图片描述](图片链接) 下面的代码&#xff0c;在上一节生成超链接的代码&#xff08;链接到网站&#xff09;前面添加一个感叹号(!)&#xff0c;同时把链接换成图片地址。 其中&#xf…

[PCIE 5.0] 第5代PCIe SSD 发展前沿(2023 Q2 更新)

声明 主页:元存储的博客_CSDN博客 依公开知识及经验整理,禁止转载,如有误请留言。 1 什么是 PCIe 5.0? 第 5 代快速周边组件互连称为 PCI Express 5.0。它也称为第 5 代 PCIe、PCIe 5、PCI v5 或简称为 PCIe 5.0。 2 PCIE5.0 速度 2.1 PCIE5.0 极限速度 从PCIe 4.0更新到…