1

根据答案中给出的规范更新游戏后,我现在收到以下错误消息:

Assets\ObjectPooler.cs(79,41):错误 CS0103:当前上下文中不存在名称“池”。我理解为什么它不起作用,因为它是在另一种方法中声明的,但是我怎样才能更改我的代码来访问这个变量呢?

提前致谢

我还没有在游戏中添加 release 和 .sceneloaded 脚本

这是我的 ObjectPooler 脚本:

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

public class ObjectPooler : MonoBehaviour
{
    [System.Serializable]
    public class Pool
    {
        public string tag;
        public GameObject prefab;
        public int size;

    }

    #region Singleton 

    public static ObjectPooler Instance;

    private void Awake()
    {
        // Already another instance?
        if (Instance)
        {
            Destroy(this.gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(this.gameObject);
    }

    #endregion

    public List<Pool> pools;
    public Dictionary<string, Queue<GameObject>> poolDictionary;


    // Start is called before the first frame update
    //change this method to make it work everytime level is loaded
    private Dictionary<string, Pool> prefabPools;

    private void Start()
    {
        foreach (var pool in pools)
        {
            Queue<GameObject> objectPool = new Queue<GameObject>();

            for (int i = 0; i < pool.size; i++)
            {
                GameObject obj = Instantiate(pool.prefab);
                obj.transform.SetParent(transform);
                obj.SetActive(false);
                objectPool.Enqueue(obj);
            }

            prefabPools.Add(pool.tag, pool);
            poolDictionary.Add(pool.tag, objectPool);
        }
    }

    public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
    {
        if (!poolDictionary.ContainsKey(tag))
        {
            Debug.LogWarning("Pool with tag" + tag + " doesn't exist.");
            return null;
        }

        GameObject objectToSpawn;

        // check if there are objects left otherwise insteantiate a new one
        if (poolDictionary[tag].Count > 0)
        {
            objectToSpawn = poolDictionary[tag].Dequeue();
        }
        else
        {
            objectToSpawn = Instantiate(pool.prefab);
            objectToSpawn.transform.SetParent(transform);
        }

        objectToSpawn.transform.position = position;
        objectToSpawn.transform.rotation = rotation;

        objectToSpawn.SetActive(true);

        IPooledObject pooledObj = objectToSpawn.GetComponent<IPooledObject>();

        if (pooledObj != null)
        {
            pooledObj.OnObjectSpawn();
        }

        return objectToSpawn;
    }


}

这是 IPooledObject 接口:

using UnityEngine;

public interface IPooledObject
{
    void OnObjectSpawn();
}

这是从脚本调用池中的游戏对象的方式:

objectPooler.SpawnFromPool("Ball", spawnPoints[randomSpawnPoint].position, Quaternion.identity);

它应该工作的方式是,当我在游戏的不同场景之间转换时,对象池会创建一个新的对象池实例,或者它会被重置,它们会出现在屏幕上并根据脚本运行,而不是不出现和就像他们被摧毁一样。需要注意的一点是,如果 objectpooler 和对象在第一个场景中表现正常,并且仅在游戏在场景之间转换时才开始抛出错误消息,而在我编辑脚本以实例化对象而不使用 objectpooler 时才开始抛出错误消息,如下所示:

Instantiate(interact[Interact], spawnPoints[randomSpawnPoint].position,
                      Quaternion.identity); 

在我将游戏对象预制件存储在名称交互的公共数组中并通过索引调用它们的地方,脚本似乎也可以工作。但是我需要能够使用 ObjectPooler 以防止我的代码变得昂贵?

4

1 回答 1

1

首先,我不会将刚刚生成的对象重新排入队列,而是将其从队列中移除。然后,当队列为空时,会生成新的附加对象来使用,而不是重复使用可能已经被起诉的对象。

private Dictionary<string, Pool> prefabPools;

private void Start()
{
    foreach (var pool in pools)
    {
        Queue<GameObject> objectPool = new Queue<GameObject>();

        for (int i = 0; i < pool.size; i++)
        {
            GameObject obj = Instantiate(pool.prefab);
            obj.SetActive(false);
            objectPool.Enqueue(obj);
        }

        prefabPools.Add(pool.tag, pool);
        poolDictionary.Add(pool.tag, objectPool);
    }
}

public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
{
    if (!poolDictionary.ContainsKey(tag))
    {
        Debug.LogWarning("Pool with tag" + tag + " doesn't exist.");
        return null;
    }

    GameObject objectToSpawn;

    // check if there are objects left otherwise insteantiate a new one
    if(poolDictionary[tag].Count > 0)
    {
        objectToSpawn = poolDictionary[tag].Dequeue();
    }
    else
    {
        objectToSpawn = Instantiate(pool.prefab);
    }

    objectToSpawn.transform.position = position;
    objectToSpawn.transform.rotation = rotation;

    objectToSpawn.SetActive(true);

    IPooledObject pooledObj = objectToSpawn.GetComponent<IPooledObject>();

    if (pooledObj != null)
    {
        pooledObj.OnObjectSpawn();
    }

    return objectToSpawn;
}

您可以使用DontDestroyOnLoad以便在场景加载时永远不会破坏它,但会破坏新加载的

private void Awake ()
{
    // Already another instance?
    if(Instance) 
    {
        Destroy (this.gameObject);
        return;
    }

    Instance = this;
    DontDestroyOnLoad(this.gameObject);
}

然后确保您的池对象也不会通过使它们成为该对象的子对象而被破坏

GameObject obj = Instantiate(pool.prefab);
obj.SetParent(transform);

并且也在

objectToSpawn = Instantiate(pool.prefab);
objectToSpawn.SetParent(transform);

你应该有某种Release方法来禁用和重新排队对象而不是像销毁它们一样

public void Release(GameObject obj)
{
    obj.SetActive(false);

    // Assuming pool.tag euqals obj.tag
    // In general I would rather go by type instead
    poolDictionary[obj.tag].Enqueue(obj);
}

最后在场景改变时为每个对象运行这个

private void Awake()
{
    ...

    SceneManager.sceneLoaded -= OnSceneLoaded;
    SceneManager.sceneLoaded += OnSceneLoaded;
}

private void OnDestroy()
{
    ...

    SceneManager.sceneLoaded -= OnSceneLoaded;
}

private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
    foreach(var child in transform)
    {
        if(!child.activeInHierachy) continue;

        Release(child);
    }
}

或者,如果您不想让它们始终是池对象的子对象,您也可以在列表中跟踪它们,而不是像

private List<GameObject> currentlySpawnedObjects = new List<GameObject>();

//...

public void Release(GameObject obj)
{
    currentlySpawnedObjects.Remove(obj);

    obj.SetActive(false);

    // here you should still make it a child so it doesn't get destroyed
    // when the scene is changed
    obj.SetParent(transform);

    // Assuming pool.tag euqals obj.tag
    // In general I would rather go by type instead
    poolDictionary[obj.tag].Enqueue(obj);
}

//...

// call this BEFORE switching scenes
public void ReleaseAll()
{
    foreach(var child in currentlySpawnedObjects)
    {
        Release(child);
    }
}

所以现在你甚至可以扩展你的 spawn 方法来额外添加成为另一个 GameObject 的孩子的能力,就像Instantiate方法一样

private Dictionary<string, Pool> prefabPools;

private void Start()
{
    foreach (var pool in pools)
    {
        Queue<GameObject> objectPool = new Queue<GameObject>();

        for (int i = 0; i < pool.size; i++)
        {
            GameObject obj = Instantiate(pool.prefab);
            obj.SetActive(false);
            objectPool.Enqueue(obj);
        }

        prefabPools.Add(pool.tag, pool);
        poolDictionary.Add(pool.tag, objectPool);
    }
}

public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation, Transform parent = null)
{
    if (!poolDictionary.ContainsKey(tag))
    {
        Debug.LogWarning("Pool with tag" + tag + " doesn't exist.");
        return null;
    }

    GameObject objectToSpawn;

    // check if there are objects left otherwise insteantiate a new one
    if(poolDictionary[tag].Count > 0)
    {
        objectToSpawn = poolDictionary[tag].Dequeue();
    }
    else
    {
        objectToSpawn = Instantiate(prefabPools[tag].prefab);
    }

    if(parent)
    {
        objectToSpawn.SetParent(parent, false);
    }

    // you could also decide to use localPosition in case parent is set
    objectToSpawn.transform.position = position;
    objectToSpawn.transform.rotation = rotation;

    objectToSpawn.SetActive(true);

    IPooledObject pooledObj = objectToSpawn.GetComponent<IPooledObject>();

    if (pooledObj != null)
    {
        pooledObj.OnObjectSpawn();
    }

    currentlySpawnedObjects.Add(objectToSpawn);

    return objectToSpawn;
}
于 2019-07-25T13:36:43.867 回答