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

devtools/2024/9/24 8:52:19/

在这里插入图片描述


前言

最近在学习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(){}
}

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


http://www.ppmy.cn/devtools/56022.html

相关文章

Linux进程概念

1. 冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。 截至目前&#xff0c;我们所认识的计算机&#xff0c;都是有一个个的硬件组件组成。 输入单元&#xff1a;包括键盘 , 鼠标&…

【Qt之·类QTableWidget】

系列文章目录 文章目录 前言一、常用属性二、成员函数2.1 左上角空白区域 三、实例演示总结 前言 一、常用属性 二、成员函数 方法描述selectRow选中行removeRow移除行insertRow插入行rowCount总行数 2.1 左上角空白区域 QTableCornerButton即不属于列表头&#xff0c;也不…

高效利用iCloud指南:打造无缝连接的数字生活

iCloud是苹果公司推出的一项云存储和云计算服务&#xff0c;它为用户提供了一个安全、便捷的云端存储空间&#xff0c;帮助用户在各个苹果设备之间无缝同步数据。无论是照片、文档、备忘录&#xff0c;还是应用程序数据&#xff0c;iCloud都能让你的数字生活更加高效和有序。本…

如何在Spring Boot中优雅处理异常

如何在Spring Boot中优雅处理异常 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将深入探讨在Spring Boot应用程序中如何优雅地处理异常&#xff0c;以…

数据结构速成--树和二叉树

由于是速成专题&#xff0c;因此内容不会十分全面&#xff0c;只会涵盖考试重点&#xff0c;各学校课程要求不同 &#xff0c;大家可以按照考纲复习&#xff0c;不全面的内容&#xff0c;可以看一下小编主页数据结构初阶的内容&#xff0c;找到对应专题详细学习一下。 气死了…

【实战教程】如何使用JMeter来轻松测试WebSocket接口?

1、websocket接口原理 打开网页&#xff1a;从http协议&#xff0c;升级到websocket协议&#xff0c;请求建立websocket连接服务器返回建立成功成功客户端向服务端发送匹配请求服务端选择一个客服上线服务器返回客服id客户端向服务器发送消息服务器推送消息给指定的客服服务器…

Python 语法基础二

7.常用内置函数 执行这个命令可以查看所有内置函数和内置对象&#xff08;两个下划线&#xff09; >>>dir(__builtins__) [__class__, __contains__, __delattr__, __delitem__, __dir__, __doc__, __eq__, __format__, __ge__, __getattribute__, __getitem__, __gt…

探索HTML DOM的奥秘:从基础到实践

HTML DOM&#xff08;Document Object Model&#xff09;是网页编程的核心概念&#xff0c;它将HTML文档转化为一个可编程的结构化树状模型&#xff0c;使开发者能够通过JavaScript等脚本语言动态地访问、修改网页内容、结构和样式。本文旨在深入解析HTML DOM的运作机制&#x…