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

前言

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

代码

代码目录结构
  • 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.rhkb.cn/news/440051.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity MVC框架演示 1-1 理论分析

本文仅作学习笔记分享与交流&#xff0c;不做任何商业用途&#xff0c;该课程资源来源于唐老狮 1.一般的图解MVC 什么是MVC我就不说了&#xff0c;老生常谈&#xff0c;网上有大量的介绍&#xff0c;想看看这三层都起到什么职责&#xff1f;那就直接上图吧 2.我举一个栗子 我有…

人工智能新闻和发展 (24001)- By 10/4/2024

1. 谷歌增强了搜索中的人工智能&#xff0c;允许对图像进行语音提问。 Google adding AI to answer voiced questions about images | AP NewsGoogle is pumping more artificial intelligence into its search engine. New features will enable people to voice questions a…

15分钟学 Python 第39天:Python 爬虫入门(五)

Day 39&#xff1a;Python 爬虫入门数据存储概述 在进行网页爬虫时&#xff0c;抓取到的数据需要存储以供后续分析和使用。常见的存储方式包括但不限于&#xff1a; 文件存储&#xff08;如文本文件、CSV、JSON&#xff09;数据库存储&#xff08;如SQLite、MySQL、MongoDB&a…

无神论文解读之ControlNet:Adding Conditional Control to Text-to-Image Diffusion Models

一、什么是ControlNet ControlNet是一种能够控制模型生成内容的方法&#xff0c;能够对文生图等模型添加限制信息&#xff08;边缘、深度图、法向量图、姿势点图等&#xff09;&#xff0c;在当今生成比较火的时代很流行。 这种方法使得能够直接提供空间信息控制图片以更细粒…

PCL 1.8.1 + VTK 1.8.0 + QT5.14.2+ VS2017 环境搭建

先看看效果: PCL 1.8.1下载安装: Tags PointCloudLibrary/pcl GitHub 安装完成后: 如果VTK想重新编译的,可以看我的这篇博客:

Spring14——案例:利用AOP环绕通知计算业务层接口执行效率

前面介绍了这么多种通知类型&#xff0c;具体该选哪一种呢? 我们可以通过一些案例加深下对通知类型的学习。 34-案例&#xff1a;利用AOP环绕通知计算业务层接口执行效率 需求分析 这个需求也比较简单&#xff0c;前面我们在介绍AOP的时候已经演示过: 需求:任意业务层接口…

冯诺依曼体系|操作系统

目录 一、硬件&#xff1a;冯诺依曼体系 1.冯诺依曼体系结构 2.冯诺依曼体系结构组成 3.内存的重要性 &#xff08;1&#xff09;提升运行速度 &#xff08;2&#xff09;提升运行效率 二、软件&#xff1a;操作系统 1.什么是操作系统 &#xff08;1&#xff09;内部理…

【GeekBand】C++设计模式笔记6_Decorator_装饰模式

1. “单一职责”模式 在软件组件的设计中&#xff0c;如果责任划分的不清晰&#xff0c;使用继承得到的结果往往是随着需求的变化&#xff0c;子类急剧膨胀&#xff0c;同时充斥着重复代码&#xff0c;这时候的关键是划清责任。典型模式 DecoratorBridge 2. Decorator 装饰模…

地理空间数据存储与处理:MySQL空间数据类型的优化与应用!

在 MySQL 数据库中&#xff0c;空间数据类型用于存储和处理地理空间数据。这些数据类型允许我们在开发时可在数据库中存储和操作地理位置、几何形状和地理空间关系等信息。 一、什么是空间数据类型 MySQL 中的空间数据类型主要包括以下几种&#xff1a; GEOMETRY&#xff1a…

iMazing只能苹果电脑吗 Win和Mac上的iMazing功能有区别吗

在当今数字时代&#xff0c;管理和备份手机数据变得越来越重要。无论是转移照片、备份短信&#xff0c;还是管理应用程序&#xff0c;一个强大的工具可以大大简化这些操作。iMazing作为一款备受好评的iOS设备管理软件&#xff0c;已经成为许多用户的选择。但是&#xff0c;许多…

SpringBoot+ElasticSearch7.12.1+Kibana7.12.1简单使用

案例简介 本案例是把日志数据保存到Elasticsearch的索引中&#xff0c;并通过Kibana图形化界面的开发工具给查询出来添加的日志数据&#xff0c;完成从0到1的简单使用 ElasticSearch职责用法简介 ElasticSearch用在哪 ElasticSearch在我这个案例中&#xff0c;不是用来缓解增…

STM32编码器接口解析及抗噪声措施探讨

1. 引言 在现代控制系统中&#xff0c;编码器扮演着非常重要的角色。它就像一个精密的测量工具&#xff0c;可以告诉我们机械部件的位置和运动状态。在STM32微控制器中&#xff0c;编码器接口可以轻松地与各种编码器连接&#xff0c;实现精确的控制。我将在这里探讨STM32编码器…

图文深入理解Oracle Network配置管理(一)

List item 本篇图文深入介绍Oracle Network配置管理。 Oracle Network概述 Oracle Net 服务 Oracle Net 监听程序 <oracle_home>/network/admin/listener.ora <oracle_home>/network/admin/sqlnet.ora建立网络连接 要建立客户机或中间层连接&#xff0c;Oracle…

sublime配置(竞赛向)

我也想要有jiangly一样的sublime 先决条件 首先&#xff0c;到官网上下载最新的sublime4&#xff0c;然后在mingw官网上下载最新的mingw64 mingw64官网&#xff1a;左边菜单栏点击dowloads,然后选择MinGW-W64-builds(可能会有点慢)——然后有时候会变成选LLVM-minGW,接着选择…

人工智能专业就业方向与前景

随着产业结构升级的持续推进&#xff0c;未来行业领域对于人工智能专业人才的需求量会逐渐增加&#xff0c;一部分高校也开始陆续在本科阶段开设人工智能专业&#xff0c;以缓解人工智能领域人才缺口较大的问题。下面是小编整理的人工智能专业就业方向与前景&#xff0c;欢迎阅…

数据结构阶段测试2的一点小补充

数据结构阶段测试2的一点小补充 1.已知⼩根堆为8,15,10,21,34,16,12&#xff0c;删除关键字8之后需重建堆&#xff0c;最后的叶⼦ 节点为() A. 34 B. 21 C. 16 D. 12 解题思路 向下调整算法删除堆顶元素 &#x1f4a1; 答案&#xff1a;C 删除堆顶元素的思路&#xff1a; …

详解Java中的堆内存

详解Java中的堆内存 堆是JVM运行数据区中的一块内存空间&#xff0c;它是线程共享的一块区域&#xff08;注意了&#xff01;&#xff01;&#xff01;&#xff09;&#xff0c;主要用来保存数组和对象实例等&#xff08;其实对象有时候是不在堆中进行分配的&#xff0c;想要了…

python-pptx 中 placeholder 和 shape 有什么区别?

在 python-pptx 库中&#xff0c;placeholder 和 shape 是两个核心概念。虽然它们看起来相似&#xff0c;但在功能和作用上存在显著的区别。为了更好地理解这两个概念&#xff0c;我们可以通过它们的定义、使用场景以及实际代码示例来剖析其差异。 Python-pptx 的官网链接&…

【微服务】服务注册与发现 - Eureka(day3)

CAP理论 P是分区容错性。简单来说&#xff0c;分区容错性表示分布式服务中一个节点挂掉了&#xff0c;并不影响其他节点对外提供服务。也就是一台服务器出错了&#xff0c;仍然可以对外进行响应&#xff0c;不会因为某一台服务器出错而导致所有的请求都无法响应。综上所述&…

【MySQL 06】表的增删查改

目录 1.insert 增添数据 1.1单行数据 全列插入 1.2多行数据 指定列插入 1.3插入否则更新 1.4.插入否则替换 2.select查找 2.1 全列查找 2.2指定列查找 2.3查询字段为表达式 2.4为查询结果指定别名 2.5 结果去重 2.6 where条件查询 2.7结果排序 2.8.筛选分页结果…