因为忙于学业和学习新的知识,所以最近都没有怎么更新,真的非常抱歉了~
为什么要搭建工具框架
在日常开发的过程中,在许多不同项目上,我们可能或多或少都会用到一些比较重复的内容,俗话说得好,程序开发的日常不是在造轮子就是在造轮子的路上,工具就是搭建轮子,经常用的插件也是搭建轮子搭建出来的,当我们的某些项目需要用到某些功能的时候,想起来我以前写过类似的,诶,这不就可以拿来用了吗?
所以学习并搭建属于自己的工具是很有用,这能大大减少日后你在开发项目上消耗的时间,这里就拿非常常见的对象池举例。
对象池概念
由于很早期的目录中有关于对象池的说明,所以我就不会在多解释了,只不过那个时候的对象池存在很多缺陷,一是类型比较死板,需要是PoolGameObject,二是没有对应的管理器,指定对象池还需要拖拽的方式(用单例模式还会导致对象池不能给不同实体使用),此外还有一个缺点就是,作为工具类的一大特征就是不继承自MonoBehaviour,工具类应该属于一个程序框架,而不是需要实例的对象,这也是我为什么要从新弄这一篇的原因。
代码
using SelfTools.Managers;
using System;
using System.Collections.Generic;
using UnityEngine;
//对象池类和对象池管理类
namespace SelfTools
{
/// <summary>
/// 对象池模版基类
/// </summary>
/// <typeparam name="T">约束MonoBehaviour继承关系的泛型对象</typeparam>
public class ObjectPool<T> where T : MonoBehaviour
{
/// <summary>
/// 对象池容器
/// </summary>
protected Queue<T> Pool = new();
/// <summary>
/// 当前池中元素的数量
/// </summary>
public int Count
{
get => Pool.Count;
// ReSharper disable once ValueParameterNotUsed
protected set{ }
}
/// <summary>
/// 最大大小
/// </summary>
protected readonly int MaxSize;
/// <summary>
/// 已有对象数量
/// </summary>
protected int CurrentCount;
/// <summary>
/// 一次新建的对象数量
/// </summary>
protected readonly int CreateNum;
/// <summary>
/// 创建新的对象执行的操作,用来构建新的对象和参数初始化
/// </summary>
public Func<T> CreateAction;
/// <summary>
/// 放回对象成功时调用,可用于归池时的参数设置
/// </summary>
public Action<T> BackAction;
/// <summary>
/// 放回对象失败时调用
/// </summary>
public Action<T> UnBackAction;
/// <summary>
/// 取出对象前调用,一般是对象做初始设置
/// </summary>
public Func<T, T> OnGetAction;
/// <summary>
/// 取出对象失败时的操作
/// </summary>
public Action OnUnGetAction;
/// <summary>
/// 清理对象池的操作,一般是销毁前的处理
/// </summary>
public Action<T> ClearAction;
/// <summary>
/// 彻底销毁时执行的方法
/// </summary>
public Action<T> RemoveAction;
/// <summary>
/// 构造方法,构造一个基本的对象池类型,按照可能使用的参数的情况顺序排序,允许无参构造
/// 其中大部分参数会按照默认值null来设置,但是一般建议实例化CreateAction使得池可以新建对象
/// </summary>
/// <param name="PoolKey">池的名称/关键字,一但不为null会自动进入管理器进行管理</param>
/// <param name="CreateNum">每次新增对象的数量</param>
/// <param name="MaxSize">对象池最大容量</param>
/// <param name="CreateAction">创建新的对象执行的操作,用来构建新的对象和参数初始化</param>
/// <param name="BackAction">放回对象成功时调用,可用于归池时的参数设置</param>
/// <param name="OnGetAction">取出对象前调用,一般是对象做初始设置</param>
/// <param name="RemoveAction">彻底清除时调用</param>
/// <param name="ClearAction">清理对象池的操作,一般是销毁前的处理</param>
/// <param name="UnBackAction">放回对象失败时调用</param>
/// <param name="OnUnGetAction">取出对象失败时的操作</param>
public ObjectPool(string PoolKey = null, int CreateNum = 4, int MaxSize = 1024, Func<T> CreateAction = null,
Action<T> BackAction = null, Func<T, T> OnGetAction = null, Action<T> RemoveAction = null,
Action<T> ClearAction = null, Action<T> UnBackAction = null, Action OnUnGetAction = null)
{
if(PoolKey != null) ManagerObjectPool<T>.AddPool(PoolKey, this);
this.CreateAction = CreateAction;
this.UnBackAction = UnBackAction;
this.BackAction = BackAction;
this.OnUnGetAction = OnUnGetAction;
this.OnGetAction = OnGetAction;
this.ClearAction = ClearAction;
this.RemoveAction = RemoveAction;
this.CreateNum = CreateNum;
this.MaxSize = MaxSize;
}
/// <summary>
/// 从对象池中获取对象,也会尝试去创建新的对象
/// </summary>
/// <returns>获取的对象</returns>
public virtual T GetObject()
{
if (Pool.Count == 0 && CreateAction != null)
{
for (int i = 0; i < CreateNum && CurrentCount < MaxSize; i++)
{
if (!BackObject(CreateAction.Invoke())) break;
CurrentCount++;
}
}
if (Pool.Count > 0)
{
var obj = OnGetAction(Pool.Dequeue()) ?? Pool.Dequeue();
obj.gameObject.SetActive(true);
return obj;
}
Debug.LogWarning("取出对象失败!");
OnUnGetAction?.Invoke();
return null;
}
/// <summary>
/// 将对象放回对象池
/// </summary>
/// <param name="obj">放回的对象</param>
/// <returns>状态</returns>
public virtual bool BackObject(T obj)
{
if(CurrentCount < MaxSize)
{
BackAction?.Invoke(obj);
Pool.Enqueue(obj);
obj.gameObject.SetActive(false);
return true;
}
Debug.LogWarning("放回对象失败!");
UnBackAction?.Invoke(obj);
return false;
}
/// <summary>
/// 清空对象池,但是不会对应的销毁实体
/// </summary>
public void ClearPool()
{
foreach (T obj in Pool) ClearAction?.Invoke(obj);
Pool.Clear();
}
/// <summary>
/// 需要彻底的销毁某个对象时调用,一般是这个对象需要被Destroy的时候,方法本身不会进行销毁操作
/// </summary>
/// <param name="obj">移除的对象</param>
public void RemoveObject(T obj)
{
Queue<T> newqueue = new();
foreach(T t in Pool)
{
if (!t.Equals(obj)) newqueue.Enqueue(t);
else
{
CurrentCount--;
RemoveAction?.Invoke(t);
}
}
Pool = newqueue;
}
}
/// <summary>
/// 循环型对象池,对于伤害飘字等限制最大存在数量的对象池类型,会将已经拿出的最久的对象作为新对象返回
/// </summary>
/// <typeparam name="T">约束MonoBehaviour继承关系的泛型对象</typeparam>
public class CircleObjectPool<T> : ObjectPool<T> where T : MonoBehaviour
{
/// <summary>
/// 已经被取出的元素
/// </summary>
protected readonly LinkedList<T> OutObjects = new();
/// <summary>
/// 已经被取出对象的被再次引用时调用的函数
/// </summary>
public Action<T> CircleAction;
/// <summary>
/// 构造方法,对循环对象池进行构造的方法,按照可能容易使用的顺序排列参数
/// 虽然我是不认为能出现把所有参数都要用上的情况
/// </summary>
/// <param name="PoolKey">池的名称/关键字,一但不为null会自动进入管理器进行管理</param>
/// <param name="CreateNum">每次新增对象的数量</param>
/// <param name="MaxSize">对象池最大容量</param>
/// <param name="CreateAction">创建新的对象执行的操作,用来构建新的对象和参数初始化</param>
/// <param name="BackAction">放回对象成功时调用,可用于归池时的参数设置</param>
/// <param name="RemoveAction">彻底清除时调用</param>
/// <param name="OnGetAction">取出对象前调用,一般是对象做初始设置</param>
/// <param name="ClearAction">清理对象池的操作,一般是销毁前的处理</param>
/// <param name="UnBackAction">放回对象失败时调用</param>
/// <param name="OnUnGetAction">取出对象失败时的操作</param>
/// <param name="CircleAction">已经被取出对象的被再次引用时调用的函数</param>
public CircleObjectPool(string PoolKey = null, int CreateNum = 4, int MaxSize = 1024, Func<T> CreateAction = null,
Action<T> BackAction = null, Func<T, T> OnGetAction = null, Action<T> RemoveAction = null, Action<T> CircleAction = null,
Action<T> ClearAction = null, Action<T> UnBackAction = null, Action OnUnGetAction = null) : base(PoolKey, CreateNum,
MaxSize, CreateAction, BackAction, OnGetAction, ClearAction, UnBackAction, RemoveAction, OnUnGetAction)
{
this.CircleAction = CircleAction;
}
/// <summary>
/// 重写的循环对象池方法,除了原来的判断数量并取出对象以外,还会自动
/// </summary>
/// <returns>取出的对象</returns>
public override T GetObject()
{
if (Pool.Count == 0)
{
if (CurrentCount < MaxSize && CreateAction != null)
{
for (int i = 0; i < CreateNum; i++)
{
if (!BackObject(CreateAction.Invoke())) break;
CurrentCount++;
}
}
else if (OutObjects.Count > 0)
{
T Out = OutObjects.First.Value;
OutObjects.RemoveFirst();
OutObjects.AddLast(Out);
CircleAction?.Invoke(Out);
return Out;
}
}
if (Pool.Count > 0)
{
var obj = OnGetAction(Pool.Dequeue()) ?? Pool.Dequeue();
obj.gameObject.SetActive(true);
return obj;
}
Debug.LogWarning("取出对象失败!");
OnUnGetAction?.Invoke();
return null;
}
/// <summary>
/// 将对象放回对象池
/// </summary>
/// <param name="obj">放回的对象</param>
/// <returns>状态</returns>
public override bool BackObject(T obj)
{
if (CurrentCount < MaxSize && OutObjects.Remove(obj))
{
BackAction?.Invoke(obj);
Pool.Enqueue(obj);
return true;
}
Debug.LogWarning("放回对象失败!");
UnBackAction?.Invoke(obj);
return false;
}
}
}
namespace SelfTools.Managers
{
/// <summary>
/// 管理对象池类型的管理器
/// </summary>
/// <typeparam name="T">约束MonoBehaviour继承关系的泛型对象</typeparam>
public static class ManagerObjectPool<T> where T : MonoBehaviour
{
static readonly Dictionary<string, ObjectPool<T>> Pools = new();
/// <summary>
/// 新增对象池进行管理
/// </summary>
/// <param name="PoolKey">对象池的名称</param>
/// <param name="Pool">对象池对象</param>
public static void AddPool(string PoolKey, ObjectPool<T> Pool)
{
if(!Pools.TryAdd(PoolKey, Pool)) Debug.LogWarning("这个名字的Pool已经存在!");
}
/// <summary>
/// 移除对象池
/// </summary>
/// <param name="PoolKey">移除对象的名称</param>
public static bool RemovePool(string PoolKey)
{
if (Pools.Remove(PoolKey)) return true;
Debug.LogWarning("没有找到指定对象池");
return false;
}
/// <summary>
/// 清空所有对象池
/// </summary>
public static void ClearPool()
{
foreach(var pool in Pools) pool.Value.ClearPool();
Pools.Clear();
}
/// <summary>
/// 直接从指定对象池中取出一个对象
/// </summary>
/// <param name="PoolKey">对象池键值</param>
/// <returns>取出的对象</returns>
public static T GetObjectInPools(string PoolKey)
{
if (Pools.TryGetValue(PoolKey, out var pool)) return pool.GetObject();
Debug.LogWarning("没有找到指定对象池");
return default;
}
/// <summary>
/// 将对象放回指定的对象池中
/// </summary>
/// <param name="PoolKey">对象池键值</param>
/// <param name="obj">放回的对象</param>
public static bool BackObjectInPools(string PoolKey, T obj)
{
if (Pools.TryGetValue(PoolKey, out var pool)) return pool.BackObject(obj);
Debug.LogWarning("没有找到指定对象池");
return false;
}
}
}
解释
一般来说,既然你都选择看这个栏目了,应该不至于看不懂代码吧......
虽然注释写的比较详细整齐,当然该解释的还是要解释的,where T : MonoBehaviour是泛型约束,这规定了这个泛型只能是继承于MonoBehaviour的对象,毕竟能在场景实例化且需要用上对象池目前只能是继承于MonoBehaviour的类,GameObject不能被继承因为它被sealed标记了,另外GameObject ≠ MonoBehaviour,尽管在使用上感觉没有什么区别,它们还是有一些差异的。
在ObjectPool的构造中你会看到很多的Action和Func委托,它们分别对应了在对象生成,取出放入及其对应的成功和失败的操作,你可以在外部定义它们的行为,从而能够很好的扩展对象池的行为,这些委托的默认参数都是null,并且按照可能的重要度排序(没错不是随便排的),有对应的方法就执行对应的行为,当然细心的人可能注意到了对于出池和入池都针对T进行了SetActive()的操作,但是创建和销毁没有进行默认的Instantiate()和Destroy()操作,这是因为有些插件或者项目需要重构了MonoBehaviour,它们的创建和销毁不能直接使用这两个方法,比如用于网络游戏开发的Phonton Fusion就继承了MonoBehaviour并做了大量修改。不写死就是为了应对更多的情况。
对象池的工具类只是一个例子,本篇的核心目的还是要建议学习搭建框架用于自己的项目中,光会做功能是不够的,更重要的是把功能中重复的部分提取优化,制作成工具并可以应用到其它的项目中才行。