我是靠谱客的博主 神勇人生,最近开发中收集的这篇文章主要介绍对象池——Unity随手记(2021.3.2)今天实现的内容:BUG以及缺陷:值得注意的:,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

  • 今天实现的内容:
        • 对象池的概念
        • 简易对象池的实现
        • 多类型对象池的实现
        • 将对象池运用到项目中
  • 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以及缺陷:值得注意的:所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(51)

评论列表共有 0 条评论

立即
投稿
返回
顶部