unity对象池的使用

对象池的用法和意义

为什么要用对象池,它有什么用呢?它一般用在哪些方面呢?比如,你要制作一个射击类游戏,射击肯定要经常发射子弹吧,如果你不使用对象池,那么你每发射一颗子弹就要实例化一颗子弹,之后还要Destroy销毁掉,不断的实例化和销毁,是非常消耗性能的,小游戏还好,如果是比较大点的游戏,很容易造成卡顿现象,这样玩家肯定就不乐意玩了。又比如跑酷游戏,游戏中会反复的生成金币和障碍物和道具,如果也是反复的生成销毁的话,性能非常差。所以就引入了对象池。

对象池的实现

无非就是,游戏物体在用的时候就设为显示的,即SetActive(true),用不到的时候就SetActive(false),当要使用的false的游戏物体用完了,就再实例化出来,没用完的时候,将false的游戏物体设为true即可。

当游戏中有许多的物品需要重复的实例化和销毁时,如金币、小车、栅栏、磁铁等,这个时候我们可以先建一个子池子,不同类型的东西分别在不同的子池子里,最后由一个大池子统一管理。

子池子的实现

在实现子池子之前,先写个接口,C#中接口可以多继承,而类只能单继承,所以使用接口,接口名一般由大写的I开头。代码如下:

using System.Collections;
using System.Collections.Generic;

public interface IReusable
{
    //取出的时候调用
    void OnSpawn();

     //回收的时候调用
    void OnUnSpawn();
}

接口写完后,再写个类,让这个类同时继承MonoBehaviour和 IReusable。
之后在对象池中只需要继承这个新写的类就可以了,相当于同时继承了MonoBehaviour和 IReusable。新的类代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class ReusableObject : MonoBehaviour, IReusable
{
    public abstract void OnSpawn();

    public abstract void OnUnSpawn();
}

为什么要先写这个接口和这个类呢,因为下面的子池子中会用到。
就是go.SendMessage(“OnSpawn”,SendMessageOptions.DontRequireReceiver);
和go.SendMessage(“OnUnSpawn”,SendMessageOptions.DontRequireReceiver);
将游戏物体设为true时,发送消息让该游戏物体执行OnSpawn()的方法,可以在OnSpawn中设置生成的游戏物体的位置和大小之类的。

将游戏物体设为false时,发送消息让该游戏物体执行OnUnSpawn()的方法,可以在OnUnSpawn中将变换过的游戏物体恢复成原来的模样,如果没有变换过,那就在OnUnSpawn中留空就好了。
子池子的代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SubPool
{
	//这个是负责保存子池子中的游戏物体
    private List<GameObject> m_objects = new List<GameObject>();

    private GameObject m_prefab;

    public string Name { get { return m_prefab.name; } }//每个子池子都有一个名字标识

    private Transform m_parent;

    public SubPool(Transform parent,GameObject go)
    {
        m_parent = parent;
        m_prefab = go;
    }

    /// <summary>
    /// 取出物体
    /// </summary>
    /// <returns></returns>
    public GameObject Spawn()
    {
        GameObject go = null;
        foreach (var obj in m_objects)
        {
            if(!obj.activeSelf)		//!obj.activeSelf代表游戏物体是false的;
            {
                go = obj;
                break;
            }
        }
        if(go==null)
        {
            go = GameObject.Instantiate<GameObject>(m_prefab);
            go.transform.parent = m_parent;
            m_objects.Add(go);
        }
        go.SetActive(true);
        go.SendMessage("OnSpawn", SendMessageOptions.DontRequireReceiver);
        return go;
    }

    /// <summary>
    /// 回收一个游戏物体
    /// </summary>
    /// <param name="go"></param>
    public void UnSpawn(GameObject go)
    {
        if(Contain(go))
        {
            go.SendMessage("OnUnSpawn", SendMessageOptions.DontRequireReceiver);
            go.SetActive(false);
        }
    }

    /// <summary>
    /// 回收所有游戏物体
    /// </summary>
    public void UnSpawnAll()
    {
        foreach (var obj in m_objects)
        {
            if(obj.activeSelf)
            {
                UnSpawn(obj);
            }
        }
    }

    /// <summary>
    /// 判断是否属于list里面,,因为m_objects是私有的,所以在大池子中访问不到,无法判断小池子中是否包含有传进来的游戏物体,所以这里提供一个供外部访问的方法。
    /// </summary>
    /// <returns></returns>
    public bool Contain(GameObject go)
    {
        return m_objects.Contains(go);
    }
}

小池子建完了以后,就需要建一个大池子来统一管理这些小池子了。
大池子的代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjectPool : MonoSingleton<ObjectPool>
{
    /// <summary>
    /// 资源目录,就是需要实例化的那些游戏物体所在的路径
    /// </summary>
    public string ResourceDir = "";
	//游戏物体的名字对应相应的子池子
    private Dictionary<string, SubPool> m_pools = new Dictionary<string, SubPool>();
	
    public GameObject Spawn(string name,Transform trans)
    {
        SubPool pool = null;
        if(!m_pools.ContainsKey(name))
        {
            RegisteNew(name,trans);
        }
        pool = m_pools[name];
        return pool.Spawn();
    }

    /// <summary>
    /// 新建一个池子
    /// </summary>
    private void RegisteNew(string names,Transform trans)
    {
        string path = ResourceDir + "/" + names;
        GameObject go = Resources.Load<GameObject>(path);
        SubPool pool = new SubPool(trans, go);
        m_pools.Add(pool.Name, pool);
    }

    /// <summary>
    /// 清除所有
    /// </summary>
    public void Clear()
    {
        m_pools.Clear();
    }

    /// <summary>
    /// 回收物体
    /// </summary>
    /// <param name="go"></param>
    public void UnSpawn(GameObject go)
    {
        SubPool pool = null;
        foreach (var p in m_pools.Values)
        {
            if(p.Contain(go))
            {
                pool = p;
                break;
            }
        }
        pool.UnSpawn(go);
    }

    //回收所有物体
    public void UnSpawnAll()
    {
        foreach (var p in m_pools.Values)
        {
            p.UnSpawnAll();
        }
    }
}

大池子继承自 MonoSingleton< ObjectPool>,就是将自身变成一个单例,单例的内容请移步https://blog.csdn.net/weixin_43839583/article/details/103405296

因为大池子只有一个,并且需要频繁的使用,所以使用到单例。

对象池的使用就到这里了,性能非常的高效。

我对象池已经建好了,那么我的对象在哪啊,国家怎么还没给我分配对象。

2020.7.23更新:

自己重新写了对象池,将多个池子整合成一个,以方便使用。代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjectPool : MonoSingleton<ObjectPool>
{
    [Header("需要生成的预制体")]
    public GameObject[] prefabs;
    [Header("生成的预制体的父物体")]
    public Transform[] parentTrans;
    [Header("生成预制体的数量")]
    public int num = 100;
    //每个预制体对应生成的游戏物体的列表
    private List<GameObject>[] goList;
    //每个预制体的名字对应一个生成的预制体列表
    private Dictionary<string, List<GameObject>> poolDic = new Dictionary<string, List<GameObject>>();

    protected override void Awake()
    {
        base.Awake();
        goList = new List<GameObject>[num];
        for (int i = 0; i < num; i++)
        {
            goList[i] = new List<GameObject>();
        }

        for (int i = 0; i < prefabs.Length; i++)
        {
            for (int j = 0; j < num; j++)
            {
                GameObject go = Instantiate<GameObject>(prefabs[i]);
                go.transform.parent = parentTrans[i];
                go.SetActive(false);
                goList[i].Add(go);
            }
            poolDic.Add(prefabs[i].name, goList[i]);
        }
    }

    /// <summary>
    /// 根据预制体名字来生成游戏物体
    /// </summary>
    /// <param name="prefabName">预制体名字</param>
    /// <returns></returns>
    public GameObject SpawnGo(string prefabName)
    {
        //查找当前预制体名字对应的下标
        int index = 0;
        for (int i = 0; i < prefabs.Length; i++)
        {
            if (prefabs[i].name == prefabName)
                index = i;
        }

        if (poolDic.ContainsKey(prefabName))//如果字典中本来就存有这个预制体
        {
            for (int i = 0; i < poolDic[prefabName].Count; i++)//遍历该预制体所对应的列表
            {
                if(poolDic[prefabName][i].activeSelf==false)
                {
                    poolDic[prefabName][i].SetActive(true);
                    return poolDic[prefabName][i];
                }
            }
            
            GameObject go = Instantiate<GameObject>(prefabs[index]);
            go.transform.parent = parentTrans[index];
            poolDic[prefabName].Add(go);
            return go;
        }
        else//字典中不含这个预制体时
        {
            poolDic.Add(prefabName, goList[index]);
            GameObject go = Instantiate<GameObject>(prefabs[index]);
            go.transform.parent = parentTrans[index];
            goList[index].Add(go);
            return go;
        }
    }

    /// <summary>
    /// 回收一个游戏物体
    /// </summary>
    /// <param name="go"></param>
    public void RecycleGo(GameObject go)
    {
        //查找当前预制体名字对应的下标
        int index = 0;
        for (int i = 0; i < prefabs.Length; i++)
        {
            if (prefabs[i].name == go.name)
                index = i;
        }
        if(goList[index].Contains(go))
        {
            go.SetActive(false);
        }
    }

    /// <summary>
    /// 回收所有游戏物体
    /// </summary>
    public void RecycleAllGo()
    {
        for (int i = 0; i < prefabs.Length; i++)
        {
            for (int j = 0; j < goList[i].Count; j++)
            {
                if (goList[i][j].activeSelf)
                    goList[i][j].SetActive(false);
            }
        }
    }
}

2020.12.06新增新方法

(我觉得这样写更好)

代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class ObjectPool<T>
{
    private Func<T> func;
    private List<T> pool = new List<T>();
    public ObjectPool(Func<T> func,int count)
    {
        this.func = func;
        InstanceObject(count);
    }

    /// <summary>
    /// 从对象池里取对象
    /// </summary>
    /// <returns></returns>
    public T GetObject()
    {
        int i = pool.Count;
        if(i-->0)
        {
            T t = pool[i];
            pool.RemoveAt(i);
            return t;
        }
        InstanceObject(3);
        return GetObject();
    }

    /// <summary>
    /// 添加对象到对象池
    /// </summary>
    /// <param name="t"></param>
    public void AddObject(T t)
    {
        pool.Add(t);
    }

    /// <summary>
    /// 实例化对象
    /// </summary>
    /// <param name="count"></param>
    private void InstanceObject(int count)
    {
        for (int i = 0; i < count; i++)
        {
            pool.Add(func());
        }
    }
}

用法如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    public GameObject ballPrefab;
    private ObjectPool<Ball> ballPool;
    void Start()
    {
        ballPool = new ObjectPool<Ball>(InstanceBallFunc, 100);
    }

    private Ball InstanceBallFunc()
    {
        GameObject ball = Instantiate(ballPrefab, transform.Find("ballPool"));
        ball.SetActive(false);
        Ball ballScript = ball.AddComponent<Ball>();
        return ballScript;
    }
}

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中,可以通过编写脚本来手动实现对象池,也可以使用Unity提供的对象池类来创建对象池。 以下是手动实现对象池的示例代码: ```csharp using UnityEngine; using System.Collections.Generic; public class ObjectPool : MonoBehaviour { public GameObject prefab; // 要缓存的对象的预制件 public int poolSize; // 缓存池的大小 private List<GameObject> objectPool = new List<GameObject>(); 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 ReturnObject(GameObject obj) { obj.SetActive(false); } } ``` 以上代码演示了如何手动实现一个简单的对象池。首先在Start方法中创建一定数量的对象,然后在GetObject方法中遍历对象池,找到一个未激活的对象返回。如果没有空闲对象,则创建一个新对象并添加到对象池中。最后,在ReturnObject方法中将不再需要的对象放回对象池,将其激活状态设为false。 使用以上代码时,需要将希望缓存的对象的预制件赋值给prefab变量,并指定缓存池的大小。 Unity也提供了可重用对象池的类ObjectPool,可以在需要使用时直接调用。以下是Unity官方文档中的示例代码: ```csharp using UnityEngine; using UnityEngine.Pool; public class ObjectPoolExample : MonoBehaviour { public GameObject prefab; public int poolSize; private ObjectPool<GameObject> objectPool; void Start() { objectPool = new ObjectPool<GameObject>(() => Instantiate(prefab), poolSize, item => item.SetActive(true), item => item.SetActive(false)); } void Update() { // 从池中获取对象 GameObject obj = objectPool.Get(); // 将对象放回池中 objectPool.Release(obj); } } ``` 以上代码演示了如何使用Unity提供的ObjectPool类创建可重用对象池。首先,在Start方法中创建一个ObjectPool对象,并指定预制件创建方法、池大小、对象取出和放回时的回调函数等。然后,在Update方法中可以通过Get方法取出对象,通过Release方法将对象放回对象池中。 无论是手动实现对象池还是使用Unity提供的ObjectPool类,都可以有效地减少对象创建和销毁的次数,提高性能并减少内存占用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值