Самый простой способ вести диалог между сценами без использования настроек игрока и т. Д. - PullRequest
1 голос
/ 21 марта 2019

Я работал над диалоговой системой для своей игры, и мне было интересно, знает ли кто-нибудь, как сохранить систему между различными сценами. Я знаю, что вы можете использовать такие вещи, как предпочтения игрока, но, с одной стороны, я не понимаю этого, и после исследования люди обычно не рекомендуют его для хранения больших сложных вещей. Мне удалось приблизиться к этому, используя dontDestroy так же, как вы это делали с персонажем, однако это не сработало полностью, так как кнопка переключения на следующую строку текста, конечно, сломалась вместе с синглтоном, который я создал для своего система. Что было бы для меня лучшим способом?

Вот весь мой код на всякий случай:

Создание скриптового объекта:

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

[CreateAssetMenu(fileName = "New Dialogue", menuName = "Dialogues")]
public class Dialogue : ScriptableObject
{
    [System.Serializable]
    public class Info
    {
        public string myName;
        public Sprite portrait;
        [TextArea(4, 8)]
        public string mytext;
    }
    [Header("Insert Dialogue Info Below")]
    public Info[] dialogueInfoSection;

}

Основной код для системы (сиглтон ломается здесь при переключении сцен):

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

public class MainDialogueManager : MonoBehaviour
{
    public static MainDialogueManager instance;

    private void Awake()
    {
        if(instance != null)
        {
            Debug.LogWarning("FIX THIS" + gameObject.name);
        }
        else
        {
            instance = this;
        }
    }

    public GameObject DialogueBoX;

    public Text dialogueNameofChar;
    public Text characterSays;
    public Image characterPortrait;
    private float textDelay = 0.005f;

    public Queue<Dialogue.Info> dialogueInfoSection = new Queue<Dialogue.Info>();

    public void EnqueueDialogue(Dialogue db)
    {
        DialogueBoX.SetActive(true);
        dialogueInfoSection.Clear();

        foreach(Dialogue.Info info in db.dialogueInfoSection)
        {
            dialogueInfoSection.Enqueue(info);
        }

        DequeueDialogue();
    }

    public void DequeueDialogue()
    {
        if (dialogueInfoSection.Count==0)
        {
            ReachedEndOfDialogue();
            return; /////
        }
        Dialogue.Info info = dialogueInfoSection.Dequeue();

        dialogueNameofChar.text = info.myName;
        characterSays.text = info.mytext;
        characterPortrait.sprite = info.portrait;

        StartCoroutine(TypeText(info));
    }

    IEnumerator TypeText(Dialogue.Info info)
    {
        characterSays.text= "";
        foreach(char c in info.mytext.ToCharArray())
        {
            yield return new WaitForSeconds(textDelay);
            characterSays.text += c;
            yield return null;
        }
    }

    public void ReachedEndOfDialogue()
    {
        DialogueBoX.SetActive(false);
    }

}

Активация диалога:

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

public class MainDialogueActivation : MonoBehaviour
{
    public Dialogue dialogue;

    public void startActivationofDialogue()
    {
        MainDialogueManager.instance.EnqueueDialogue(dialogue);
    }
    private void Start()
    {
        startActivationofDialogue();
    }
}

Перейти к следующей строке диалога:

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

public class MainDialogueButtons : MonoBehaviour
{
   public void GoToNextDialogueLine()
    {
        MainDialogueManager.instance.DequeueDialogue();
    }
}

Ответы [ 2 ]

1 голос
/ 21 марта 2019

Это может быть немного непопулярным мнением, но использование синглтона вполне подойдет. Просто MonoBehaviour синглтоны хитры, вы можете использовать Object.DontDestroyOnLoad (instance). Но вещи становятся ужасными, потому что они не разрушаются при смене сцены (хорошо), но если вы вернетесь на сцену, она загрузит другую (плохо). Есть несколько способов обойти это, например, уничтожить сам объект, если уже есть экземпляр, или создать подцену.

Я бы предложил не использовать синглтоны MonoBehaviour и использовать синглтоны ScriptableObject. Вы можете создать ленивый экземпляр, поместив ресурс в папку ресурсов и использовать Resource.Load, как это.

public class ScriptableSingleton<T> : ScriptableObject where T : ScriptableSingleton<T> {

    private static string ResourcePath {
        get {
            return typeof(T).Name;
        }
    }

    public static T Instance {
        get {
            if (instance == null) {
                instance = Resources.Load(ResourcePath) as T;
            }
            return instance;
        }
    }

    private static T instance;
}

С помощью этого кода вы создаете класс Singleton, скажем DialogueManager, создаете для него DialogueManager.asset и помещаете его в папку «Ресурсы».

1 голос
/ 21 марта 2019

Как на счет этого?

Идея очень похожа на то, что вы делаете, с несколькими изменениями:

  • Я сохраняю активный диалог вобъект сценариев (DialogueSystem), чтобы он мог сохраняться между сценами.Каждый раз, когда я загружаю новую сцену, я проверяю, есть ли активный диалог, и если да, то я показываю всплывающее диалоговое окно в Start().
  • , в то время как вы удаляете секцию диалога, которую вы в настоящее время показываетигрок из текущего диалога, я не удаляю текущий раздел, пока игрок не перейдет к следующему разделу.Это необходимо, потому что вам может потребоваться повторно показать тот же раздел, если вы переместитесь в новую сцену.

Убедитесь, что вы создали экземпляр объекта DialogueSystem и можете назначить его дляMainDialogueActivation и MainDialogManager

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

MainDialogueActiviation.cs

using UnityEngine;
using UnityEngine.SceneManagement;

public class MainDialogueActivation : MonoBehaviour
{
    public Dialogue dialogue;

    // This scriptable object stores the active dialog so that you
    // can persist it between scenes
    public DialogueSystem dialogSystem;

    private void Start()
    {
        // If we had an active dialog from the previous scene, resume that dialog
        if (dialogSystem?.dialogInfoSections.Count > 0)
        {
            GetComponent<MainDialogueManager>().ShowDialog();
        }
    }

    private void Update()
    {
        // Pressing D queues and shows a new dialog
        if (Input.GetKeyDown(KeyCode.D))
        {
            GetComponent<MainDialogueManager>().EnqueueDialogue(this.dialogue);
        }

        // Pressing C ends the current dialog
        if (Input.GetKeyDown(KeyCode.C))
        {
            this.dialogSystem.dialogInfoSections.Clear();
            GetComponent<MainDialogueManager>().ReachedEndOfDialogue();
        }

        // Pressing S swaps between two scenes so you can see the dialog
        // persisting
        if (Input.GetKeyDown(KeyCode.S))
        {
            if (SceneManager.GetActiveScene().name == "Scene 1")
            {
                SceneManager.LoadScene("Scene 2");
            }
            else if (SceneManager.GetActiveScene().name == "Scene 2")
            {
                SceneManager.LoadScene("Scene 1");
            }
        }
    }
}

MainDialogueManager.cs

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

public class MainDialogueManager : MonoBehaviour
{
    // This scriptable object stores the active dialog
    public DialogueSystem dialogSystem;

    public GameObject DialogueBox;

    public Text dialogueNameofChar;
    public Text characterSays;
    public Image characterPortrait;
    private float textDelay = 0.005f;

    // The game object for the dialog box that is instantiated in this
    // scene
    private GameObject dialogBoxGameObject;

    /// <summary>
    ///     Shows the dialog window for the dialog that is in this object's
    ///     dialogSystem property. 
    /// </summary>
    public void ShowDialog()
    {
        // Instantiate the dialog box prefab
        this.dialogBoxGameObject = Instantiate(this.DialogueBox);

        // I'd recommend putting a script on your "dialog box" prefab to
        // handle this stuff, so that this script doesn't need to get a
        // reference to each text element within the dialog prefab.  But
        // this is just a quick and dirty example for this answer
        this.dialogueNameofChar = GameObject.Find("Character Name").GetComponent<Text>();
        this.characterSays = GameObject.Find("Character Text").GetComponent<Text>();
        this.characterPortrait = GameObject.Find("Character Image").GetComponent<Image>();

        // If you have multiple response options, you'd wire them up here.
        // Again; I recommend putting this into a script on your dialog box
        GameObject.Find("Response Button 1").GetComponent<Button>().onClick.AddListener(ShowNextDialogSection);
        GameObject.Find("Response Button 2").GetComponent<Button>().onClick.AddListener(ShowNextDialogSection);

        ShowDialogSection(this.dialogSystem.dialogInfoSections.Peek());
    }

    /// <summary>
    ///     Puts a dialog into this object's dialogSystem property and
    ///     opens a dialog window that will show that dialog.
    /// </summary>
    public void EnqueueDialogue(Dialogue db)
    {
        foreach (Dialogue.Info info in db.dialogueInfoSection)
        {
            this.dialogSystem.dialogInfoSections.Enqueue(info);
        }
        ShowDialog();
    }

    /// <summary>
    ///     Removes the dialog section at the head of the dialog queue, 
    ///     and shows the following dialog statement to the player.  This
    ///     is a difference in the overall logic, because now the dialog
    ///     section at the head of the queue is the dialog that's currently
    ///     being show, rather than the previous one that was shown
    /// </summary>
    public void ShowNextDialogSection()
    {
        this.dialogSystem.dialogInfoSections.Dequeue();
        if (this.dialogSystem.dialogInfoSections.Count == 0)
        {
            ReachedEndOfDialogue();
            return;
        }

        Dialogue.Info dialogSection = this.dialogSystem.dialogInfoSections.Peek();
        ShowDialogSection(dialogSection);
    }

    /// <summary>
    ///     Shows the specified dialog statement to the player.
    /// </summary>
    public void ShowDialogSection(Dialogue.Info dialogSection)
    {
        dialogueNameofChar.text = dialogSection.myName;
        characterSays.text = dialogSection.mytext;
        characterPortrait.sprite = dialogSection.portrait;

        StartCoroutine(TypeText(dialogSection));
    }

    IEnumerator TypeText(Dialogue.Info info)
    {
        characterSays.text = "";
        foreach (char c in info.mytext.ToCharArray())
        {
            yield return new WaitForSeconds(textDelay);
            characterSays.text += c;
            yield return null;
        }
    }

    public void ReachedEndOfDialogue()
    {
        // Destroy the dialog box
        Destroy(this.dialogBoxGameObject);
    }

}    

DialogSystem.cs

using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = "Dialogues/Dialog System")]
public class DialogueSystem : ScriptableObject
{
    public Queue<Dialogue.Info> dialogInfoSections = new Queue<Dialogue.Info>();
}

Вот как выглядит мой префаб в диалоговом окне

enter image description here

Каждая сцена нуждается в объекте (предположительно, в префабе, чтобы его можно было легко добавить в каждую сцену), которыйимеет MainDialogActiviation и MainDialogManager на нем.Моя выглядит так:

enter image description here

...