对象池是游戏编程中非常常用的优化策略以及设计模式。虽然非常简单,但是太常用了,所以我觉得有必要写一篇文章来巩固自己这方面的知识点,也又必要写一个对象池来作为自己放在自己的工具集中。
用途
游戏中,我们常常会遇到频繁得创建和销毁大量相同对象的场景。如果我们不做任何的特殊处理,这种场景会出现两个性能问题——大量的内存碎片以及频繁的分配内存空间。而对象池能供完美得解决这两个问题。
原理
当创建对象时,对象池将对象放入池管理的某种内存连续的数据结构中(数组或者栈等)。当不需要对象时,对象池并不销毁对象,而是将对象回收到池中,下次需要的时候再次从池中拿出来。
因为,对象储存在内存连续的数据结构中,所以解决了内存碎片的问题。
因为,对象每次用完以后就放回池中循环利用而不是再次创建和销毁,这样就解决了频繁的内存分配和销毁的问题。
一个我自己的对象池
接下来,我介绍一些我自己写的一个对象池。比较简单,主要时根据接口将相应规则定下来,以便可以快速的实现各种类的对象池的开发。
定义关于对象池的接口
using System;
/// <summary>
/// 对象池接口
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IPool<T>
{
event Action<T> OnRecycle;
/// <summary>
/// 获取IPoolObject
/// </summary>
/// <returns></returns>
IPoolObject<T> Get();
/// <summary>
/// 回收IPoolObject
/// </summary>
/// <param name="poolObject"></param>
void Recycle(IPoolObject<T> poolObject);
/// <summary>
/// 销毁IPoolObject
/// </summary>
/// <param name="poolObject"></param>
void Dispose(IPoolObject<T> poolObject);
}
/// <summary>
/// 对象池被管理单位
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IPoolObject<T>
{
/// <summary>
/// 对象池的引用
/// </summary>
/// <value></value>
IPool<T> Pool { get; }
/// <summary>
/// 内容
/// </summary>
/// <value></value>
T Content { get; }
/// <summary>
/// 回收自己
/// </summary>
/// <param name="poolObject"></param>
void Recycle();
/// <summary>
/// 销毁自己IPoolObject
/// </summary>
/// <param name="poolObject"></param>
void Dispose();
}
根据接口实现一个管理GameObject的对象池类
GameObjectPool
using System.Collections.Generic;
using UnityEngine;
using System;
/// <summary>
/// GameObject对象池
/// </summary>
public class GameObjectPool : IPool<GameObject>
{
public event Action<GameObject> OnRecycle;
public int MaxSize { get; set; }
public int TotalCount { get { return objectsInPools.Count + objectsPoped.Count; } }
private List<IPoolObject<GameObject>> objectsInPools = new List<IPoolObject<GameObject>>();
private List<IPoolObject<GameObject>> objectsPoped = new List<IPoolObject<GameObject>>();
private GameObject prefab;
private GameObject poolRoot;
public GameObjectPool(GameObject prefab, int maxSize)
{
this.prefab = prefab;
this.MaxSize = maxSize;
poolRoot = new GameObject("GameObjectPool");
}
public void WarmUp()
{
if (TotalCount < MaxSize)
{
GameObject content = GameObject.Instantiate(prefab);
content.transform.SetParent(poolRoot.transform);
content.SetActive(false);
IPoolObject<GameObject> poolObject = new GameObjectPoolObject(this, content);
objectsInPools.Add(poolObject);
}
}
public IPoolObject<GameObject> Get()
{
IPoolObject<GameObject> poolObject = null;
if (objectsInPools.Count == 0)
{
GameObject content = GameObject.Instantiate(prefab);
poolObject = new GameObjectPoolObject(this, content);
poolObject.Content.SetActive(true);
objectsPoped.Add(poolObject);
}
else
{
int lastIndex = objectsInPools.Count - 1;
poolObject = objectsInPools[lastIndex];
poolObject.Content.SetActive(true);
objectsInPools.RemoveAt(lastIndex);
objectsPoped.Add(poolObject);
}
poolObject.Content.transform.SetParent(null);
return poolObject;
}
public void Recycle(IPoolObject<GameObject> poolObject)
{
if (poolObject.Pool != this)
return;
OnRecycle?.Invoke(poolObject.Content);
if (TotalCount > MaxSize)
{
poolObject.Dispose();
}
else
{
objectsPoped.Remove(poolObject);
objectsInPools.Add(poolObject);
poolObject.Content.transform.SetParent(poolRoot.transform);
poolObject.Content.SetActive(false);
}
}
public void Dispose(IPoolObject<GameObject> poolObject)
{
if (poolObject.Pool != this)
return;
GameObject.DestroyImmediate(poolObject.Content);
if (objectsPoped.Contains(poolObject))
objectsPoped.Remove(poolObject);
if (objectsInPools.Contains(poolObject))
objectsInPools.Remove(poolObject);
}
}
GameObjectPoolObject
using UnityEngine;
/// <summary>
/// GameObject对象池被管理对象
/// </summary>
public class GameObjectPoolObject : IPoolObject<GameObject>
{
public IPool<GameObject> Pool { get; set; }
public GameObject Content { get; set; }
public GameObjectPoolObject(GameObjectPool pool, GameObject content)
{
Pool = pool;
Content = content;
}
public void Recycle()
{
Pool.Recycle(this);
}
public void Dispose()
{
Pool.Dispose(this);
}
}
MaxSize
关于我的这个对象池,有一个MaxSize.定义对象池可容纳的最大数量。关于超过MaxSize的处理,有以下几个策略。
1.无法再次创建,返回空对象。这种比较严格,但是一些音效,比如很多脚步声,没有的话玩家也不会注意到的。
2.强制回收一个已有的对象。同样也要用在玩家,不会注意到的地方,比如声音,粒子什么的。
3.增加池的大小。这样,就不会担心创建不出对象的问题了,但是要考虑要不要缩回去。(我的GameObject当超出MAXSIZE的时候,回收对象,不会再放回池中,会直接被销毁,知道池里的对象小于MAXSIZE位置)
Github仓库网址:
https://github.com/PotroChen/ObjectPool
参考:
游戏编程模式