教程来自M_Studio 迷失岛2游戏框架开发
整体思路
实现对话的逻辑其实非常简单,我们还是可以使用MVC的模式进行设计。其实在设计背包时,我们就已经使用了这种设计,简单应用来说,就是将数据模型、逻辑控制、UI表现分开,将需要使用和传递的数据在数据模型中单独配置,然后通过逻辑控制,将数据模型与UI表现“连通”,使得数据的更新能够显示在UI上。
- 对于数据模型
DialougeDate_SO
,我们只需要简单地使用一个List
将需要使用到的对话存下来即可 - 对于逻辑控制
DialogueController
,主要实现两个功能:第一,从数据模型DialogueData_SO
中获取具体的对话数据并暂存;第二,对外暴露展示对话内容的方法,让相应角色触发对话时能够调用相应的触发对话的方法;第三,将暂存的对话数据转发给UI进行显示。逻辑控制和UI更新的通信毫无疑问通过事件来实现,从暂存区取出对话台词,并通知UI做对应更新,角色触发互动进行对话与逻辑控制中的对话实现也可以通过事件通信,但是这里直接让角色脚本获取DialogueController
脚本简化操作(因为该游戏中所有可互动角色都有对话,这样设计也合理) - 对于UI表现,自然是订阅一个事件,当对话台词更新时,接受来自
DialoggueController
的文本,并做相应显示。同时,对于视频中出现的切换场景时对话框不会消失重置的问题,可以直接让对话UI也订阅此前实现的AfterSceneLoadedEvent
事件,每次新场景加载后时直接取消激活对话框,这里采用了这种简单粗暴的方法()
最后,不要忘记为角色也添加一个脚本,继承Interactive
基类,实现具体的触发互动的方法
代码实现
EventHandler
补全
//更新对话UI的事件
public static event Action<string> UpdateDialogueEvent;
/// <summary>
/// 呼叫更新对话UI的事件
/// </summary>
/// <param name="dialogue"></param>
public static void CallUpdateDialogueEvent(string dialogue)
{
UpdateDialogueEvent?.Invoke(dialogue);
}
DialogueDate_SO
实现
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "DialogueData_SO", menuName = "Dialogue/DialogueData_SO")]
public class DialogueData_SO : ScriptableObject
{
public List<string> dialogueDateList = new List<string>();
}
DialogueController
实现
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DialogueController : MonoBehaviour
{
public DialogueData_SO dialogueEmpty;
public DialogueData_SO dialogueFinish;
private Stack<string> _dialogueEmptyStack;
private Stack<string> _dialogueFinishStack;
//是否正在对话
private bool _isTalking;
private void Awake()
{
FillDialogueStack();
}
/// <summary>
/// 填充对话栈的内容
/// </summary>
private void FillDialogueStack()
{
_dialogueEmptyStack = new Stack<string>();
_dialogueFinishStack = new Stack<string>();
for (int i = dialogueEmpty.dialogueDateList.Count - 1; i >= 0; i--)
{
_dialogueEmptyStack.Push(dialogueEmpty.dialogueDateList[i]);
}
for (int i = dialogueFinish.dialogueDateList.Count - 1; i >= 0; i--)
{
_dialogueFinishStack.Push(dialogueFinish.dialogueDateList[i]);
}
}
public void ShowDialogueEmpty()
{
if (!_isTalking)
{
StartCoroutine(DialogueCoroutine(_dialogueEmptyStack));
}
}
public void ShowDialogueFinish()
{
if (!_isTalking)
{
StartCoroutine(DialogueCoroutine(_dialogueFinishStack));
}
}
/// <summary>
/// 从对话栈中取出对话内容
/// </summary>
/// <param name="dialogueStack"></param>
/// <returns></returns>
private IEnumerator DialogueCoroutine(Stack<string> dialogueStack)
{
//开始对话
_isTalking = true;
//当前对话栈不空时,取出栈顶的对话
if (dialogueStack.TryPop(out var result))
{
//呼叫对话UI更新
EventHandler.CallUpdateDialogueEvent(result);
yield return null;
//对话结束
_isTalking = false;
}
else
{
//当对话栈为空,重新填充对话栈
FillDialogueStack();
EventHandler.CallUpdateDialogueEvent(string.Empty);
//对话结束
_isTalking = false;
}
}
}
DialogueUI
实现
这里还设计了一个打字机的效果,实现思路也很简单,使用协程,将需要展示的对话文本逐个字符取出并一一展示在对话框中
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class DialogueUI : MonoBehaviour
{
public GameObject dialoguePanel;
public Text dialogueText;
private readonly float typingDelay = 0.05f;
private void OnEnable()
{
EventHandler.UpdateDialogueEvent += OnUpdateDialogueUIEvent;
EventHandler.AfterSceneLoadedEvent += OnAfterSceneLoadedEvent;
}
private void OnDisable()
{
EventHandler.UpdateDialogueEvent -= OnUpdateDialogueUIEvent;
EventHandler.AfterSceneLoadedEvent -= OnAfterSceneLoadedEvent;
}
/// <summary>
/// 更新对话框及对话文本
/// </summary>
/// <param name="text"></param>
private void OnUpdateDialogueUIEvent(string text)
{
if (text == string.Empty) //无文本展示时,关闭对话框
{
dialoguePanel.SetActive(false);
}
else //有文本展示时,打开对话框并设置对话文本
{
dialoguePanel.SetActive(true);
dialogueText.text = string.Empty;
StartCoroutine(DoTypingEffect(text));
}
}
/// <summary>
/// 实现文本的打字机效果
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
private IEnumerator DoTypingEffect(string text)
{
foreach (char letter in text)
{
dialogueText.text += letter;
yield return new WaitForSeconds(typingDelay);
}
}
private void OnAfterSceneLoadedEvent()
{
//只需要取消激活对话框即可,因为每次加载新场景都会重新填充对话栈(对话栈没有做持久化处理)
dialoguePanel.SetActive(false);
}
}
CharacterH2
实现
using UnityEngine;
[RequireComponent(typeof(DialogueController))]
public class CharacterH2 : Interactive
{
private DialogueController _dialogueController;
private void Awake()
{
_dialogueController = GetComponent<DialogueController>();
}
protected override void OnInteractiveAction()
{
//进行互动,播放互动完成的对话
_dialogueController.ShowDialogueFinish();
}
public override void ClickWithoutItem()
{
if (isDone) //当互动已经完成,则只展示互动完成的对话
{
_dialogueController.ShowDialogueFinish();
}
else //否则循环播放互动互动未完成的对话
{
_dialogueController.ShowDialogueEmpty();
}
}
}