前言
最近在复习自己所做的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的对象池中添加一行代码
然后在下方两张图添加对应预制体和预制体名字(和上方的对象池名字要一样)
注意事项
这三张图片箭头指向的名字要一样,不然无法正确加载。
最后
如果大家有什么更好的方式,希望大佬可以指点下,一起学习,共同进步!