UFL3-2——缓存池具体实现

缓存池具体实现

  1. 创建PoolManager​继承 不继承MonoBehaviour的单例模式基类

    1
    2
    3
    4
    5
    6
    7
    /// <summary>
    /// 缓存池模块 管理器
    /// </summary>
    public class PoolManager : BaseManager<PoolManager>
    {
    private PoolManager() { }
    }
  2. 声明柜子(Dictionary​)和抽屉(List​、Stack​、Queue​等)容器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /// <summary>
    /// 缓存池模块 管理器
    /// </summary>
    public class PoolManager : BaseManager<PoolManager>
    {
    //柜子容器当中有抽屉的体现
    private Dictionary<string, Stack<GameObject>> poolDic = new Dictionary<string, Stack<GameObject>>();

    private PoolManager() { }
    }
  3. 拿东西方法 GetObj

    1. 有抽屉并且抽屉里有东西 直接获取
    2. 没有抽屉或者抽屉里没东西 创造
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    /// <summary>
    /// 缓存池模块 管理器
    /// </summary>
    public class PoolManager : BaseManager<PoolManager>
    {
    //柜子容器当中有抽屉的体现
    private Dictionary<string, Stack<GameObject>> poolDic = new Dictionary<string, Stack<GameObject>>();

    private PoolManager() { }

    /// <summary>
    /// 拿东西的方法
    /// </summary>
    /// <param name="name">抽屉容器的名字</param>
    /// <returns>从缓存池中取出的对象</returns>
    public GameObject GetObj(string name)
    {
    GameObject obj;
    //有抽屉,且抽屉内有对象
    if (poolDic.ContainsKey(name) && poolDic[name].Count > 0)
    {
    obj = poolDic[name].Pop();
    obj.SetActive(true);
    }
    //否则,就应该去创建对象
    else
    {
    obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
    }
    return obj;
    }
    }
  4. 放东西方法 PushObj

    1. 有抽屉,直接放
    2. 没抽屉,创建抽屉,再放
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    /// <summary>
    /// 缓存池模块 管理器
    /// </summary>
    public class PoolManager : BaseManager<PoolManager>
    {
    //柜子容器当中有抽屉的体现
    private Dictionary<string, Stack<GameObject>> poolDic = new Dictionary<string, Stack<GameObject>>();

    private PoolManager() { }

    /// <summary>
    /// 往缓存池中放入对象
    /// </summary>
    /// <param name="name">抽屉(对象池)的名字</param>
    /// <param name="obj">要放入的对象</param>
    public void PushObj(string name, GameObject obj)
    {
    //并不是直接移除对象,而是将对象失活一会再用,用的时候再激活它
    //除了失活对象,还可以通过把对象放到很远的地方等方式来隐藏对象(失活的性能可能偏低一些)
    obj.SetActive(false);
    //如果不存在对应的抽屉容器,先创建抽屉,再往抽屉内放
    if (!poolDic.ContainsKey(name))
    poolDic.Add(name, new Stack<GameObject>());
    poolDic[name].Push(obj);
    }

    /// <summary>
    /// 拿东西的方法
    /// </summary>
    /// <param name="name">抽屉容器的名字</param>
    /// <returns>从缓存池中取出的对象</returns>
    public GameObject GetObj(string name)
    {
    GameObject obj;
    //有抽屉,且抽屉内有对象
    if (poolDic.ContainsKey(name) && poolDic[name].Count > 0)
    {
    obj = poolDic[name].Pop();
    obj.SetActive(true);
    }
    //否则,就应该去创建对象
    else
    {
    obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
    }
    return obj;
    }
    }
  5. 清空柜子方法 ClearPool

    我们在切场景时,对象都会被移除,这时应该清空柜子,否则会出现内存泄漏,并且下次取东西会出问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    /// <summary>
    /// 缓存池模块 管理器
    /// </summary>
    public class PoolManager : BaseManager<PoolManager>
    {
    //柜子容器当中有抽屉的体现
    private Dictionary<string, Stack<GameObject>> poolDic = new Dictionary<string, Stack<GameObject>>();

    private PoolManager() { }

    /// <summary>
    /// 清除整个柜子当中的数据
    /// </summary>
    public void ClearPool()
    {
    poolDic.Clear();
    }

    /// <summary>
    /// 拿东西的方法
    /// </summary>
    /// <param name="name">抽屉容器的名字</param>
    /// <returns>从缓存池中取出的对象</returns>
    public GameObject GetObj(string name)
    {
    GameObject obj;
    //有抽屉,且抽屉内有对象
    if (poolDic.ContainsKey(name) && poolDic[name].Count > 0)
    {
    obj = poolDic[name].Pop();
    obj.SetActive(true);
    }
    //否则,就应该去创建对象
    else
    {
    obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
    }
    return obj;
    }

    /// <summary>
    /// 往缓存池中放入对象
    /// </summary>
    /// <param name="name">抽屉(对象池)的名字</param>
    /// <param name="obj">要放入的对象</param>
    public void PushObj(string name, GameObject obj)
    {
    //并不是直接移除对象,而是将对象失活一会再用,用的时候再激活它
    //除了失活对象,还可以通过把对象放到很远的地方等方式来隐藏对象(失活的性能可能偏低一些)
    obj.SetActive(false);
    //如果不存在对应的抽屉容器,先创建抽屉,再往抽屉内放
    if (!poolDic.ContainsKey(name))
    poolDic.Add(name, new Stack<GameObject>());
    poolDic[name].Push(obj);
    }
    }

缓存池测试

实现一个按下鼠标左键或右键就从缓存池内分别取出两种对象,取出的对象过一秒后就压入池内
按照一定频率反复按下鼠标左键和右键,观察两种对象场景上的数量是否最后是恒定的

对象从池内取出时执行的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using UnityEngine;

public class DelayRemove : MonoBehaviour
{
public string poolName; //在Unity编辑器上输入对象所在路径

private void OnEnable()
{
//从池子内取出对象后,过一秒压回池内
Invoke("RemoveMe", 1f);
}

private void RemoveMe()
{
PoolManager.Instance.PushObj(poolName, this.gameObject);
}
}

调用缓存池的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using UnityEngine;

public class Main : MonoBehaviour
{
void Update()
{
if (Input.GetMouseButtonDown(0))
{
PoolManager.Instance.GetObj("Test/Cube");
}

if (Input.GetMouseButtonDown(1))
{
PoolManager.Instance.GetObj("Test/Sphere");
}
}
}

按照一定频率反复按下鼠标左键和右键,场景上创建的两种对象数量在一定时间后不再增长,被创建出来的对象不会被销毁而是被复用:

image

压入方法参数优化

假设要让 PushObj​ 方法只需要传入需要被压入池内的对象这一个参数,而不需要传入对象所在的名字(路径)
而原来这个传入的名字(路径)是用来确认对象需要压入到哪个池子的
因此,我们可以通过传入的对象的名字,来确认压入到哪个池子内,如果没有这个池子,就根据传入的名字创建对应的缓存池

值得一提的是,由于GameObject.Instantiate​方法克隆出来的对象名字会自带一个(clone)​,
因此我们需要在创建对象的时候,手动让对象的名字等于GetObj​方法传入的对象的名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 缓存池模块 管理器
/// </summary>
public class PoolManager : BaseManager<PoolManager>
{
//柜子容器当中有抽屉的体现
private Dictionary<string, Stack<GameObject>> poolDic = new Dictionary<string, Stack<GameObject>>();

private PoolManager() { }

/// <summary>
/// 拿东西的方法
/// </summary>
/// <param name="name">抽屉容器的名字</param>
/// <returns>从缓存池中取出的对象</returns>
public GameObject GetObj(string name)
{
GameObject obj;
//有抽屉,且抽屉内有对象
if (poolDic.ContainsKey(name) && poolDic[name].Count > 0)
{
obj = poolDic[name].Pop();
obj.SetActive(true);
}
//否则,就应该去创建对象
else
{
obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
//为了避免实例化出来的对象默认名字带一个"(clone)",我们需要重命名这个对象
obj.name = name;
}
return obj;
}

/// <summary>
/// 往缓存池中放入对象
/// </summary>
/// <param name="name">抽屉(对象池)的名字</param>
/// <param name="obj">要放入的对象</param>
public void PushObj(GameObject obj)
{
//并不是直接移除对象,而是将对象失活一会再用,用的时候再激活它
//除了失活对象,还可以通过把对象放到很远的地方等方式来隐藏对象(失活的性能可能偏低一些)
obj.SetActive(false);
//如果不存在对应的抽屉容器,先创建抽屉,再往抽屉内放
if (!poolDic.ContainsKey(obj.name))
poolDic.Add(obj.name, new Stack<GameObject>());
poolDic[obj.name].Push(obj);
}

/// <summary>
/// 清除整个柜子当中的数据
/// </summary>
public void ClearPool()
{
poolDic.Clear();
}
}

因此,调用 PushObj​ 方法不再需要额外传入对象的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using UnityEngine;

public class DelayRemove : MonoBehaviour
{
//public string poolName //不需要这个变量了

private void OnEnable()
{
Invoke("RemoveMe", 1f);
}

private void RemoveMe()
{
PoolManager.Instance.PushObj(this.gameObject);
}
}