不使用Player Prefs等在场景之间进行对话的最简单方法

时间:2019-03-21 01:16:40

标签: unity3d scene

我一直在为我的游戏开发一个对话系统,我想知道是否有人知道如何在不同场景之间保持该系统。我知道您可以使用Player Prefs之类的东西,但是对于其中的一项,我不了解,经研究,人们通常不建议使用它来存储大型复杂的东西。我设法像使用字符一样通过使用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;

}

系统的主要代码(切换场景时,glegleton在此处中断):

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 个答案:

答案 0 :(得分:1)

这样的事情怎么样?

这个想法与您正在做的事情非常相似,但有一些调整:

  • 我将活动对话框存储在可编写脚本的对象(DialogueSystem)中,以便它可以在场景之间持久存在。每次加载新场景时,都会检查是否有一个活动对话框,如果有的话,我会在Start()中显示对话框弹出窗口。
  • 从当前对话框中删除当前正在显示给播放器的对话框部分,而在播放器单击到下一部分之前,我不会删除当前部分。这是必要的,因为如果移至新场景,则可能需要重新显示同一部分。

确保创建DialogueSystem可脚本化对象的实例,并将其分配给MainDialogueActivationMainDialogManager

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

每个场景都需要一个上面带有MainDialogActiviationMainDialogManager的对象(大概是一个预制件,可以很容易地添加到每个场景中)。我的看起来像这样:

enter image description here

答案 1 :(得分:1)

这可能是一个不受欢迎的观点,但是使用Singleton可以。只是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并将其放入“ Resources”文件夹中。