Как предотвратить перезагрузку уже загруженных игровых объектов в Unity? - PullRequest
0 голосов
/ 08 октября 2019

Я сейчас разрабатываю игру в 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;
    }



}

Ответы [ 2 ]

0 голосов
/ 08 октября 2019

Я не уверен, но первая возможная проблема для меня уже, кажется, заключается в том, что он работает в Coroutine на объекте в сцене, которую вы собираетесь выгрузить.

Круто, что это выполнимоно имейте в виду, что Coroutine перестанет работать, как только объект / компонент вызывающей стороны будет уничтожен или отключен.

Чтобы избежать этого, я бы переместил ваш скрипт к объекту в DontDestroyOnLoadScene, используя шаблон Singleton.

Следующей проблемой может быть то, что вы идете по 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);

, не делая его аддитивным, чтобы текущая сцена просто автоматически удалялась, как только новая сцена полностью загружалась.


Примечание: типына смартфоне, так что никаких гарантий, но я надеюсь, что идея становится ясной

0 голосов
/ 08 октября 2019

В зависимости от ваших потребностей вы можете попробовать следующие способы:

  1. Используйте шаблон синглтона, если вам нужно сохранить один экземпляр объектов. Этот случай актуален для сохранения менеджеров (GameplayManager, SceneController, AssetBundleManager и т. Д.), В других случаях будет лучше использовать другие способы. Подробнее о реализации вы можете прочитать в этой статье .
  2. Уничтожить все старые объекты при загрузке новой сцены. Для этого вы можете использовать метод SceneManager.LoadScene с параметром LoadSceneMode.Single. Он сохранит DontDestoryOnLoad объектов, но удалит все остальные.
...