前言
对象池技术在游戏开发中的应用非常普遍,它是一种高效管理对象实例的技术,能够避免频繁和重复创建对象所带来的性能开销。本篇文章我们就来探索一下如何在游戏开发中设计通用对象池,使之易于使用和扩展。
代码
代码目录结构
- ObjectPool
- Base
- Interface
- Settings
ObjectPool作为本模块的根目录,用于存储模块子目录和具体的对象池脚本。Base目录用于存储对象池抽象基类,用于规范对象池的设计。Interface目录用于存储对象池相关的接口,用于未来扩展。Settings目录用于存储创建对象池的参数脚本以及对象池的设置。
Base目录
BasePool.cs
cs
using System;
using System.Collections.Generic;
/// <summary>
/// 对象池基类
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
public abstract class BasePool<T>
{
/// <summary>
/// 对象池所生产对象的总数量
/// </summary>
public int totalCount { get; protected set; }
/// <summary>
/// 对象池当前空闲对象的数量
/// </summary>
public int freeCount => _pool.Count;
/// <summary>
/// 是否为固定容量的对象池
/// <para>默认值:False</para>
/// </summary>
public readonly bool isFixed;
/// <summary>
/// 对象池容量
/// <para>默认值:PoolConstant.DEFAULT_CAPACITY</para>
/// </summary>
public readonly int capacity;
/// <summary>
/// 对象创建逻辑
/// <para>提示:用来自定义对象的创建逻辑</para>
/// </summary>
public Func<T> overrideCreate;
/// <summary>
/// 对象重置逻辑
/// <para>提示:用来自定义对象的重置逻辑</para>
/// </summary>
public Func<T, T> overrideReset;
/// <summary>
/// 池对象
/// </summary>
protected readonly Stack<T> _pool;
/// <summary>
/// 对象类型是否为可释放对象类型
/// </summary>
protected static bool _isDisposable => _staticIsDisposable;
static readonly bool _staticIsDisposable = typeof(IDisposable).IsAssignableFrom(typeof(T));
/// <summary>
/// 对象类型名称
/// </summary>
protected static string _typeName => _staticTypeName;
static readonly string _staticTypeName = typeof(T).Name;
protected BasePool()
{
_pool = new Stack<T>(PoolConstant.DEFAULT_CAPACITY);
capacity = PoolConstant.DEFAULT_CAPACITY;
}
protected BasePool(int capacity)
{
if (capacity <= 0) throw new ArgumentException("Pool:The capacity is not allowed to be less than or equal to zero for the pool.");
_pool = new Stack<T>(capacity);
this.capacity = capacity;
}
protected BasePool(int capacity, bool isFixed)
{
if (capacity <= 0) throw new ArgumentException("Pool:The capacity is not allowed to be less than or equal to zero for the pool.");
_pool = new Stack<T>(capacity);
this.capacity = capacity;
this.isFixed = isFixed;
}
/// <summary>
/// 重置对象并返回
/// </summary>
protected abstract T Reset(T item);
/// <summary>
/// 创建对象
/// </summary>
protected abstract T Create();
/// <summary>
/// 获取对象
/// </summary>
public abstract T Get();
/// <summary>
/// 释放对象
/// </summary>
public abstract void Release(T item);
/// <summary>
/// 清空对象池
/// </summary>
public abstract void Clear();
}
Interface目录
......
Settings目录
PoolConstant.cs
cs
public static class PoolConstant
{
// 对象池默认容量
public const int DEFAULT_CAPACITY = 10;
}
UnityObjectPoolSettings.cs
cs
using UnityEngine;
/// <summary>
/// Unity对象池设置
/// </summary>
public class UnityObjectPoolSettings<T> where T : Object
{
/// <summary>
/// 对象池初始容量
/// </summary>
public int capacity = PoolConstant.DEFAULT_CAPACITY;
/// <summary>
/// 对象池是否持久化
/// </summary>
public bool isPersistant = true;
/// <summary>
/// 对象池是否固定容量
/// </summary>
public bool isFixed;
/// <summary>
/// 对象池容器
/// </summary>
public GameObject container;
/// <summary>
/// 对象原型
/// </summary>
public T original;
/// <summary>
/// 对象默认名称
/// </summary>
public string defaultName;
/// <summary>
/// 获取时激活对象
/// </summary>
public bool activeWhenGet = true;
}
具体的对象池
ClassPool.cs
cs
using System;
/// <summary>
/// Class 类型对象池
/// </summary>
/// <typeparam name="T">具体的 Class 类型</typeparam>
public class ClassPool<T> : BasePool<T>
where T : class
{
public ClassPool() { }
public ClassPool(int capacity) : base(capacity) { }
public ClassPool(int capacity, bool isFixed) : base(capacity, isFixed) { }
public override void Clear()
{
if (_isDisposable)
{
while (_pool.Count > 0)
{
if (_pool.Pop() is IDisposable ds)
ds?.Dispose();
}
}
else _pool.Clear();
totalCount = 0;
}
public override T Get()
{
T item;
if (freeCount > 0) item = _pool.Pop();
else
{
item = Create();
totalCount++;
}
return item;
}
public override void Release(T item)
{
if (item == null) return;
_pool.Push(Reset(item));
}
protected override T Reset(T item)
{
T v_item;
if (overrideReset != null) v_item = overrideReset(item);
else v_item = item;
return v_item == null ? item : v_item;
}
protected override T Create()
{
if (isFixed && totalCount == capacity)
throw new InvalidOperationException("Pool:The number of objects in the object pool has reached the upper limit.");
T item;
if (overrideCreate != null) item = overrideCreate();
else item = Activator.CreateInstance<T>();
if (item == null) throw new InvalidOperationException("Pool:The item created is null.");
return item;
}
}
UnityObjectPool.cs
cs
using System;
using UnityEngine;
/// <summary>
/// Unity对象池
/// </summary>
/// <typeparam name="T">Unity对象类型</typeparam>
public class UnityObjectPool<T> : ClassPool<T>, IDisposable
where T : UnityEngine.Object
{
protected readonly GameObject _container;
protected readonly T _original;
protected readonly string _defaultName;
protected readonly bool _activeWhenGet;
bool _isDisposed;
public UnityObjectPool()
{
_container = new GameObject($"{_typeName}Pool");
MonoBehaviour.DontDestroyOnLoad(_container);
_activeWhenGet = true;
}
public UnityObjectPool(int capacity) : base(capacity)
{
_container = new GameObject($"{_typeName}Pool");
MonoBehaviour.DontDestroyOnLoad(_container);
_activeWhenGet = true;
}
public UnityObjectPool(int capacity, bool isFixed) : base(capacity, isFixed)
{
_container = new GameObject($"{_typeName}Pool");
MonoBehaviour.DontDestroyOnLoad(_container);
_activeWhenGet = true;
}
public UnityObjectPool(UnityObjectPoolSettings<T> settings) :
base(settings == null ? PoolConstant.DEFAULT_CAPACITY : settings.capacity, settings != null && settings.isFixed)
{
if (settings == null)
{
_container = new GameObject($"{_typeName}Pool");
MonoBehaviour.DontDestroyOnLoad(_container);
return;
}
_container = settings.container;
_original = settings.original;
_defaultName = settings.defaultName;
_activeWhenGet = settings.activeWhenGet;
if (settings.isPersistant) MonoBehaviour.DontDestroyOnLoad(_container);
}
/// <summary>
/// 释放对象池
/// <para>提示:释放后对象池将无法继续使用</para>
/// </summary>
public void Dispose()
{
if (_isDisposed) return;
Dispose(true);
GC.SuppressFinalize(this);
}
public sealed override void Clear()
{
if (_isDisposed) return;
DoClear();
}
public sealed override void Release(T item)
{
if (_isDisposed) return;
DoRelease(item);
}
public sealed override T Get()
{
if (_isDisposed) return null;
return DoGet();
}
protected virtual void DoClear()
{
T item;
while (_pool.Count > 0)
{
item = _pool.Pop();
MonoBehaviour.Destroy(item);
}
totalCount = 0;
}
protected virtual void DoRelease(T item) { base.Release(item); }
protected virtual T DoGet() { return base.Get(); }
void Dispose(bool disposing)
{
if (_isDisposed) return;
_isDisposed = true;
if (disposing)
{
Clear();
MonoBehaviour.Destroy(_container);
}
}
~UnityObjectPool()
{
Dispose(false);
}
}
GameObjectPool.cs
cs
using System;
using UnityEngine;
/// <summary>
/// GameObject 对象池
/// </summary>
public sealed class GameObjectPool : UnityObjectPool<GameObject>
{
public GameObjectPool() { }
public GameObjectPool(int capacity) : base(capacity) { }
public GameObjectPool(int capacity, bool isFixed) : base(capacity, isFixed) { }
public GameObjectPool(UnityObjectPoolSettings<GameObject> settings) : base(settings) { }
protected override GameObject DoGet()
{
if (!_activeWhenGet) return base.DoGet();
else
{
GameObject item = base.DoGet();
item.SetActive(true);
return item;
}
}
protected override GameObject Reset(GameObject item)
{
GameObject v_item;
if (overrideReset != null) v_item = overrideReset(item);
else
{
v_item = item;
v_item.SetActive(false);
}
if (v_item == null) throw new InvalidOperationException("Pool:The item being reset is null.");
return v_item;
}
protected override GameObject Create()
{
if (isFixed && totalCount == capacity)
throw new InvalidOperationException("Pool:The number of objects in the object pool has reached the upper limit.");
GameObject item;
if (overrideCreate != null) item = overrideCreate();
else
{
if (_original == null) item = new GameObject();
else item = MonoBehaviour.Instantiate(_original);
if (item != null)
{
if (!string.IsNullOrEmpty(_defaultName)) item.name = _defaultName;
item.transform.SetParent(_container.transform);
item.SetActive(false);
}
}
if (item == null) throw new InvalidOperationException("Pool:The item being created is null.");
return item;
}
}
MonoPool.cs
cs
using System;
using UnityEngine;
/// <summary>
/// Monobehaviour 类型对象池
/// </summary>
/// <typeparam name="T">具体的 Monobehaviour 类型</typeparam>
public sealed class MonoPool<T> : UnityObjectPool<T>
where T : MonoBehaviour
{
public MonoPool() { }
public MonoPool(int capacity) : base(capacity) { }
public MonoPool(int capacity, bool isFixed) : base(capacity, isFixed) { }
public MonoPool(UnityObjectPoolSettings<T> settings) : base(settings) { }
protected override T DoGet()
{
if (!_activeWhenGet) return base.DoGet();
else
{
T item = base.DoGet();
item.enabled = true;
return item;
}
}
protected override T Reset(T item)
{
T v_item;
if (overrideReset != null) v_item = overrideReset(item);
else
{
v_item = item;
v_item.enabled = false;
}
if (v_item == null) throw new InvalidOperationException("Pool:The item being reset is null.");
return v_item;
}
protected override T Create()
{
if (isFixed && totalCount == capacity)
throw new InvalidOperationException("Pool:The number of objects in the object pool has reached the upper limit.");
T item;
if (overrideCreate != null) item = overrideCreate();
else
{
if (_original == null) item = _container.AddComponent<T>();
else item = MonoBehaviour.Instantiate(_original);
if (item != null)
{
if (!string.IsNullOrEmpty(_defaultName)) item.name = _defaultName;
item.enabled = false;
}
}
if (item == null) throw new InvalidOperationException("Pool:The item being created is null.");
return item;
}
}
测试
cs
using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
public class ObjectPoolTest
{
// 子弹
public class Bullet : MonoBehaviour
{
public Action<Bullet> onDestroy;
public DamageModel damageModel;
public void OnCustomTriggerEnter(string tag)
{
if (tag == "Head")
{
Debug.Log("Attack Head:" + damageModel.AttackHead());
onDestroy?.Invoke(this);
}
else if (tag == "Body")
{
Debug.Log("Attack Body:" + damageModel.AttackBody());
onDestroy?.Invoke(this);
}
}
}
// 伤害计算模型
public class DamageModel
{
public int damage;
public int AttackHead()
{
return damage * 2;
}
public int AttackBody()
{
return damage;
}
}
static readonly GameObjectPool bulletPool = new GameObjectPool();
static readonly ClassPool<DamageModel> damagePool = new ClassPool<DamageModel>();
static readonly string[] tags = { "Head", "Body" };
static ObjectPoolTest()
{
bulletPool.overrideReset = ResetBullet;
damagePool.overrideReset = ResetDamageModel;
}
static GameObject ResetBullet(GameObject go)
{
if (go.TryGetComponent(out Bullet bullet))
{
damagePool.Release(bullet.damageModel);
bullet.damageModel = null;
bullet.onDestroy = null;
}
go.SetActive(false);
return go;
}
static DamageModel ResetDamageModel(DamageModel dm)
{
dm.damage = 0;
return dm;
}
Bullet GetBullet()
{
GameObject go = bulletPool.Get();
if (!go.TryGetComponent(out Bullet bullet)) bullet = go.AddComponent<Bullet>();
DamageModel damageModel = damagePool.Get();
damageModel.damage = UnityEngine.Random.Range(10, 100);
bullet.damageModel = damageModel;
bullet.onDestroy = OnBulletDestroy;
return bullet;
}
void OnBulletDestroy(Bullet bullet)
{
Debug.Log("Bullet is being destroied.");
bulletPool.Release(bullet.gameObject);
}
[UnityTest]
public IEnumerator ObjectPool_Test()
{
int index = 0;
WaitForSeconds waitForSeconds = new WaitForSeconds(0.5f);
Stack<Bullet> temp = new Stack<Bullet>();
while (index < 9)
{
Debug.Log($"正在进行第{index + 1}次射击...");
int sendBulletCount = UnityEngine.Random.Range(1, 5);
for (int i = 0; i < sendBulletCount; i++)
{
Debug.Log($"正在生成第{i + 1}颗子弹...");
temp.Push(GetBullet());
}
Debug.Log($"生产子弹总量:{bulletPool.totalCount},子弹库存:{bulletPool.freeCount}");
int j = 0;
while (temp.Count > 0)
{
Debug.Log($"正在发射第{j + 1}颗子弹...");
temp.Pop().OnCustomTriggerEnter(tags[UnityEngine.Random.Range(0, 1)]);
j++;
}
yield return waitForSeconds;
index++;
}
yield return null;
Assert.IsTrue(true);
}
}
上述代码基于Unity Test Framework进行测试,模拟了9次射击,每次随机发射1-5颗子弹,随机设置每个子弹的基本伤害为10-100,用对象池技术管理子弹游戏对象实例和伤害计算模型实例。
分析
BasePool作为所有对象池的抽象基类,规范对象池的必要属性和方法。PoolConstant记录对象池所用的常量值。UnityObjectPoolSettings作为Unity对象池特有的对象池设置参数,在创建Unity对象池时传递。ClassPool作为C#类对象池。UnityObjectPool作为Unity对象池,继承自ClassPool。GameObjectPool作为Unity游戏对象池。MonoPool作为Monobehaviour对象池。
版本改进
......
系列文章
......
如果这篇文章对你有帮助,请给作者点个赞吧!