UnityVR--Managers--对象池1

news/2025/2/22 6:09:42/

  本篇中使用的API:gameObject.CompareTag("标签")、UnityEvent()事件管理、ObjectPool<GameObject>()对象池

  参照unity官方教程:Hi ObjectPool 


目录

1. 应用场景

   2. 对象池的原理

  3. 查看资源消耗情况

  4. 不使用对象池实现的情形

  5. 使用对象池

  6. 是否使用对象池的比较


1. 应用场景

  对象池管理的作用场景是当短时间出现大量对象时,合理管理对象的出现、消亡,从而优化内存、GPU性能管理,不至于出现卡顿等现象,这在基于移动平台的场景中尤为明显。比如说下面这个游戏结束时,大量奖品突然涌现的画面:

  另外,还有在游戏中经常看到的,使用到大量子弹、受到大量怪兽攻击等的画面。

   2. 对象池的原理

  在上面的场景中,如果在短时间内大量地创建和销毁重复的对象,就会消耗大量资源,并产生大量的垃圾。在垃圾回收的时候,会中断执行程序代码,直到回收完成。这个时间可能持续1ms~几百ms,在PC上可能直观感受不明显,但到移动端、VR一体机或者其他低端终端运行时就会看到游戏的卡顿。

  因此,现在的Unity使用了ObjectPool的思想,即把场景中需要大量出现的GameObject存入一个数据集(字典或数组),就像一个池子一样。需要在场景中呈现时就激活它——SetActive(true),而不是重新去创建,不用了就让它失活——SetActive(false),而不是销毁它。  

  3. 查看资源消耗情况

  以下比较使用和不使用对象池的情况,对于运行时资源占用的情况,可以在菜单Windows->Analysis->Profiler查看,可以选择Scripts(脚本)和GarbageCollector(垃圾回收),重点查看这两项:

  

  4. 不使用对象池实现的情形

  如果不使用对象池管理,那么在Update()中会不断地使用Instantiate实例化物体,并且用Destroy()销毁物体,会让系统不断地分配内存和回收内存。下面写一个不使用对象池管理的例子,包含Trophy.cs和TrophyManager.cs两段代码。

  (1)建立简单测试场景:在场景中建立一个空节点(Trophy),把需要大量显示的对象都放在它底下,这些对象每一个都要假设Rigidbody和Collider,等下需要它们触地消失。

  

   (2)Trophy代码:动态地挂在每个奖品上,其中:

    定义一个事件destroyEvent,用于触发TrophyManager中的销毁(Destroy)回调;

    使用一个OnCollisionEnter控制奖品接触其他碰撞器后, 激活destroyEvent事件。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;//使用事件时,需要加载这个命名空间
//对象:控制每个奖品,动态地挂载在每一个礼物上
//作用:让礼物碰到其他碰撞器就消失public class Trophy : MonoBehaviour
{public UnityEvent destroyEvent=new UnityEvent();//公开定义一个用于销毁的事件,在TrophyManager中监听它并调用Destroypublic  bool isDestroy = false;//判断是否已经销毁,以免重复触碰不同的Collider多次销毁public void OnEnable(){//Trophy有效时(不管是激活的还是新创建的),先把isDestroy标记置为falseisDestroy = false;}private void OnCollisionEnter(Collision collision){//碰撞事件if(collision.gameObject.CompareTag("Ground") &&!isDestroy){//如果碰到了标签为"Ground"的碰撞器,且Trophy本身也是有效状态isDestroy = true;destroyEvent?.Invoke(); //触发销毁事件}}
}

   (3)TrophyManager代码:这里只要做两个步骤:第一步,创建新的Trophy;第二步:监听到销毁事件后,销毁Trophy。

public class TrophyManager : MonoBehaviour
{public GameObject[] trophies; //建立一个数组,用于存放所有的trophy对象public int number = 50; //定义在场上新建的trophy对象的数量void Update(){        for(int i=0;i<number;i++){var trophy=Instantiate(trophies[Random.Range(0, trophies.Length)], transform);//实例化对象到父物体(挂载TrophyManager脚本的对象)下trophy.transform.localPosition= Random.insideUnitSphere;//Random.insideUnitSphere返回半径为1的球体内的一个随机点trophy.AddComponent<Trophy>().destroyEvent.AddListener(() =>{   //监听Trophy脚本中的销毁事件Destroy(Trophy);});

  这个脚本挂在所有奖品的父节点上,并将所有奖品对象拖入数组当中。这样就能执行礼物喷涌的效果。

  5. 使用对象池

  使用Unity定义的对象池,需要在开始使用一下命名空间:using UnityEngine.Pool;

  Trophy.cs代码不做更改,TrophyManager.cs代码修改如下

public class TrophyManager : MonoBehaviour
{public GameObject[] trophies;public int number = 50;private ObjectPool<GameObject> trophyPool; //定义一个对象池void Start(){trophyPool = new ObjectPool<GameObject>(createFunc: () =>{   //建立Trophy的对象池trophyPool,类型为<GameObject>类型//设置对象池的回调var trophy = Instantiate(trophies[Random.Range(0, trophies.Length)], transform);trophy.AddComponent<Trophy>().destroyEvent.AddListener(call: () =>{   //监听Trophy脚本的destroyEvent事件trophyPool.Release(trophy);  //不销毁,而是在对象池中回收});return trophy;},actionOnGet: (go) =>{  //调用时激活go.SetActive(true);go.transform.localPosition = Random.insideUnitSphere;},actionOnRelease: (go) =>{  //失活go.SetActive(false);},actionOnDestroy: (go) =>{  //销毁Destroy(go);});}

  这个λ表达式比较长,主要是因为对象池的声明就很长,包括了建立对象池、调用、释放、销毁、检查内容、初始容量、最大容量这些参数:

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)

  6. 是否使用对象池的比较

  如果在PC端运行,上面两段代码的区别并不能直观感受出来,可以打开Profiler查看一下资源占用的情况,下面左图是不使用对象池,右图使用了对象池,可以看到GC垃圾回收数量右边明显少于左边。

  

 


http://www.ppmy.cn/news/71607.html

相关文章

什么是日志文件

文章目录 什么是日志文件Centos 7 日志文件简易说明日志文件的重要性Linux常见的日志文件文件名/var/log/boot.log/var/log/cron/var/log/dmesg/var/log/lastlog/var/log/maillog或 /var/log/mail/*/var/log/messages/var/log/secure/var/log/wtmp、/var/log/faillog/var/log/h…

Go语言网络编程:TCP、HTTP、Gin、WebSocket、RPC、gRPC入门实例

Go语言网络编程&#xff1a;TCP、HTTP、Gin、WebSocket、RPC、gRPC入门实例 在本文中&#xff0c;我们将介绍Go语言中的网络编程的不同方式&#xff0c;包括TCP、HTTP、Gin框架、WebSocket、RPC、gRPC的介绍与连接实例&#xff0c;并对所有示例代码都给出了详细的注释&#xf…

BoostSearch搜索引擎

今天讲的项目是基于C的Boost库的站内搜索引擎。因为Boost库内没有搜索关键字功能&#xff0c;所以在这里我们来手动实现一个这样的搜索引擎。当用户在输入框输入要查询的关键字后&#xff0c;就会快速查询出相关的 boost 库中的文档&#xff0c;弥补 boost 在线文档没有搜索功能…

使用 VS Code 快速搭建 ESP-IDF 开发环境 (Windows、Linux、MacOS)

ESP-IDF 是乐鑫官方的物联网开发框架&#xff0c;适用于 ESP32、ESP32-S、ESP32-C 和 ESP32-H 系列 SoC。它基于 C/C 语言提供了一个自给自足的 SDK&#xff0c;方便用户在这些平台上开发通用应用程序&#xff0c;并集成了大量的软件组件&#xff0c;包括 RTOS、外设驱动程序、…

SpringMVC简介、请求与响应、REST风格、SSM整合、拦截器

目录 SpringMVC简介 SpringMVC概述 入门案例 入门案例工作流程分析 Controller加载控制 PostMan 请求与响应 设置请求映射路径 五种类型参数传递 JSON数据传输参数 JSON对象数据 JSON对象数组 日期类型参数传递 响应 REST风格 REST风格简介 RESTful入门案例…

【分布式】一致性哈希和哈希槽

当我们拥有了多台存储服务器之后&#xff0c;现在有多个key&#xff0c;希望可以将这些个key均匀的缓存到这些服务器上&#xff0c;可以使用哪些方案呢&#xff1f; 1. 普通哈希取模法 1.1 直接哈希取模 这是一种最容易想到的方法&#xff0c;使用取模算法hash&#xff08;k…

SpringBoot中使用lombok

1.添加依赖 在项目的根目录中找到pom.xml&#xff0c;在dependencies下复制这段代码 <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifac…

10个高质量的简历制作网站推荐

刚经历完有金三银四&#xff0c;有没有因为简历不行&#xff0c;面试少的可怜的同学。 今天推荐10个高质量的简历制作网站&#xff0c;包括可以在线免费生成设计简历的网站。 1.即时设计资源社区 即时设计是国内首款专业级的 UI 设计工具&#xff0c;像 PC 端的网页&#xf…