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

在这里插入图片描述


前言

最近在学习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 above
    private 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 pool
    private RevisedProjectile CreateProjectile()
    {
        RevisedProjectile projectileInstance = Instantiate(projectilePrefab);
        projectileInstance.ObjectPool = objectPool;
        return projectileInstance;
    }

    // invoked when returning an item to the object pool
    private void OnReleaseToPool(RevisedProjectile pooledObject)
    {
        pooledObject.gameObject.SetActive(false);
    }

    // invoked when retrieving the next item from the object pool
    private 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()
    {}
}

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

  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中,可以使用对象池(Object Pool)来缓存一些需要频繁创建和销毁的对象,以便在需要时直接从对象池中获取,避免频繁创建和销毁对象所带来的性能开销。下面我们来讲解如何使用对象池。 ### 手动创建对象池 手动创建对象池的步骤如下: 1. 创建一个GameObject对象作为对象池的容器,将需要缓存的对象挂在该容器下。 2. 编写一个对象池类来实现对象的缓存和取出。以下是一个简单的对象池类示例: ```csharp using System.Collections.Generic; using UnityEngine; public class ObjectPool : MonoBehaviour { public GameObject prefab; // 缓存的对象的预制体 public int poolSize; // 对象池大小 private List<GameObject> objectPool = new List<GameObject>(); // 对象池列表 private void Start() { // 创建对象池中的对象 for (int i = 0; i < poolSize; i++) { GameObject obj = Instantiate(prefab); obj.SetActive(false); objectPool.Add(obj); } } // 从对象池中取出对象 public GameObject GetObject() { foreach (GameObject obj in objectPool) { if (!obj.activeInHierarchy) { obj.SetActive(true); return obj; } } // 如果对象池中没有可用对象,则新建一个对象 GameObject newObj = Instantiate(prefab); objectPool.Add(newObj); return newObj; } // 将对象放回对象池 public void ReleaseObject(GameObject obj) { obj.SetActive(false); } } ``` 3. 在需要使用对象的地方,通过对象池类的GetObject方法获取一个对象,使用完后通过ReleaseObject方法将对象放回对象池即可。 ### 使用Unity提供的对象池Unity提供了ObjectPool类来支持对象池的创建和使用使用Unity提供的对象池类的步骤如下: 1. 创建一个GameObject对象作为对象池的容器,将需要缓存的对象挂在该容器下。 2. 编写脚本,在Start方法中初始化对象池。以下是一个简单的示例: ```csharp using UnityEngine; using UnityEngine.Pool; public class ObjectPoolExample : MonoBehaviour { public GameObject prefab; // 缓存的对象的预制体 public int poolSize; // 对象池大小 private ObjectPool<GameObject> objectPool; // 对象池 private void Start() { objectPool = new ObjectPool<GameObject>( () => Instantiate(prefab), null, obj => obj.SetActive(true), obj => obj.SetActive(false), null, poolSize); } // 在需要使用对象的地方,通过GetObject方法获取一个对象,使用完后通过Release方法将对象放回对象池即可。 private void Update() { GameObject obj = objectPool.Get(); objectPool.Release(obj); } } ``` 以上是手动创建对象池使用Unity提供的对象池类的两种方式。两种方式都可以有效地减少对象创建和销毁的次数,提高性能并减少内存占用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值