概述
文章目录
- 今天实现的内容:
- 对象池的概念
- 简易对象池的实现
- 多类型对象池的实现
- 将对象池运用到项目中
- BUG以及缺陷:
- 值得注意的:
今天实现的内容:
对象池的概念
简单来说,对于某些游戏对象,我们在游戏运行时提前生成一堆该游戏对象,在需要用到该游戏对象时,直接使用之前已经生成出来的,而不是用Instantiate当场生成一个新的。在遇到需要大量生成并且存在时间短的游戏对象时,比如子弹,大量地调用Instantiate会产生性能上的花销,同时,产生大量的游戏对象让系统自动进行处理会导致频繁的GC,也会产生性能上的问题。而使用对象池,虽然游戏运行时不管游戏对象有用没用都会占据空间,却避免了以上两个性能损失。毕竟CPU还是比内存精贵。
图片来自视频:龙博士——超实用游戏编程第一期:对象池
简易对象池的实现
这是一个超简易的对象池设计,适用于单一种的游戏物体。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 专为子弹一类的游戏对象设计的对象池管理器
public class EasyPool : MonoBehaviour
{
// 集合
public List<GameObject> list = new List<GameObject>();
// 游戏预设体
public GameObject prefab;
// 集合中 元素的最大量
public int MaxCount = 100;
// 将对象回收到对象池
public void Push(GameObject _targetObject)
{
if(list.Count < MaxCount)
{
// 如果集合中的元素数量没有超过上限 添加到对象池
list.Add(_targetObject);
}
else
{
// 如果超过上限 不保存直接销毁
Destroy(_targetObject);
}
}
// 将对象从对象池中取出
public GameObject Pop()
{
if (list.Count > 0)
{
// 如果集合中有对象 直接取出集合中的对象
GameObject temp_object = list[0];
list.RemoveAt(0);
return temp_object;
}
else
{
// 如果没有对象 新建一个对象再返回
return Instantiate(prefab);
}
}
// 清空对象池
public void Clear()
{
list.Clear();
}
}
测试代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PoolTest : MonoBehaviour
{
// 用于管理取出对象池的对象
private List<GameObject> objectInSceneList = new List<GameObject>();
private void Update()
{
// 创建
if(Input.GetMouseButtonDown(0))
{
// 从对象池取出
GameObject temp_object = GetComponent<EasyPool>().Pop();
temp_object.SetActive(true);
objectInSceneList.Add(temp_object);
}
// 删除
if(Input.GetMouseButtonDown(1))
{
if(objectInSceneList.Count > 0)
{
// 放回对象池
GetComponent<EasyPool>().Push(objectInSceneList[0]);
objectInSceneList[0].SetActive(false);
objectInSceneList.RemoveAt(0);
}
}
}
}
多类型对象池的实现
这是一个泛用的对象池。PoolItem定义可以放到对象池中的对象的类型,可以是不同的类型。PoolInfo是放到对象池中的对象的信息。这个对象池的效率并不是最好的,原因是会遍历对象池。同样值得一说的是,这个对象池设计采用了单例模式。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 多类型对象池
public class Pool : MonoBehaviour
{
// 单例模式
private static Pool m_instance;
public static Pool Instance
{
get { return m_instance; }
}
private void Awake()
{
m_instance = this;
}
// 对象池物品的列表 用于在对象池中生成游戏物体
public List<PoolItem> itemsList = new List<PoolItem>();
// 对象池内的对象信息
private List<PoolInfo> m_inPoolInfoList = new List<PoolInfo>();
/// <summary>
/// 从池中生成物品对象 并调用OnSpawned
/// </summary>
/// <param name="_name">对需要生成的对象的标识</param>
/// <param name="_parent">对象生成后的父物体</param>
/// <returns></returns>
public GameObject Create(string _name, Transform _parent)
{
// 是否有剩余对象
foreach (var item in m_inPoolInfoList)
{
if(item.itemName == _name)
{
// 有剩余对象 直接使用
m_inPoolInfoList.Remove(item);
item.gameObject.transform.SetParent(_parent);
item.gameObject.SetActive(true);
// 出池
item.gameObject.SendMessage("OnSpawned", SendMessageOptions.DontRequireReceiver);
return item.gameObject;
}
}
// 没有剩余对象 创建新的使用
foreach (var item in itemsList)
{
if(item.name == _name)
{
// 创建新的对象
GameObject temp_object = Instantiate(item.prefab);
temp_object.AddComponent<PoolInfo>().itemName = _name;
// 使用这个新建的对象
temp_object.SetActive(true);
temp_object.gameObject.transform.SetParent(_parent);
temp_object.SendMessage("OnSpawned", SendMessageOptions.DontRequireReceiver);
return temp_object;
}
}
// 没有找到PoolItem
#if UNITY_EDITOR
Debug.LogError("没有找到Name:" + _name);
#endif
return null;
}
/// <summary>
/// 回收对象放回池中 并调用OnDespawned 不是由对象池管理的对象会直接Destroy
/// </summary>
/// <param name="_target">需要回收的对象</param>
/// <returns></returns>
public bool Destroy(GameObject _target)
{
PoolInfo temp_info = _target.GetComponent<PoolInfo>();
// _target是否由对象池进行管理 取决于对象是否挂载了PoolInfo组件
if (temp_info != null)
{
// 有PoolInfo组件 将_target回收到对象池
temp_info.gameObject.SendMessage("OnDespawned", SendMessageOptions.DontRequireReceiver);
temp_info.gameObject.SetActive(false);
temp_info.gameObject.transform.SetParent(this.gameObject.transform);
m_inPoolInfoList.Add(temp_info);
return true;
}
else
{
// 没有该组件说明_target不由对象池进行管理 直接进行删除
GameObject.Destroy(_target);
return false;
}
}
}
// 对象池中的物品信息 将需要使用对象池的物体放入Inspector中 能够实现泛用
[System.Serializable]
public class PoolItem
{
// 物品的预制体
public GameObject prefab;
// 物品名称 作为标识
public string name;
}
// 对象池中的物品的信息
public class PoolInfo : MonoBehaviour
{
// 对象池内物品的名称 对象池中的唯一标识
public string itemName;
// 从对象池中取出对象时调用
private void OnSpawned()
{
Debug.Log("取出" + itemName);
}
// 将对象放回对象池时调用
private void OnDespawned()
{
Debug.Log("放回" + itemName);
}
}
测试脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PoolTest : MonoBehaviour
{
public float period = 2f;
// Start is called before the first frame update
void Start()
{
StartCoroutine(CreateForFun());
}
// Update is called once per frame
void Update()
{
if(Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out RaycastHit hit, float.MaxValue))
{
if (hit.collider != null)
{
Pool.Instance.Destroy(hit.collider.gameObject);
}
}
}
private IEnumerator CreateForFun()
{
bool trigger = false;
while(true)
{
GameObject temp_object;
if (trigger)
{
temp_object = Pool.Instance.Create("Cube", this.transform);
}
else
{
temp_object = Pool.Instance.Create("Sphere", this.transform);
}
temp_object.transform.localPosition = Random.insideUnitSphere * 5f;
trigger = !trigger;
yield return new WaitForSeconds(period);
}
}
}
将对象池运用到项目中
将对象池运用于之前的FPS项目中,用于子弹的生成和回收。
BulletPool,在需要子弹时从对象池中取已经生成的子弹,或者在池中没有子弹时生成子弹,到了子弹要销毁时将子弹重新放回对象池,注意取出和放回时的参数调整。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Scripts.Weapon
{
public class BulletPool : MonoBehaviour
{
// 集合 最好是将GameObject替换为游戏对象上的某个脚本 比如子弹上的Bullet 方便追踪到个体
public List<Bullet> list = new List<Bullet>();
// 游戏预设体
public GameObject prefab;
// 集合中 元素的最大量
public int MaxCount = 200;
// 将对象回收到对象池
public void Push(Bullet _targetObject)
{
if (list.Count < MaxCount)
{
// 如果集合中的元素数量没有超过上限 添加到对象池
_targetObject.trail.enabled = false;
_targetObject.gameObject.SetActive(false);
list.Add(_targetObject);
}
else
{
// 如果超过上限 不保存直接销毁
Destroy(_targetObject);
}
}
// 将对象从对象池中取出
public GameObject Pop(Vector3 _position, Quaternion _rotation)
{
if (list.Count > 0)
{
// 如果集合中有对象 直接取出集合中的对象
Bullet temp_bullet = list[0];
list.RemoveAt(0);
temp_bullet.gameObject.SetActive(true);
temp_bullet.transform.position = _position;
temp_bullet.transform.rotation = _rotation;
temp_bullet.prevPosition = _position;
StartCoroutine(temp_bullet.BulletRecycle());
StartCoroutine(temp_bullet.DisplayTrail());
return temp_bullet.gameObject;
}
else
{
// 如果没有对象 新建一个对象再返回
Bullet temp_bullet = Instantiate(prefab, _position, _rotation).GetComponent<Bullet>();
StartCoroutine(temp_bullet.BulletRecycle());
StartCoroutine(temp_bullet.DisplayTrail());
return temp_bullet.gameObject;
}
}
// 清空对象池
public void Clear()
{
list.Clear();
}
}
}
在Bullet类中添加两个新协程用于协调。
// 子弹回收协程
public IEnumerator BulletRecycle()
{
yield return new WaitForSeconds(existTime);
pool.Push(this);
}
// 开启子弹轨迹协程 轨迹要过几帧再显示
public IEnumerator DisplayTrail()
{
yield return new WaitForSeconds(trailDisplayTime);
trail.enabled = true;
}
}
}
最后,开枪时实例化子弹改为从对象池中取子弹。
// 实例化子弹对象
GameObject temp_bullet = pool.Pop(muzzlePoint.position, muzzlePoint.rotation);
BUG以及缺陷:
使用对象池之后,子弹轨迹出现了异常显示,从对象池中取出子弹开火时,轨迹会出现在子弹从放回对象池中的位置到枪口位置,所以我让子弹轨迹过一小段时间再显示,以规避BUG。这样同样解决了手枪的子弹轨迹问题。
// 开启子弹轨迹协程 轨迹要过几帧再显示
public IEnumerator DisplayTrail()
{
yield return new WaitForSeconds(trailDisplayTime);
trail.enabled = true;
}
值得注意的:
最后
以上就是神勇人生为你收集整理的对象池——Unity随手记(2021.3.2)今天实现的内容:BUG以及缺陷:值得注意的:的全部内容,希望文章能够帮你解决对象池——Unity随手记(2021.3.2)今天实现的内容:BUG以及缺陷:值得注意的:所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复