Unity学习笔记--如何优雅简便地利用对象池生成游戏对象

前言

最近在复习自己所做的FPS项目,发现之前写的对象池代码简直就难以入目。。。
加上我最近学习了工厂模式。我就优化了下我的对象池代码。
更新后:代码更加简洁,更加易懂,操作方便,代码耦合性降低。

提前准备

  • 注意:本例子用的是子弹预制体,大家可以换其他的预制体。

1.我们需要在Assets文件夹下创建一个Resources文件夹
在这里插入图片描述
2. 需要在Resources文件夹下加入我们的预制体
在这里插入图片描述

实现步骤

步骤一:创建对象池管理者

创建一个对象池,让他继承自单例模板类。
(关于单例模板类怎么写:浅谈设计模式和其Unity中的应用:一、单例模式
PS:代码如果没有注释的话会简短很多,但是为了大家方便看懂,我还是打了注释。

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

public class PoolManager : Singleton<PoolManager>
{
    
    
    /// <summary>
    /// 所有的对象池,每一个元素都对应一个游戏对象的对象池
    /// </summary>
    private Dictionary<string, Stack<GameObject>> pool = new Dictionary<string, Stack<GameObject>>()
    {
    
    
        {
    
    "AssaultRifleBullet", new Stack<GameObject>() },           //步枪子弹对象池
        {
    
    "SniperBullet", new Stack<GameObject>() },                 //狙击子弹对象池
        {
    
    "HandgunBullet", new Stack<GameObject>() },                //手枪子弹对象池
        {
    
    "SMGBullet", new Stack<GameObject>() },                    //冲锋枪子弹对象池
        {
    
    "ShotGunBullet", new Stack<GameObject>() },                //霰弹枪子弹对象池
    };
    /// <summary>
    /// 存储所有的可以用于对象池的预制体,放在Resources文件夹下
    /// </summary>
    private Dictionary<string, GameObject> Prefabs = new Dictionary<string, GameObject>();
    [SerializeField]
    /// <summary>
    /// 存储需要动态加载的预制体的名字
    /// </summary>
    private List<string> prefabsName;
    protected override void Awake()
    {
    
    
        base.Awake();
        DontDestroyOnLoad(this);
        InitDictionary();
    }
    /// <summary>
    /// 动态加载预制体,注意Resources文件夹下的所有预制体的名字要和对象池名字相同
    /// </summary>
    private void InitDictionary()
    {
    
    
        for (int i = 0; i < prefabsName.Count; i++)
        {
    
    
            Prefabs.Add(prefabsName[i], (GameObject)Resources.Load(prefabsName[i]));
        }
    }
    /// <summary>
    /// 回收至对象池
    /// </summary>
    /// <param name="gameobjectName">游戏对象在对象池中的名字</param>
    /// <param name="go">被回收的游戏对象</param>
    public void CollectObject(string gameobjectName, GameObject go)
    {
    
    
        if(pool.TryGetValue(gameobjectName, out Stack<GameObject> stack))
        {
    
    
            //将游戏对象压入栈(加入到对象池)
            go.SetActive(false);
            stack.Push(go);
        }
    }
    /// <summary>
    /// 创建对象调用
    /// </summary>
    /// <param name="gameobjectName">游戏对象在对象池中的名字</param>
    /// <returns>从对象池取出来的游戏对象</returns>
    public GameObject Creat(string gameobjectName)
    {
    
    
        GameObject res = null;
        //如果存在该游戏对象所对应的对象池
        if (pool.TryGetValue(gameobjectName, out Stack<GameObject> stack) && Prefabs.ContainsKey(gameobjectName))
        {
    
    
            //如果对象池里面有对象,就直接拿出来,不然重新生成一个预制体。
            res = stack.Count >= 1 ? stack.Pop() : Instantiate(Prefabs[gameobjectName]);
            res?.gameObject.SetActive(true);
        }
        return res;
    }
    /// <summary>
    /// 创建对象调用
    /// </summary>
    /// <param name="gameobjectName">游戏对象在对象池中的名字</param>
    /// <param name="position">游戏对象的坐标</param>
    /// <param name="rotation">游戏对象的旋转</param>
    /// <returns>从对象池取出来的游戏对象</returns>
    public GameObject Creat(string gameobjectName, Vector3 position, Quaternion rotation)
    {
    
    
        GameObject res = Creat(gameobjectName);
        res.transform.position = position;
        res.transform.rotation = rotation;
        return res;
    }
}
  • 注意,处于Resources文件夹下的预制体名字要和prefabsName列表里的名字要一样。
    在这里插入图片描述在这里插入图片描述

步骤二:创建抽象工厂

这里限于篇幅,我就不贴代码了。
抽象工厂步骤我写过一个文章:浅谈设计模式和其Unity中的应用:三、抽象工厂模式

步骤三:子弹工厂代码

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

public class BulletFactory : Singleton<BulletFactory>, IBaseFactory
{
    
    
    protected override void Awake()
    {
    
    
        base.Awake();
        DontDestroyOnLoad(this);
    }
    /// <summary>
    /// 创建子弹时调用
    /// </summary>
    /// <param name="produceName">子弹名称:AssaultRifleBullet、SniperBullet、HandgunBullet、SMGBullet、ShotGunBullet</param>
    /// <returns>子弹</returns>
    public GameObject Creat(string produceName)
    {
    
    
        return PoolManager.Ins.Creat(produceName);
    }
    /// <summary>
    /// 创建子弹时调用
    /// </summary>
    /// <param name="produceName">子弹名称:AssaultRifleBullet、SniperBullet、HandgunBullet、SMGBullet、ShotGunBullet</param>
    /// <param name="position">子弹位置</param>
    /// <param name="rotation">子弹旋转</param>
    /// <returns>子弹</returns>
    public GameObject Creat(string produceName, Vector3 position, Quaternion rotation)
    {
    
    
        GameObject go = Creat(produceName);
        go.transform.position = position;
        go.transform.rotation = rotation;
        return go;
    }
}

步骤四:如何生成游戏对象

假设我的枪械脚本有一个射击函数,那我们就可以这样调用

public void HandleShot()
{
    
    
	GameObject bullet = FactoryProducer.GetFactory(FactoryType.BulletFactory).Creat(firearmBulletName);
}

步骤五:如何回收游戏对象

比如我的子弹脚本有一个定时器,过5s就自动回收本游戏对象,那我们就可以这么写:

private void RecoveryToPool()
{
    
    
    PoolManager.Ins.CollectObject(owner.firearmBulletName, gameObject);
}

添加新对象池

如果以后我们要增加一个对象池,只需要在PoolManager的对象池中添加一行代码在这里插入图片描述
然后在下方两张图添加对应预制体和预制体名字(和上方的对象池名字要一样)在这里插入图片描述在这里插入图片描述

注意事项

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这三张图片箭头指向的名字要一样,不然无法正确加载。

最后

如果大家有什么更好的方式,希望大佬可以指点下,一起学习,共同进步!

おすすめ

転載: blog.csdn.net/qq_52855744/article/details/122301626