1

我目前正在 Unity 中开发游戏,但遇到了一个小问题。我正在开发一个重新启动功能,当玩家死亡并再次加载第一个场景时会自动调用它。但是由于某种原因,当重新加载场景时,游戏对象会被复制,死亡时处于活动状态的游戏对象版本处于非活动状态,并且每次玩家死亡时加载的版本应该被加载设置为活动等等将相同游戏对象的新副本添加到层次结构中。我试图以多种方式解决这个问题。首先,通过附加一个脚本来检查每个被复制的游戏对象是否已经运行了一个实例,该脚本在每次场景发生变化时检查它们是否已经是存在的游戏对象的实例:

 public static GameObject Instance;

 void Awake()
 {
     if(Instance){
         DestroyImmediate(gameObject);
     }else
     {
         DontDestroyOnLoad(gameObject);
         Instance = this;
     }
 }

起初这似乎解决了问题,但到最后变得很麻烦,因为脚本使我所有的其他场景对象表现不佳或根本没有,所以我选择寻找另一种解决方案。

其次,我尝试在开始加载第一个场景之前销毁每个单独的游戏对象。起初这似乎也有效,但现在我的对象池只是重新创建游戏对象的新实例,它也添加了层次结构,基本上将相同的问题转移到其他游戏对象。

最后,为了解决这个问题,我试图让我的 objectpooler 在需要加载它的场景被调用时只运行一次,但这似乎也不起作用。有谁知道我如何解决这个问题。这是负责在玩家死亡时加载原始场景的脚本的一部分:

void Restart()
{
    GameObject[] allObjects = UnityEngine.Object.FindObjectsOfType<GameObject>();

    foreach (GameObject gos in allObjects)
    {
        if (gos.activeInHierarchy)
        {
            if (gos != GameObject.Find("GameManager") && gos != GameObject.Find("ScreenBound")) 
            {
                gos.SetActive(false);
            }
        }
    }
    MySceneManager.LoadScene(0, this);
}

我该如何更改它以便能够重新加载原始场景而不会GameObject复制任何先前加载的内容并根据它在最初加载的场景中的行为方式进行操作?

负责加载和卸载场景的类:

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

public static class MySceneManager
{

    private static int lastLoadedScene = 0;

    public static void LoadScene(int index, MonoBehaviour caller)
    {
        ObjectPooler objP = new ObjectPooler();
        objP.ReleaseAll();
        caller.StartCoroutine(loadNextScene(index));
    }

    private static IEnumerator loadNextScene(int index)
    {

        var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);


        _async.allowSceneActivation = false;
        while (_async.progress < 0.9f)
        {

            yield return null;
        }

        _async.allowSceneActivation = true;

        while (!_async.isDone)
        {
            yield return null;
        }


        var newScene = SceneManager.GetSceneByBuildIndex(index);


        if (!newScene.IsValid()) yield break;


        SceneManager.SetActiveScene(newScene);


        if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);

        lastLoadedScene = index;
    }
}

这是我的对象池:

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()
    {

        if (Instance)
        {
            Destroy(this.gameObject);
            return;
        }

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

    #endregion

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

    private Dictionary<string, Pool> prefabPools;

    void Start()
    {
        poolDictionary = new Dictionary<string, Queue<GameObject>>();

        foreach (Pool pool in pools)
        {
            Queue<GameObject> objectPool = new Queue<GameObject>();

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

            poolDictionary.Add(pool.tag, objectPool);


        }
    }

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



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

        obj.SetActive(false);


        obj.transform.SetParent(transform);


        poolDictionary[obj.tag].Enqueue(obj);
        DontDestroyOnLoad(obj);
    }

    public void ReleaseAll()
    {
        foreach (var child in currentlySpawnedObjects)
        {
            Release(child);
        }
    }

    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 = poolDictionary[tag].Dequeue();


        objectToSpawn.SetActive(true);
        objectToSpawn.transform.position = position;
        objectToSpawn.transform.rotation = rotation;

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

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

        poolDictionary[tag].Enqueue(objectToSpawn);

        return objectToSpawn;

        currentlySpawnedObjects.Add(objectToSpawn);

        return objectToSpawn;
    }



}
4

2 回答 2

0

根据您的需要,您可以尝试以下方法:

  1. 如果您需要保存对象的单个实例,请使用单例模式。这种情况在其他情况下与保存管理器(GameplayManager、SceneController、AssetBundleManager等)相关的情况下,使用其他方式会更好。要阅读有关实现的更多信息,您可以查看这篇文章
  2. 加载新场景时销毁所有旧对象。为此,您可以使用SceneManager.LoadScene带有LoadSceneMode.Single参数的方法。它会保留DontDestoryOnLoad对象,但会删除所有其他对象。
于 2019-10-08T15:55:11.823 回答
0

我不确定,但对我来说第一个可能的问题似乎是它在 Coroutine 中运行在你要卸载的场景中的一个对象上。

这是可行的,但请记住,一旦调用者对象/组件被销毁或禁用,协程就会停止工作。

为避免这种情况,我会将您的脚本移动到DontDestroyOnLoadScene使用单例模式的对象中。

下一个问题可能是您通过 SceneIndex ...两个场景,您要卸载的场景和要加载的场景都有 index 0

因此,您可能会在场景附加加载和要卸载的场景之间遇到冲突。

当您打电话时,这也可能再次发生

var newScene = SceneManager.GetSceneByIndex(lastLoadedScene);

为避免这种情况,我宁愿通过场景参考进行卸载

public class MySceneManager : MonoBehaviour
{
    private static MySceneManager instance;

    // Lazy initialization
    // With this you wouldn't even need this object in the scene
    public static MySceneManager Instance
    {
        if(instance) return instance;

        instance = new GameObject ("MySceneManager").AddComponent<MySceneManager>();

        DontDestroyOnLoad(instance);
    }

    // Usual instant initialization having this object in the scene
    private void Awake ()
    {
        if(instance && instance != this)
        {
            Destroy(gameObject);
            return;
        }

        instance = this;

        DontDestroyOnLoad(this);
    }



    public void LoadScene(int index)
    {
        StartCoroutine(loadNextScene(index));
    }

    private IEnumerator loadNextScene(int index)
    { 
        // I didn't completely go through your ObjectPooler but I guess you need to do this
        ObjectPooler.Instance.ReleaseAll();

        // Instead of the index get the actual current scene instance
        var currentScene = SceneManager.GetActiveScene();

        var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);


        _async.allowSceneActivation = false;
        yield return new WaitWhile(() => _async.progress < 0.9f);

        _async.allowSceneActivation = true;

        yield return new WaitUntil(() => _async.isDone);

        // You have to do this before otherwise you might again
        // get by index the previous scene 
        var unloadAsync = SceneManager.UnloadSceneAsync(currentScene);
        yield return new WaitUntil(()=>unloadAsync.isDone);            

        var newScene = SceneManager.GetSceneByBuildIndex(index);    

        SceneManager.SetActiveScene(newScene);
    }
}

或者,因为无论如何你在加载/卸载场景时没有做任何特别的事情:

Additive如果您也可以简单地调用,为什么还要使用场景加载

ObjectPooler.Instance.ReleaseAll();
SceneManager.LoadSceneAsync(index);

没有添加它,因此只要新场景完全加载,当前场景就会自动删除。


注意:智能手机上的类型,所以没有保修,但我希望这个想法很清楚

于 2019-10-08T16:24:27.373 回答