学习搭建工具框架-对象池

因为忙于学业和学习新的知识,所以最近都没有怎么更新,真的非常抱歉了~


为什么要搭建工具框架

        在日常开发的过程中,在许多不同项目上,我们可能或多或少都会用到一些比较重复的内容,俗话说得好,程序开发的日常不是在造轮子就是在造轮子的路上,工具就是搭建轮子,经常用的插件也是搭建轮子搭建出来的,当我们的某些项目需要用到某些功能的时候,想起来我以前写过类似的,诶,这不就可以拿来用了吗?

        所以学习并搭建属于自己的工具是很有用,这能大大减少日后你在开发项目上消耗的时间,这里就拿非常常见的对象池举例。

对象池概念

        由于很早期的目录中有关于对象池的说明,所以我就不会在多解释了,只不过那个时候的对象池存在很多缺陷,一是类型比较死板,需要是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并做了大量修改。不写死就是为了应对更多的情况。

        对象池的工具类只是一个例子,本篇的核心目的还是要建议学习搭建框架用于自己的项目中,光会做功能是不够的,更重要的是把功能中重复的部分提取优化,制作成工具并可以应用到其它的项目中才行。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值