Как я могу использовать синглтон при переключении / загрузке сцен? - PullRequest
0 голосов
/ 17 апреля 2020

В Иерархии у меня есть 3 объекта, которые я хочу сохранить и не уничтожить при запуске новой игры. Но я хочу уничтожить их при переключении обратно в главное меню.

Объекты: игрок, менеджер игр, загрузчик сцен

Hierarchy

На 3-х объектах Player, Game Manager, Scene Loader я добавил к каждому из них имя сценария PersistentManager:

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

public class PersistentManager : MonoBehaviour
{
    public static PersistentManager Instance { get; private set; }

    private void Awake()
    {
        if(Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

Сценарий Scene Loader, прикрепленный к Scene Loader:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class SceneLoader : MonoBehaviour
{
    private bool loadScene = false;

    [SerializeField]
    private int scene;
    [SerializeField]
    private Text loadingText;

    // Updates once per frame
    void Update()
    {
        // If the player has pressed the space bar and a new scene is not loading yet...
        if (Input.GetKeyUp(KeyCode.Space) && loadScene == false)
        {
            LoadScene(scene);
        }

        // If the new scene has started loading...
        if (loadScene == true)
        {
            // ...then pulse the transparency of the loading text to let the player know that the computer is still working.
            loadingText.color = new Color(loadingText.color.r, loadingText.color.g, loadingText.color.b, Mathf.PingPong(Time.time, 1));
        }
    }

    // The coroutine runs on its own at the same time as Update() and takes an integer indicating which scene to load.
    IEnumerator LoadNewScene()
    {
        // This line waits for 3 seconds before executing the next line in the coroutine.
        // This line is only necessary for this demo. The scenes are so simple that they load too fast to read the "Loading..." text.
        //yield return new WaitForSeconds(3);

        // Start an asynchronous operation to load the scene that was passed to the LoadNewScene coroutine.
        AsyncOperation async = SceneManager.LoadSceneAsync(scene);

        // While the asynchronous operation to load the new scene is not yet complete, continue waiting until it's done.
        while (!async.isDone)
        {
            yield return null;
        }
    }

    public void LoadScene(int scene)
    {
        // ...set the loadScene boolean to true to prevent loading a new scene more than once...
        loadScene = true;

        // ...change the instruction text to read "Loading..."
        loadingText.text = "Loading...";

        this.scene = scene;

        // ...and start a coroutine that will load the desired scene.
        StartCoroutine(LoadNewScene());
    }
}

И скрипт Game Manager, который прикреплен к Game Manager:

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

public class GameManager : MonoBehaviour
{
    public SceneLoader sceneLoader;
    public PlayerController playerController;
    public CamMouseLook camMouseLook;
    public static bool backToMainMenu = false;
    public static bool togglePauseGame;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.P))
        {
            PauseGame();
        }

        if (Input.GetKeyDown(KeyCode.Escape))
        {
            BackToMainMenu();
        }
    }

    public void PauseGame()
    {
        togglePauseGame = !togglePauseGame;

        if (togglePauseGame == true)
        {
            playerController.enabled = false;
            camMouseLook.enabled = false;
            Time.timeScale = 0f;
        }
        else
        {
            playerController.enabled = true;
            camMouseLook.enabled = true;
            Time.timeScale = 1f;
        }
    }

    private void BackToMainMenu()
    {
        sceneLoader.LoadScene(0);
        playerController.enabled = false;
        camMouseLook.enabled = false;
        Cursor.lockState = CursorLockMode.None;
        Time.timeScale = 0f;
        backToMainMenu = true;
    }
}

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

Перед использованием сценария PersistentManager я использовал для каждого из 3 объектов по одному сценарию:

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

public class DontDestroy : MonoBehaviour
{
    private void Awake()
    {
        if (GameManager.backToMainMenu == false)
        {
            DontDestroyOnLoad(transform);
        }
    }
}

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

Итак, у меня было 6 копий объектов. 3 в сцене главного меню и 3 в сцене DontDestroyOnLoad. Вот почему я пытаюсь использовать синглтон.

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

1 Ответ

0 голосов
/ 17 апреля 2020

Проблема в том, что у класса, от которого вы наследуете, есть экземпляр в родительском

public static PersistentManager Instance { get; private set; }

, это означает, что первый объект будет go

  1. Экземпляр нулевой? да
  2. Установить меня как экземпляр
  3. Установить не уничтожать при загрузке

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

В общем, я бы посоветовал не использовать синглтоны как можно чаще, так как некоторые могут считать их шаблоном анти-дизайна.

Однако, если вы все еще хотите использовать один; Здесь я прилагаю скрипт, который я использовал в играх, которые находятся в производстве в течение нескольких лет с миллионами пользователей, поэтому он уже настроен на большинство крайних случаев, с которыми мы столкнулись.

#region Using

using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;

#endregion

/// <summary>
///     Generic singleton Class. Extend this class to make singleton component.
///     Example:
///     <code>
/// public class Foo : GenericSingleton<Foo>
/// </code>
///     . To get the instance of Foo class, use <code>Foo.instance</code>
///     Override <code>Init()</code> method instead of using <code>Awake()</code>
///     from this class.
/// </summary>
public abstract class GenericSingleton<T> : MonoBehaviour where T : GenericSingleton<T>
{
    private static T _instance;

    [SerializeField, Tooltip("If set to true, the gameobject will deactive on Awake")] private bool _deactivateOnLoad;
    [SerializeField, Tooltip("If set to true, the singleton will be marked as \"don't destroy on load\"")] private bool _dontDestroyOnLoad;

    private bool _isInitialized;

    public static T instance
    {
        get
        {
            // Instance required for the first time, we look for it
            if (_instance != null)
            {
                return _instance;
            }

            var instances = Resources.FindObjectsOfTypeAll<T>();
            if (instances == null || instances.Length == 0)
            {
                return null;
            }

            _instance = instances.FirstOrDefault(i => i.gameObject.scene.buildIndex != -1);
            if (Application.isPlaying)
            {
                _instance?.Init();
            }
            return _instance;
        }
    }

    // If no other monobehaviour request the instance in an awake function
    // executing before this one, no need to search the object.
    protected virtual void Awake()
    {
        if (_instance == null || !_instance || !_instance.gameObject)
        {
            _instance = (T)this;
        }
        else if (_instance != this)
        {
            Debug.LogError($"Another instance of {GetType()} already exist! Destroying self...");
            Destroy(this);
            return;
        }
        _instance.Init();
    }

    /// <summary>
    ///     This function is called when the instance is used the first time
    ///     Put all the initializations you need here, as you would do in Awake
    /// </summary>
    public void Init()
    {
        if (_isInitialized)
        {
            return;
        }

        if (_dontDestroyOnLoad)
        {
            DontDestroyOnLoad(gameObject);
        }

        if (_deactivateOnLoad)
        {
            gameObject.SetActive(false);
        }

        SceneManager.activeSceneChanged += SceneManagerOnActiveSceneChanged;

        InternalInit();
        _isInitialized = true;
    }

    private void SceneManagerOnActiveSceneChanged(Scene arg0, Scene scene)
    {
        // Sanity
        if (!instance || gameObject == null)
        {
            SceneManager.activeSceneChanged -= SceneManagerOnActiveSceneChanged;
            _instance = null;
            return;
        }

        if (_dontDestroyOnLoad)
        {
            return;
        }

        SceneManager.activeSceneChanged -= SceneManagerOnActiveSceneChanged;
        _instance = null;
    }

    protected abstract void InternalInit();

    /// Make sure the instance isn't referenced anymore when the user quit, just in case.
    private void OnApplicationQuit()
    {
        _instance = null;
    }

    void OnDestroy()
    {
        // Clear static listener OnDestroy
        SceneManager.activeSceneChanged -= SceneManagerOnActiveSceneChanged;

        StopAllCoroutines();
        InternalOnDestroy();
        if (_instance != this)
        {
            return;
        }
        _instance = null;
        _isInitialized = false;
    }

    protected abstract void InternalOnDestroy();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...