游戏中的对象池技术探索(一)

ops/2024/10/9 3:54:25/

前言

对象池技术在游戏开发中的应用非常普遍,它是一种高效管理对象实例的技术,能够避免频繁和重复创建对象所带来的性能开销。本篇文章我们就来探索一下如何在游戏开发中设计通用对象池,使之易于使用和扩展。

代码

代码目录结构
  • ObjectPool
    • Base
    • Interface
    • Settings

ObjectPool作为本模块的根目录,用于存储模块子目录和具体的对象池脚本。Base目录用于存储对象池抽象基类,用于规范对象池的设计。Interface目录用于存储对象池相关的接口,用于未来扩展。Settings目录用于存储创建对象池的参数脚本以及对象池的设置。

Base目录

BasePool.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

public static class PoolConstant
{// 对象池默认容量public const int DEFAULT_CAPACITY = 10;
}

UnityObjectPoolSettings.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

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

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

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

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;}
}

测试

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对象池

版本改进

......

系列文章

......

如果这篇文章对你有帮助,请给作者点个赞吧!


http://www.ppmy.cn/ops/122968.html

相关文章

Pikachu-unsafe upfileupload-getimagesize

什么是getimagesize()&#xff1f; getimagesize()是PHP中用于获取图像的大小和格式的函数。它可以返回一个包含图像的宽度、高度、类型和MIME类型的数组。 由于返回的这个类型可以被伪造&#xff0c;如果用这个函数来获取图片类型&#xff0c;从而判断是否时图片的话&#xff…

Pigar:Python 项目的依赖管理利器

&#x1f31f; 引言 在Python项目开发过程中&#xff0c;依赖管理是一个不可忽视的环节。一个精确且易于维护的requirements.txt文件对于项目的部署和协作至关重要。今天&#xff0c;我们将介绍一款名为Pigar的自动生成requirements.txt文件的依赖管理工具&#xff0c;它通过一…

OpenAI在周四推出了一种与ChatGPT互动的新方式——一种名为“Canvas”的界面

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

掌握C#核心概念:类、继承、泛型等

C# 是一门功能强大且灵活的面向对象编程语言&#xff0c;它结合了许多现代编程语言的特点和特性。无论你是编程新手&#xff0c;还是有经验的开发者&#xff0c;理解C#中的核心概念都是非常重要的。本文将介绍C#中的类与对象、构造函数和析构函数、方法的重载与重写、继承与多态…

『网络游戏』窗口基类【06】

创建脚本&#xff1a;WindowRoot.cs 编写脚本&#xff1a; 修改脚本&#xff1a;LoginWnd.cs 修改脚本&#xff1a;LoadingWnd.cs 修改脚本&#xff1a;ResSvc.cs 修改脚本&#xff1a;LoginSys.cs 运行项目 - 功能不变 本章结束

算法知识点————贪心

贪心&#xff1a;只考虑局部最优解&#xff0c;不考虑全部最优解。有时候得不到最优解。 DP&#xff1a;考虑全局最优解。DP的特点&#xff1a;无后效性&#xff08;正在求解的时候不关心前面的解是怎么求的&#xff09;&#xff1b; 二者都是在求最优解的&#xff0c;都有最优…

rust gio-rs 挂载 samba 磁盘

linux 使用的 gio 管理工具 这个工具如下 这是 gio 的rust版本 https://crates.io/crates/gio 可以用 rust 语言实现下面所有操作 gio mout 挂载 samba 如下 //https://valadoc.org/gio-2.0/GLib.MountOperation.html pub async fn gio_mount(uri路径:&str, 用户名:Opti…

CNAI趋势下,打造一体化AI赋能平台

在数字化转型的浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;已成为推动企业创新和转型的核心力量。云原生技术以其灵活性和可扩展性&#xff0c;为AI的应用和发展提供了坚实的基础。本文将探讨云原生人工智能&#xff08;CNAI&#xff09;如何为企业带来颠覆性的变革…