欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 时评 > 【Unity设计模式】使用对象池

【Unity设计模式】使用对象池

2024/10/24 21:21:00 来源:https://blog.csdn.net/milu_ELK/article/details/139918464  浏览:    关键词:【Unity设计模式】使用对象池

在这里插入图片描述


前言

最近在学习Unity游戏设计模式,看到两本比较适合入门的书,一本是unity官方的 《Level up your programming with game programming patterns》 ,另一本是 《游戏编程模式》

这两本书介绍了大部分会使用到的设计模式,因此很值得学习

本专栏暂时先记录一些教程部分,深入阅读后续再更新


文章目录

  • 前言
  • 什么是对象池
  • 关于生命周期管理


什么是对象池

对象池技术是老生常谈的一个概念了,很简单,无论是软件开发还是编程语言的学习中,我们都经常接触到池化技术。池化技术指的是将一些常用的变量或对象存储到一个公共池中,当我们需要调用这些变量或对象的时候,直接引用公共池中的对象而无需new一个新对象。例如管理socket的连接池,管理string的Intern字符串池,还有管理GameObject的对象池等等等等。

在Unity中,如果你想要实现发射子弹的功能,那么就需要在发射时实例化子弹预制件。但是如果使用创建Instantiate()和销毁Destory()方法来控制子弹在场景中的生命周期,这无疑是一个很浪费性能的策略,会导致游戏运行卡顿。

所以我们引入了对象池,我们在场景初始化的时候提前生成好指定数量的子弹实例,当我们发射子弹时,从对象池中取出物体,而销毁子弹时则将他们放回对象池。这样对性能的消耗就小多了。

优点是能够更好的管理物体,减少Cpu压力,减少对象生成占用堆空间导致的GC

对象池代码挺简单的,主要由三个部分组成:
1.对象池初始化代码
2.从对象池取出物体的代码
3.将物体放回对象池的代码

public class ObjPool<T> where T : new()
{private Stack<T> ObjStack;public ObjPool (int maxCount){ObjStack = new Stack<T>(maxCount);for (int i = 0; i < maxCount; i++){var obj = new T();ObjStack.Push(obj);}}public void InPool (T obj){ObjStack.Push(obj);}public T OutPool(){return ObjStack.Pop();}
}

很简单就能手搓一个对象池,当然上述代码只是随手写的,也可也根据自己需要来写一个对象池

为什么我不介绍unity的对象池呢,因为unity太幽默了,大部分版本不支持UnityEngine.Pool这个命名空间。不过可以贴出它的代码,相比于我们的代码,就是在对象池函数更完善了,且在执行时添加了一些委托方法以供触发。

using System;
using System.Collections.Generic;namespace UnityEngine.Pool
{/// <summary>///   <para>A stack based Pool.IObjectPool_1.</para>/// </summary>public class ObjectPool<T> : IDisposable, IObjectPool<T> where T : class{internal readonly List<T> m_List;private readonly Func<T> m_CreateFunc;private readonly Action<T> m_ActionOnGet;private readonly Action<T> m_ActionOnRelease;private readonly Action<T> m_ActionOnDestroy;private readonly int m_MaxSize;internal bool m_CollectionCheck;public int CountAll { get; private set; }public int CountActive => this.CountAll - this.CountInactive;public int CountInactive => this.m_List.Count;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){if (createFunc == null)throw new ArgumentNullException(nameof (createFunc));if (maxSize <= 0)throw new ArgumentException("Max Size must be greater than 0", nameof (maxSize));this.m_List = new List<T>(defaultCapacity);this.m_CreateFunc = createFunc;this.m_MaxSize = maxSize;this.m_ActionOnGet = actionOnGet;this.m_ActionOnRelease = actionOnRelease;this.m_ActionOnDestroy = actionOnDestroy;this.m_CollectionCheck = collectionCheck;}public T Get(){T obj;if (this.m_List.Count == 0){obj = this.m_CreateFunc();++this.CountAll;}else{int index = this.m_List.Count - 1;obj = this.m_List[index];this.m_List.RemoveAt(index);}Action<T> actionOnGet = this.m_ActionOnGet;if (actionOnGet != null)actionOnGet(obj);return obj;}public PooledObject<T> Get(out T v) => new PooledObject<T>(v = this.Get(), (IObjectPool<T>) this);public void Release(T element){if (this.m_CollectionCheck && this.m_List.Count > 0){for (int index = 0; index < this.m_List.Count; ++index){if ((object) element == (object) this.m_List[index])throw new InvalidOperationException("Trying to release an object that has already been released to the pool.");}}Action<T> actionOnRelease = this.m_ActionOnRelease;if (actionOnRelease != null)actionOnRelease(element);if (this.CountInactive < this.m_MaxSize){this.m_List.Add(element);}else{Action<T> actionOnDestroy = this.m_ActionOnDestroy;if (actionOnDestroy != null)actionOnDestroy(element);}}public void Clear(){if (this.m_ActionOnDestroy != null){foreach (T obj in this.m_List)this.m_ActionOnDestroy(obj);}this.m_List.Clear();this.CountAll = 0;}public void Dispose() => this.Clear();}
}

关于生命周期管理

还是射击游戏的例子,现在我们要枪射出子弹,代码设计如下:

1.创建一个枪类
2.在枪类中创建一个接受子弹类的对象池
3.创建n个子弹类的预制体

那么我们该如何管理它们的生命周期?或者说上述三个类哪些需要执行MonoBehaviour的Update方法?

首先,对象池类肯定不执行Update方法,它就不该继承MonoBehaviour,我们只需要让它实现管理物体进出池的方法,对物体本身生命周期的执行不该由他执行。

子弹类可以实现Update方法吗?由于子弹类是GameObject,所以通常需要继承MonoBehaviour,但不代表生命周期函数一定由子弹类自身执行。很简单的道理,因为每个Update的存在都是对Unity 的性能消耗,假设场景里有十把枪,每把发射100发子弹,那么总计1000发子弹就得执行1000个Update方法!显然这是不合适的。

所以正常的方式是让枪类实现Update方法,并在枪类中对子弹应做的Update事件进行处理。这样10把枪就只需要10个update就能管理1000发子弹。

using UnityEngine.Pool;public class RevisedGun : MonoBehaviour
{// stack-based ObjectPool available with Unity 2021 and aboveprivate IObjectPool<RevisedProjectile> objectPool;// throw an exception if we try to return an existing item, already in the pool[SerializeField] private bool collectionCheck = true;// extra options to control the pool capacity and maximum size[SerializeField] private int defaultCapacity = 20;[SerializeField] private int maxSize = 100;private void Awake(){objectPool = new ObjectPool<RevisedProjectile>(CreateProjectile,OnGetFromPool, OnReleaseToPool, OnDestroyPooledObject,collectionCheck, defaultCapacity, maxSize);}// invoked when creating an item to populate the object poolprivate RevisedProjectile CreateProjectile(){RevisedProjectile projectileInstance = Instantiate(projectilePrefab);projectileInstance.ObjectPool = objectPool;return projectileInstance;}// invoked when returning an item to the object poolprivate void OnReleaseToPool(RevisedProjectile pooledObject){pooledObject.gameObject.SetActive(false);}// invoked when retrieving the next item from the object poolprivate void OnGetFromPool(RevisedProjectile pooledObject){pooledObject.gameObject.SetActive(true);}// invoked when we exceed the maximum number of pooled items (i.e. destroy the pooled object)private void OnDestroyPooledObject(RevisedProjectile pooledObject){Destroy(pooledObject.gameObject);}private void FixedUpdate(){}
}

还有一点,就是如果多个对象引用同一个对象池,那么我们在实现对象池代码的时候还需要注意类型安全,线程安全等特性。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com