大家都知道在一些游戏中常常要创建大量的游戏对象,如果这些对象长期占用一些内存而没有触发垃圾回收机制(以下简称GC)或者过于频繁的触发GC就会导致游戏的帧数暴跌,在移动设备直接造成卡死的现象,那引用对象池的概念,能让这些游戏对象在刚开始的时候就被初始实例化而不会在游戏中频繁生成也不用触发垃圾回收机制,相当于对性能极大的提升,这些都是Unity非常经典的模式,那么在Unity2021.2以后的版本Unity终于自己创了一个新的命名空间UnityEngine.Pool不用玩家再自己造轮子了,下面跟着B站一位大佬Up学习了如何引用该命名空间,
这里贴个连接:
【Unity 2021】对象池API | 对象池模式 | Unity提高教程 | Object Pool_哔哩哔哩_bilibili
Github:GitHub - https://github.com/AtCloudStudio/UnityObjectPoolTutorial
官方的API:Unity - Scripting API: ObjectPool
进入官网的API可以看到这个命名空间包含了多个数据结构的对象池,

这里就用最常用的ObjectPool
然后选择一个2021.2之后的版本,我用的是长期支持的LTS3.8
创建一个项目进入后然后把素材导进来,如果你想自己动手做的话Scripts里面的就别放进来了,然后把无法识别的脚本的组件都删了,因为我们要动手跟着大佬做一遍,
我们先创建一个名字叫Gem的脚本,然后挂载到Gem Base的父预制体,这样其它的宝石预制体都会挂载他。
创建一个委托以及一个计时器要来记录什么时候执行委托,当碰到标签为Floor的地板后就触发开始计时器的bool
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class Gem : MonoBehaviour
- {
- [SerializeField] private float lifeAfterLanding = 2f; //着陆后过了两秒就自动消除
- private float deactiveTimer;
- private bool hasLanded;
- System.Action
deactiveAcion; -
- void Update()
- {
- if (!hasLanded)
- return;
- deactiveTimer += Time.deltaTime;
- if(deactiveTimer >= lifeAfterLanding)
- {
- deactiveTimer = 0;
- deactiveAcion.Invoke(this); // 执行这个委托
- }
-
- }
-
- private void OnTriggerEnter(Collider other)
- {
- if (other.gameObject.CompareTag("Floor"))
- {
- hasLanded = true;
- }
- }
-
- public void SetDeactiveAction(System.Action
deactiveAcion ) - {
- this.deactiveAcion = deactiveAcion;
- }
- }
再创建一个叫GemSpawnNormalVersion的脚本
这个用来控制生成宝石的。别忘了在生成宝石的函数最后订阅这个委托并给它要执行的事件
- using UnityEngine;
-
- public class GemSpawnNormalVersion : MonoBehaviour
- {
- [SerializeField] private Gem[] gemPrefabs;
- [SerializeField] private int spawnAmounts = 50;
- [SerializeField] private float gemSpawnInterval;
- private float gemSpawnTimer;
-
- private void Update()
- {
- gemSpawnTimer += Time.deltaTime;
-
- if(gemSpawnTimer >= gemSpawnInterval)
- {
- gemSpawnTimer = 0;
- Spawn();
- }
- }
-
- private void Spawn()
- {
- for (int i = 0; i < spawnAmounts; i++)
- {
- var randomIndex = Random.Range(0, gemPrefabs.Length);
- var prefab = gemPrefabs[randomIndex]; //随机生成某种类型的宝石
- var gem = Instantiate(prefab, transform); //把这个宝石生成器对象作为生成宝石的脚本
-
- gem.transform.position = transform.position + Random.insideUnitSphere * 2;
- gem.SetDeactiveAction(delegate { Destroy(gameObject);});
- }
- }
- }
再窗口配置好后,

这时候运行游戏,没问题12钟宝石随机生成,在地面过了两秒后触发垃圾回收机制GC,但你的电脑有没有红温呢,反正我笔记本电脑温度高的一批,我就不截图了怕我电脑烧了。
我们创建一个GemPool的脚本挂载上去。
请结合代码看我的解释,我们引用新命名空间,using UnityEngine.Pool;
然后创建一个ObjectPool

- using UnityEngine;
- using UnityEngine.Pool;
-
- public class GemPool : MonoBehaviour
- {
- [SerializeField] private Gem prefab;
- [SerializeField] private int minCapcitySize =10;//这两个变量用来定义对象池也就是栈的存储空间
- [SerializeField] private int maxCapcitySize = 100;
- [SerializeField] private int activeCount => pool.CountActive;
- [SerializeField] private int inacitveCount => pool.CountInactive;
- [SerializeField] private int totalCount => pool.CountAll;
- ObjectPool
pool; - //对象池仅仅发生激活和非激活状态之间的切换,只有调用ObjectPool.Clear()或者Dispose()才会清除对象池中的元素
- private void Awake()
- {
- pool = new ObjectPool
(OnCreatePoolItem, OnGetPoolItem, OnReleasePoolItem, OnDestoryPoolItem, true, minCapcitySize, maxCapcitySize); - }
- private void Update()
- {
- var gem = pool.Get();
- gem.transform.position = transform.position + Random.insideUnitSphere * 2;
- }
-
- private void OnDestoryPoolItem(Gem obj)
- {
- Destroy(obj.gameObject);
- }
-
- private void OnReleasePoolItem(Gem obj)
- {
- obj.gameObject.SetActive(false);
- }
-
- private void OnGetPoolItem(Gem obj)
- {
- obj.gameObject.SetActive(true);
- }
-
- private Gem OnCreatePoolItem()
- {
- var gem = Instantiate(prefab, transform);
- gem.SetDeactiveAction(delegate { pool.Release(gem); }); //在实例化宝石后再调用release函数回收这个宝石
-
- return gem;
- }
- }
别忘了调用委托SetDeactiveAction先让实例化的对象返回对象池中。

这里随机选择一个预制体,运行游戏可以看到有一部分就在禁用状态。
那么我们怎么推广所有宝石预制体呢?很简单,只需要用数组给Gem类加个数组用foreach依次生成即可,那对于游戏中所有要用到的预制体呢,他们可没有用Gem这个类。
这样我们要造新轮子写个基类让所有要用到对象池的游戏对象继承使用即可,
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.Pool;
-
- public class BasePool<T> : MonoBehaviour where T : Component
- {
- [SerializeField] protected T prefab;
- [SerializeField] int minSize =100;
- [SerializeField] int maxSize = 500;
- public int activeCount => pool.CountActive;
- public int inacitveCount => pool.CountInactive;
- public int totalCount => pool.CountAll;
-
- ObjectPool
pool; -
- public void Initialize(bool checkPoolSize = true)
- {
- pool = new ObjectPool
(OnCreatePoolItem, OnGetPoolItem, OnReleasePoolItem, OnDestoryPoolItem, checkPoolSize, minSize, maxSize); - }
-
- protected virtual void OnDestoryPoolItem(T obj)
- {
- Destroy(obj.gameObject);
- }
-
- protected virtual void OnReleasePoolItem(T obj)
- {
- obj.gameObject.SetActive(false);
- }
-
- protected virtual void OnGetPoolItem(T obj)
- {
- obj.gameObject.SetActive(true);
- }
-
- protected virtual T OnCreatePoolItem() => Instantiate(prefab, transform);
-
- public void Get() => pool.Get();
- public void Release(T obj) => pool.Release(obj);
-
- public void Clear() => pool.Clear();
-
- }

这样回到GemPool的脚本只需要继承这个类再重写两个函数即可
- using UnityEngine;
- using UnityEngine.Pool;
-
- public class GemPool : BasePool<Gem>
- {
-
- private void Awake()
- {
- Initialize();
- }
- private void Update()
- {
- Get();
- }
- protected override Gem OnCreatePoolItem()
- {
- var gem = base.OnCreatePoolItem();
- gem.SetDeactiveAction(delegate { Release(gem); }); //在实例化宝石后再调用release函数回收这个宝石
-
- return gem;
- }
-
- protected override void OnGetPoolItem(Gem gem)
- {
- base.OnGetPoolItem(gem);
- gem.transform.position = transform.position + Random.insideUnitSphere * 2;
- }
-
- public void SetGemPrefab(Gem prefab)
- {
- this.prefab = prefab;
- }
- }
创建一个新脚本叫GemSpawnNormalVersion
首先是Gem【】数组用来管理每一种宝石的生成,在Start函数中为他们每个创造一个父对象poolHolder,这样方便管理各个种类的宝石,然后直到poolHolder挂载GemPool脚本并且为这个脚本上设置好它专属的Gem类(不然为空会报错)就可以激活它了,别忘了赋在我们的链表List

- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class GemSpawnPoolVersion : MonoBehaviour
- {
- [SerializeField] private Gem[] gemPrefabs;
- [SerializeField] private int spawnAmounts =50;
- [SerializeField] private float gemSpawnInterval;
- private float gemSpawnTimer;
-
- List
gemPools = new List(); - private void Start()
- {
- foreach (var gemPrefab in gemPrefabs)
- {
- var poolHolder = new GameObject($"Pool:{gemPrefab.name}");
-
- poolHolder.transform.parent = transform;
- poolHolder.transform.position = transform.position;
- poolHolder.SetActive(false);
-
- var pool = poolHolder.AddComponent
(); -
- pool.SetGemPrefab(gemPrefab);
- poolHolder.SetActive(true);
- gemPools.Add(pool);
- }
- }
-
- private void Update()
- {
- gemSpawnTimer += Time.deltaTime;
-
- if (gemSpawnTimer >= gemSpawnInterval)
- {
- gemSpawnTimer = 0;
- Spawn();
- }
- }
-
- private void Spawn()
- {
- for (int i = 0; i < spawnAmounts; i++)
- {
- var randomIndex = Random.Range(0, gemPrefabs.Length);
- var pool = gemPools[randomIndex];
- pool.Get();
- }
- }
- }

挂载后运行,无论你的SpawnAmounts调的多离谱都会帧数稳定很多了。(30帧左右吧)
这样性能就能妥善解决了大性能小号稳定
学习了怎么使用新命名空间UnityEngine.Pool,并且了探讨了更加优化版本的正确对象池使用。