现在我们的文本仍然是单句显示的,这还不是我们想要的效果,本期让我们完善对话流程,并使用SO(ScriptableObject)来进行一段对话序列的配置和动态加载。
最终效果功能展示
功能一:场景内可根据资源序号ID来动态加载对话数据
功能二:按空格进行下一句,如果当前对话未打印完毕,按下空格则快进直接显示全部文本。
功能三:支持对话中可委托触发
这里碍于篇幅我会分析几个类中的部分关键点,关键的功能源码在后文。
一.功能分析
1.DialogueDataSequence
对话数据SO类
职能
创建SO文件,易于配置对话数据。
注意类前需要使用特性[CreateAssetMenu()]
示例代码
[CreateAssetMenu(menuName = "编辑器界面的创建目录", fileName = "创建出的文件的名称")]
using System;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "Data/DialogueDataLine", fileName = "DialogueDataLine")]
public class DialogueDataSequence : ScriptableObject
{
[Header("对话线ID")]
public int ID;
[Header("自动下一句")]
public bool canAutonNext;
[Header("可以快进")]
public bool canQuickShow;
[Header("打字机效果")]
public bool needTyping;
[Header("当前话语序号")]
public int currentIndex;
[Header("淡入时间")]
public float fadeDuration;
[Header("对话线")]
public List<DialogueData> dialogueLine = new List<DialogueData>();
}
[Serializable]
public class DialogueData
{
[Header("讲述者")]
public string speaker;
[Multiline]
[Header("内容文本")]
public string content;
}
2.DialoguePanel
对话框面板
对话框面板无需考虑当前是哪个对话序列,只接收显示的必要信息。
职能
显示一句对话文本信息
关键字段
[Header("高级文本")]
public AdvancedText displayText;
[Header("文本讲述者")]
public TMP_Text speakerNameText;
//可以快速显示
bool _canQuickShow;
//自动显示下一句
bool _canAutonNext;
关键函数
/// <summary>
/// 显示对话框
/// </summary>
/// <param name="speaker">讲述者</param>
/// <param name="content">内容</param>
/// <param name="needTyping">使用打字机效果</param>
/// <param name="fadeDuration">打印间隔</param>
/// <param name="canQickShow">快速显示</param>
/// <param name="canAutonNext">自动下一句</param>
public void ShowDialogue(string speaker, string content, bool needTyping = true, float fadeDuration = 0.2f, bool canQickShow = true, bool canAutonNext = false)
{
speakerNameText.text = speaker;
speakerNameText.GetComponent<Animator>().SetTrigger("Show");
if (displayText.text != "")
{
displayText.TextDisAppear();
}
_canQuickShow = canQickShow;
_canAutonNext = canAutonNext;
if (needTyping)
StartCoroutine(displayText.ShowText(content,E_DisplayType.Typing,fadeDuration));
else
StartCoroutine(displayText.ShowText(content,E_DisplayType.Fading,fadeDuration));
}
3.DialogueManager
对话管理器
职能
1.持有对当前对话序列SO文件的引用。通过外部传入的序列号加载目标对话序列。
2.开启和关闭对话框面板。
3.操作当前的对话序列(如添加对话委托)
关键字段
[Header("当前进行的对话序列")]
public DialogueDataSequence currentDialogueSq;
DialoguePanel D_Panel;//对话面板
功能函数
1.BeginDialogueSequence()
暴露开始对话及开始下一句对话接口,若下一句已经是最后一句,则关闭对话框。
/// <summary>
/// 根据对话线的ID开启一段对话
/// </summary>
/// <param name="ID">对话线序号</param>
/// <param name="action">对话委托</param>
public void BeginDialogueSequence(int ID, Action action = null)
{
//加载目标对话序列,获得其引用
currentDialogueSq = Resources.Load<DialogueDataSequence>("StaticData/DialogueLine/" + ID);
currentDialogueSq.currentIndex = 0;
action?.Invoke();//接收对话委托
UIManager.Instance.ShowPanel<DialoguePanel>(panel =>
{
//开启对话框,从对话序列的开始显示对话文本内容
panel.ShowDialogue(currentDialogueSq.dialogueLine[currentDialogueSq.currentIndex].speaker,
currentDialogueSq.dialogueLine[currentDialogueSq.currentIndex].content,
currentDialogueSq.needTyping, currentDialogueSq.fadeDuration,
currentDialogueSq.canQuickShow, currentDialogueSq.canAutonNext);
D_Panel = panel;
});
Debug.Log(currentDialogueSq.currentIndex);
ActionCheck();
}
/// <summary>
/// 每结束一句对话时检查是否有委托需要执行
/// </summary>
void ActionCheck()
{
for (int i = 0; i < currentDialogueSq.eventList.Count; i++)
{
if (currentDialogueSq.eventList[i].eventIndex == currentDialogueSq.currentIndex)
{
currentDialogueSq.eventList[i].MyEvent?.Invoke();
Debug.Log("检测到对话事件触发!");
}
}
}
2.NextDialogue()
当前对话序列内索引自增,显示下一句对话
/// <summary>
/// 用于隐藏当前文本并显示下一句文本,用于外部调用(如面板或按钮)
/// </summary>
public IEnumerator NextDialogue()
{
if (currentDialogueSq.currentIndex + 1 < currentDialogueSq.dialogueLine.Count)
{
currentDialogueSq.currentIndex++;
D_Panel.ShowDialogue(currentDialogueSq.dialogueLine[currentDialogueSq.currentIndex].speaker,
currentDialogueSq.dialogueLine[currentDialogueSq.currentIndex].content,
currentDialogueSq.needTyping, currentDialogueSq.fadeDuration,
currentDialogueSq.canQuickShow, currentDialogueSq.canAutonNext);
Debug.Log(currentDialogueSq.currentIndex);
ActionCheck();
}
else
{
EndDialogueSquence();//结束对话
}
yield return null;
}
/// <summary>
/// 结束一段对话序列
/// </summary>
void EndDialogueSquence()
{
currentDialogueSq?.eventList.Clear();
currentDialogueSq = null;
UIManager.Instance.HidePanel<DialoguePanel>();
}
//退出游戏重置对话状态
private void OnApplicationQuit()
{
currentDialogueSq?.eventList.Clear();
currentDialogueSq = null;
}
由于我个人框架的实现与UI部分联系密切的问题,考虑给出完整源码文章会过于冗余且侧重点失衡,上面分享的是我实现过程中的关键功能片段,需要朋友们结合实际应用适配到自己的项目中,原理的大致通用的,当然上述代码某些地方肯定是能够进一步优化拓展的,也欢迎大佬不吝赐教。
二.实验结果测试
1.测试脚本
using UnityEngine;
public class TMPTest : MonoBehaviour
{
[Header("对话序号")]
public int dialogueID;
private void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
DialogueManager.Instance.BeginDialogueSequence(dialogueID, () =>
{
DialogueManager.Instance.AddDialogueEvent(1, 2, T1);
DialogueManager.Instance.AddDialogueEvent(1, 4, T2);
});
}
}
void T1()
{
Debug.Log("对话内事件1触发");
}
void T2()
{
Debug.Log("对话内事件2触发");
}
}
2.数据配置
这里我挂载了测试脚本,并配置了一段对话数据,序号为1
三.实验结果展示
打印效果:
对话中事件触发效果:
三.后文
由于我个人框架的实现与UI部分联系密切的问题,考虑给出完整源码文章会过于冗余且侧重点失衡,上面分享的是我实现过程中的关键功能片段,需要朋友们结合实际应用适配到自己的项目中,原理的大致通用的,当然上述代码某些地方肯定是能够进一步优化拓展的,也欢迎大佬不吝赐教。
本篇完!