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

前言

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

代码

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

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

Base目录

BasePool.cs

using System;
using System.Collections.Generic;

namespace GameAssistant.Pool
{
    /// <summary>
    /// 对象池基类
    /// </summary>
    /// <typeparam name="T">对象类型</typeparam>
    public abstract class BasePool<T>
    {
        /// <summary>
        /// 对象池所生产对象的总数量
        /// </summary>
        public int totalCount { get; protected set; }

        /// <summary>
        /// 对象池当前空闲对象的数量
        /// </summary>
        public int freeCount => _poolGetter.Count;

        /// <summary>
        /// 是否为固定容量的对象池
        /// <para>默认值:False</para>
        /// </summary>
        public bool isFixed => _isFixed;
        private bool _isFixed;

        /// <summary>
        /// 对象池容量
        /// <para>默认值:PoolConstant.DEFAULT_CAPACITY</para>
        /// </summary>
        public int capacity => _capacity;
        private int _capacity;

        /// <summary>
        /// 对象创建逻辑
        /// <para>提示:用来自定义对象的创建逻辑</para>
        /// </summary>
        public Func<T> overrideCreate;

        /// <summary>
        /// 对象重置逻辑
        /// <para>提示:用来自定义对象的重置逻辑</para>
        /// </summary>
        public Action<T> overrideReset;

        /// <summary>
        /// 对象销毁逻辑
        /// <para>提示:用来自定义对象的销毁逻辑</para>
        /// </summary>
        public Action<T> overrideDestroy;

        /// <summary>
        /// 池对象访问器
        /// </summary>
        protected Stack<T> _poolGetter => _pool;
        private Stack<T> _pool;

        /// <summary>
        /// 对象类型是否为可释放对象类型
        /// </summary>
        protected static bool _isDisposable => _staticIsDisposable;
        private static readonly bool _staticIsDisposable = typeof(IDisposable).IsAssignableFrom(typeof(T));

        /// <summary>
        /// 对象类型名称
        /// </summary>
        protected static string _typeName => _staticTypeName;
        private 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);
            _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);
            _capacity = capacity;
            _isFixed = isFixed;
        }

        /// <summary>
        /// 重置对象并返回
        /// </summary>
        protected abstract void 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

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

UnityObjectPoolSettings.cs

namespace GameAssistant.Pool
{
    /// <summary>
    /// Unity对象池设置
    /// </summary>
    public class UnityObjectPoolSettings<T> where T : UnityEngine.Object
    {
        /// <summary>
        /// 对象池初始容量
        /// </summary>
        public int capacity = PoolConstant.DEFAULT_CAPACITY;

        /// <summary>
        /// 对象池是否持久化
        /// </summary>
        public bool isPersistant = true;

        /// <summary>
        /// 对象池是否固定容量
        /// </summary>
        public bool isFixed;

        /// <summary>
        /// 对象池容器
        /// </summary>
        public UnityEngine.GameObject container;

        /// <summary>
        /// 对象原型
        /// </summary>
        public T original;

        /// <summary>
        /// 对象默认名称
        /// </summary>
        public string defaultName;

        /// <summary>
        /// 获取时激活对象
        /// </summary>
        public bool activeWhenGet = true;
    }
}
具体的对象池 

ClassPool.cs

using System;

namespace GameAssistant.Pool
{
    /// <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()
        {
            totalCount -= _poolGetter.Count;
            while (_poolGetter.Count > 0)
            {
                var pop = _poolGetter.Pop();
                if (overrideDestroy != null) overrideDestroy(pop);
                else
                {
                    if (_isDisposable)
                    {
                        ((IDisposable)pop).Dispose();
                    }
                }
            }
        }

        public override T Get()
        {
            T item;

            if (freeCount > 0) item = _poolGetter.Pop();
            else
            {
                item = Create();
                totalCount++;
            }

            return item;
        }

        public override void Release(T item)
        {
            if (item == null) return;
            Reset(item);
            _poolGetter.Push(item);
        }

        protected override void Reset(T item)
        {
            if (overrideReset != null) overrideReset(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;

namespace GameAssistant.Pool
{
    /// <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;
        private bool _isDisposed;

        public UnityObjectPool()
        {
            _container = CreateContainer();
            _activeWhenGet = true;
        }

        public UnityObjectPool(int capacity) : base(capacity)
        {
            _container = CreateContainer();
            _activeWhenGet = true;
        }

        public UnityObjectPool(int capacity, bool isFixed) : base(capacity, isFixed)
        {
            _container = CreateContainer();
            _activeWhenGet = true;
        }

        public UnityObjectPool(UnityObjectPoolSettings<T> settings) :
        base(settings == null ? PoolConstant.DEFAULT_CAPACITY : settings.capacity,
        settings != null && settings.isFixed)
        {
            if (settings == null)
            {
                _container = CreateContainer();
                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()
        {
            totalCount -= _poolGetter.Count;
            while (_poolGetter.Count > 0)
            {
                var item = _poolGetter.Pop();
                if (overrideDestroy != null) overrideDestroy(item);
                else MonoBehaviour.Destroy(item);
            }
        }

        protected virtual void DoRelease(T item) { base.Release(item); }
        protected virtual T DoGet() { return base.Get(); }

        private GameObject CreateContainer()
        {
            var container = new GameObject($"{_typeName}Pool");
            MonoBehaviour.DontDestroyOnLoad(container);
            return container;
        }

        private void Dispose(bool disposing)
        {
            if (_isDisposed) return;
            _isDisposed = true;

            if (disposing)
            {
                Clear();
                MonoBehaviour.Destroy(_container);
            }
        }

        ~UnityObjectPool()
        {
            Dispose(false);
        }
    }
}

GameObjectPool.cs

using System;
using UnityEngine;

namespace GameAssistant.Pool
{
    /// <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 void Reset(GameObject item)
        {
            if (item == null) throw new InvalidOperationException("Pool:The item being reset is null.");

            if (overrideReset != null) overrideReset(item);
            else item.SetActive(false);
        }

        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;

namespace GameAssistant.Pool
{
    /// <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 void Reset(T item)
        {
            if (item == null) throw new InvalidOperationException("Pool:The item being reset is null.");

            if (overrideReset != null) overrideReset(item);
            else item.enabled = false;
        }

        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 GameAssistant.Pool;
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 void ResetBullet(GameObject go)
    {
        if (go.TryGetComponent(out Bullet bullet))
        {
            damagePool.Release(bullet.damageModel);
            bullet.damageModel = null;
            bullet.onDestroy = null;
        }
        go.SetActive(false);
    }

    static void ResetDamageModel(DamageModel dm)
    {
        dm.damage = 0;
    }

    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, 100);
            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对象池。

版本改进

版本改进说明
v1.1

1.添加overrideDestroy回调;

2.Reset方法更改为"protected abstract void Reset(T item)";

3.优化代码

......

系列文章

......

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值