Menu
CoddyTech

使用队列实现对象池

本课属于 Coddy Unity 学习旅程的“Unity 游戏架构”单元。第 47 课,共 64 课。

对象池需要一个地方来存放备用对象,C# 为此提供了集合 Queue<T>:Enqueue 会在队尾添加一个项目,Dequeue 会取出队首的项目,Count 会显示有多少个项目在等待。这个水族箱每 0.3 秒会冒出一个气泡,每个气泡到达水面后都会回到对象池:

using System.Collections.Generic;
using UnityEngine;

public class BubblePool : MonoBehaviour
{
    public GameObject bubblePrefab;
    private Queue<GameObject> pool = new Queue<GameObject>();
    private float timer;

    void Update()
    {
        timer += Time.deltaTime;
        if (timer >= 0.3f)
        {
            timer = 0f;
            Get(new Vector3(Random.Range(-2f, 2f), 0f, 0f));
        }
    }

    public GameObject Get(Vector3 position)
    {
        GameObject bubble;
        if (pool.Count > 0)
        {
            bubble = pool.Dequeue();
        }
        else
        {
            bubble = Instantiate(bubblePrefab);
            bubble.GetComponent<Bubble>().pool = this;
        }
        bubble.transform.position = position;
        bubble.SetActive(true);
        return bubble;
    }

    public void Return(GameObject bubble)
    {
        bubble.SetActive(false);
        pool.Enqueue(bubble);
    }
}
// Bubble.cs,挂在气泡预制体上
public BubblePool pool;

void Update()
{
    transform.Translate(0f, 2f * Time.deltaTime, 0f);
    if (transform.position.y > 5f) pool.Return(gameObject);
}

Get 会先检查 Count,因为对空队列调用 Dequeue 会抛出 InvalidOperationException。只有在这之后才会调用 Instantiate,而且只有新气泡需要设置它的 pool 字段:复用的气泡仍保留该字段。队列会先交出等待时间最长的气泡,也就是先进先出。气泡到达水面时会调用 Return,而不是 Destroy;它会留在场景中并处于非激活状态,直到下一次调用 Get。

Get 会先设置气泡的位置,再调用 SetActive(true),因为启用气泡会立即运行它的 OnEnable,而其中的任何操作都应该看到新位置,而不是它上次消失时的位置。复用对象也会在这里重新开始:Awake 和 Start 在创建气泡时只运行一次,而 OnEnable 会在每次调用 Get 时运行。

challenge icon

挑战

中等

将脚印放入对象池,而不是销毁它们。

  1. 在 Trail 中,用 Queue<GameObject> 保存备用对象。Return(footprint) 会将脚印禁用并将其加入队列;Start 会创建 prewarm 个脚印并将它们放回队列。
  2. Get(position) 会从队列中取出一个备用脚印(如果没有,就创建一个),设置它的位置并将它启用。
  3. 在 Footprint 中,替换 Destroy:在 OnEnable 中重置年龄;经过 life 秒后,调用 trail.Return(gameObject)。

按“开始”并行走:脚印会在 1.5 秒后消失。然后按“检查”。

亲手试试

using System.Collections.Generic;
using UnityEngine;

public class Trail : MonoBehaviour
{
    public GameObject footprintPrefab;
    public float step = 1f;
    public int prewarm = 6;
    private Vector3 lastDrop;

    void Start()
    {
        lastDrop = transform.position;
        // 用 prewarm 个脚印填充对象池
    }

    void Update()
    {
        if (Vector3.Distance(transform.position, lastDrop) >= step)
        {
            lastDrop = transform.position;
            Get(new Vector3(transform.position.x, 0.02f, transform.position.z));
        }
    }

    public GameObject Get(Vector3 position)
    {
        GameObject footprint = Instantiate(footprintPrefab, position, Quaternion.identity);
        footprint.GetComponent<Footprint>().trail = this;
        return footprint;
    }

    public void Return(GameObject footprint)
    {
        // 备用脚印在对象池中等待使用
    }
}

Unity 游戏架构中的所有课

自己练习: Unity 在线编辑器