【问题标题】:How to prevent the reload of already loaded gameobjects in Unity?如何防止在 Unity 中重新加载已加载的游戏对象?
【发布时间】:2020-02-05 22:17:42
【问题描述】:

我目前正在使用 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;
    }
}

这是我的 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()
    {

        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;
    }



}

【问题讨论】:

  • 你描述的行为首先听起来很奇怪......我会尝试解决这个问题,而不是试图找到看起来更糟糕的解决方法......加载场景应该重置一切,就像如果游戏开始了。你能展示一下你是如何加载场景的吗?
  • 我同意 @derHugo 的观点,听起来你正在破解一种解决方法来部分重置游戏。
  • 但这正是我想要发生的事情
  • 我在最后添加了负责加载和卸载场景的类
  • 看起来副本来自 Unloaded 场景。他们呆在那里并被禁用,直到场景完全卸载。由于lastLoadedScene 的默认值始终为&gt;=0,这可能会导致问题,因为您异步加载和卸载相同的场景?

标签: c# unity3d game-development scene-manager


【解决方案1】:

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

  1. 如果您需要保存单个对象实例,请使用单例模式。这种情况在其他情况下与保存管理器(GameplayManager、SceneController、AssetBundleManager等)相关的情况下,使用其他方式会更好。要了解有关实施的更多信息,您可以查看this article
  2. 加载新场景时销毁所有旧对象。为此,您可以使用SceneManager.LoadScene 方法和LoadSceneMode.Single 作为参数。它将保留 DontDestoryOnLoad 对象,但会删除所有其他对象。

【讨论】:

    【解决方案2】:

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

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

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

    下一个问题可能是您通过 SceneIndex ... 两个场景,您要卸载的场景和要加载的场景都有索引 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);
    

    不添加任何内容,因此只要新场景完全加载,当前场景就会自动删除。


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

    【讨论】:

    • 我在最后添加了我的 objectpooler。我正在查看您的代码
    • 另外我试图理解你写的一些代码,我假设我应该在括号之间使用一个 void awake 方法 // 延迟初始化 public static MySceneManager Instance ??但随后我收到一条错误消息,说 awake 返回 null 并且还收到错误消息,指出 loadscene 从其他类获取参数?
    猜你喜欢
    • 2022-10-01
    • 1970-01-01
    • 2019-05-11
    • 2012-05-18
    • 2018-04-11
    • 1970-01-01
    • 2013-11-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多