对话与任务系统基础架构 (二) - 对话系统实现详解
前言
上一篇文章介绍了整体架构设计,这次我们深入对话系统的具体实现。经过一段时间的开发和优化,我们的对话系统已经能够支持复杂的多分支对话、角色切换、事件触发等功能。
本篇将从以下几个方面详细介绍:
- 配置表设计与数据结构
- UI状态机的实现
- 对话流程控制逻辑
- 选择分支处理机制
- 事件触发与系统集成
配置表设计详解
数据结构层次
我们采用了三层数据结构来组织对话内容:
DialogueMain (对话章节)
├── DialogueGroup (对话组1)
│ ├── DialogueData (对话1)
│ ├── DialogueData (对话2)
│ └── DialogueOption[] (选择选项)
├── DialogueGroup (对话组2)
└── ...
核心配置表结构
1. DialogueMain - 对话章节表
[Serializable]
public class DialogueMain
{
public int id; // 章节ID
public string name; // 章节名称
public int firstGroupId; // 首个对话组ID
public List<DialogueGroup> dialogueGroups; // 对话组列表
}
2. DialogueGroup - 对话组表
[Serializable]
public class DialogueGroup
{
public int id; // 对话组ID
public int chapterId; // 所属章节ID
public List<DialogueData> dialogueDatas; // 对话数据列表
public List<DialogueOption> dialogueOptions; // 选择选项列表
public int nextGroupId; // 下一个对话组ID
public string triggerEvent; // 触发事件配置
}
3. DialogueData - 单条对话表
[Serializable]
public class DialogueData
{
public int id; // 对话ID
public int groupId; // 所属对话组ID
public string characterName; // 角色名称
public string characterIcon; // 角色头像路径
public string content; // 对话内容
public float showTime; // 显示时长
public DialoguePosition position; // 对话位置(左/右/中)
}
4. DialogueOption - 选择选项表
[Serializable]
public class DialogueOption
{
public int id; // 选项ID
public int groupId; // 所属对话组ID
public string content; // 选项文本
public int targetGroupId; // 目标对话组ID
public string triggerEvent; // 触发事件
public bool isVisible; // 是否可见
public string condition; // 显示条件
}
配置表示例
来看一个实际的配置例子:
DialogueMain表:
| id | name | firstGroupId |
|---|---|---|
| 1001 | 新手村对话 | 2001 |
DialogueGroup表:
| id | chapterId | nextGroupId | triggerEvent |
|---|---|---|---|
| 2001 | 1001 | 2002 | “1,StartTask_101” |
| 2002 | 1001 | -1 | “2,GiveItem_sword” |
DialogueData表:
| id | groupId | characterName | content | position |
|---|---|---|---|---|
| 3001 | 2001 | 村长 | “欢迎来到新手村!” | Left |
| 3002 | 2001 | 玩家 | “您好,我是新来的冒险者。” | Right |
DialogueOption表:
| id | groupId | content | targetGroupId | triggerEvent |
|---|---|---|---|---|
| 4001 | 2001 | “我想接受任务” | 2002 | “1,StartTask_101” |
| 4002 | 2001 | “我只是路过” | -1 | “” |
UI状态机实现
对话UI采用状态机模式管理不同的显示状态,这样可以清晰地控制UI的各种交互行为。
UI状态定义
public enum DialogueUIState
{
None, // 无状态
Showing, // 正在显示对话
WaitingInput, // 等待用户输入
ShowingOptions, // 显示选择选项
Playing, // 自动播放中
Finished // 对话结束
}
状态机核心实现
public class DialogueUIStateMachine
{
private DialogueUIState _currentState;
private DialogueUIState _previousState;
private Dictionary<DialogueUIState, IDialogueUIState> _states;
public void Init()
{
_states = new Dictionary<DialogueUIState, IDialogueUIState>
{
{ DialogueUIState.Showing, new DialogueShowingState() },
{ DialogueUIState.WaitingInput, new DialogueWaitingState() },
{ DialogueUIState.ShowingOptions, new DialogueOptionsState() },
{ DialogueUIState.Playing, new DialoguePlayingState() },
{ DialogueUIState.Finished, new DialogueFinishedState() }
};
}
public void ChangeState(DialogueUIState newState)
{
if (_currentState == newState) return;
// 退出当前状态
_states[_currentState]?.OnExit();
_previousState = _currentState;
_currentState = newState;
// 进入新状态
_states[_currentState]?.OnEnter();
Debug.Log($"对话状态切换: {_previousState} -> {_currentState}");
}
public void Update()
{
_states[_currentState]?.OnUpdate();
}
}
具体状态实现
显示对话状态
public class DialogueShowingState : IDialogueUIState
{
private float _typewriterSpeed = 0.05f;
private Coroutine _typewriterCoroutine;
public void OnEnter()
{
// 开始打字机效果
_typewriterCoroutine = DialogueUI.Instance.StartCoroutine(TypewriterEffect());
}
public void OnUpdate()
{
// 检测用户点击跳过打字机效果
if (Input.GetMouseButtonDown(0))
{
SkipTypewriter();
}
}
public void OnExit()
{
if (_typewriterCoroutine != null)
{
DialogueUI.Instance.StopCoroutine(_typewriterCoroutine);
}
}
private IEnumerator TypewriterEffect()
{
var dialogueText = DialogueUI.Instance.DialogueText;
var fullText = DialogueUI.Instance.CurrentDialogue.content;
dialogueText.text = "";
for (int i = 0; i <= fullText.Length; i++)
{
dialogueText.text = fullText.Substring(0, i);
yield return new WaitForSeconds(_typewriterSpeed);
}
// 打字机效果完成,切换到等待输入状态
DialogueUI.Instance.StateMachine.ChangeState(DialogueUIState.WaitingInput);
}
}
等待输入状态
public class DialogueWaitingState : IDialogueUIState
{
public void OnEnter()
{
// 显示继续提示
DialogueUI.Instance.ShowContinueHint(true);
}
public void OnUpdate()
{
if (Input.GetMouseButtonDown(0) || Input.GetKeyDown(KeyCode.Space))
{
DialogueManager.Instance.NextDialogue();
}
}
public void OnExit()
{
DialogueUI.Instance.ShowContinueHint(false);
}
}
选择选项状态
public class DialogueOptionsState : IDialogueUIState
{
public void OnEnter()
{
DialogueUI.Instance.ShowOptions(DialogueManager.Instance.CurrentOptions);
}
public void OnUpdate()
{
// 处理选项选择
HandleOptionSelection();
}
private void HandleOptionSelection()
{
for (int i = 0; i < DialogueManager.Instance.CurrentOptions.Count; i++)
{
if (Input.GetKeyDown(KeyCode.Alpha1 + i))
{
DialogueManager.Instance.SelectOption(i);
break;
}
}
}
}
对话管理器核心逻辑
DialogueManager主要职责
public class DialogueManager : MonoSingleton<DialogueManager>
{
private DialogueDataManager _dataManager;
private IDialogueUI _dialogueUI;
// 当前对话状态
public DialogueMain CurrentChapter { get; private set; }
public DialogueGroup CurrentGroup { get; private set; }
public DialogueData CurrentDialogue { get; private set; }
public List<DialogueOption> CurrentOptions { get; private set; }
private int _currentDialogueIndex = 0;
public async ETTask Init()
{
_dataManager = new DialogueDataManager();
await _dataManager.Init();
_dialogueUI = FindObjectOfType<TestDialogueView>();
RegisterEvents();
}
}
对话流程控制
开始对话章节
public void StartDialogueChapter(int chapterId, int groupId = -1)
{
CurrentChapter = _dataManager.GetDialogueMain(chapterId);
if (CurrentChapter == null)
{
Debug.LogError($"找不到对话章节: {chapterId}");
return;
}
// 确定起始对话组
int startGroupId = groupId != -1 ? groupId : CurrentChapter.firstGroupId;
StartDialogueGroup(startGroupId);
}
private void StartDialogueGroup(int groupId)
{
CurrentGroup = _dataManager.GetDialogueGroup(groupId);
if (CurrentGroup == null)
{
Debug.LogError($"找不到对话组: {groupId}");
return;
}
_currentDialogueIndex = 0;
CurrentOptions = CurrentGroup.dialogueOptions;
// 显示UI并开始第一条对话
_dialogueUI.Show();
ShowCurrentDialogue();
}
对话推进逻辑
public void NextDialogue()
{
_currentDialogueIndex++;
// 检查是否还有更多对话
if (_currentDialogueIndex < CurrentGroup.dialogueDatas.Count)
{
ShowCurrentDialogue();
}
else
{
// 当前组对话结束,检查是否有选择选项
if (CurrentOptions != null && CurrentOptions.Count > 0)
{
ShowOptions();
}
else
{
// 没有选项,检查是否有下一个对话组
ProcessGroupEnd();
}
}
}
private void ShowCurrentDialogue()
{
CurrentDialogue = CurrentGroup.dialogueDatas[_currentDialogueIndex];
_dialogueUI.ShowDialogue(CurrentDialogue);
_dialogueUI.StateMachine.ChangeState(DialogueUIState.Showing);
}
对话组结束处理
private void ProcessGroupEnd()
{
// 触发对话组完成事件
TriggerGroupEvent(CurrentGroup.triggerEvent);
// 检查是否有下一个对话组
if (CurrentGroup.nextGroupId > 0)
{
StartDialogueGroup(CurrentGroup.nextGroupId);
}
else
{
// 整个对话章节结束
EndDialogueChapter();
}
}
private void EndDialogueChapter()
{
_dialogueUI.Hide();
_dialogueUI.StateMachine.ChangeState(DialogueUIState.Finished);
// 发送对话章节完成事件
UniEvent.SendMessage(new DialogueChapterCompleted
{
ChapterId = CurrentChapter.id
});
}
选择分支处理机制
选项显示与过滤
private void ShowOptions()
{
// 过滤可见选项
var visibleOptions = CurrentOptions.Where(IsOptionVisible).ToList();
if (visibleOptions.Count == 0)
{
// 没有可见选项,直接结束
ProcessGroupEnd();
return;
}
_dialogueUI.ShowOptions(visibleOptions);
_dialogueUI.StateMachine.ChangeState(DialogueUIState.ShowingOptions);
}
private bool IsOptionVisible(DialogueOption option)
{
if (!option.isVisible) return false;
// 检查显示条件
if (!string.IsNullOrEmpty(option.condition))
{
return EvaluateCondition(option.condition);
}
return true;
}
条件评估系统
private bool EvaluateCondition(string condition)
{
// 简单的条件评估系统
// 格式: "TaskCompleted:101" 或 "ItemCount:sword>=5"
var parts = condition.Split(':');
if (parts.Length != 2) return true;
string conditionType = parts[0];
string conditionValue = parts[1];
return conditionType switch
{
"TaskCompleted" => IsTaskCompleted(int.Parse(conditionValue)),
"ItemCount" => EvaluateItemCount(conditionValue),
"PlayerLevel" => EvaluatePlayerLevel(conditionValue),
_ => true
};
}
private bool IsTaskCompleted(int taskId)
{
// 检查任务是否完成
return TaskManager.Instance.IsTaskCompleted(taskId);
}
选项选择处理
public void SelectOption(int optionIndex)
{
if (optionIndex < 0 || optionIndex >= CurrentOptions.Count)
{
Debug.LogError($"无效的选项索引: {optionIndex}");
return;
}
var selectedOption = CurrentOptions[optionIndex];
// 触发选项事件
if (!string.IsNullOrEmpty(selectedOption.triggerEvent))
{
TriggerOptionEvent(selectedOption.triggerEvent);
}
// 跳转到目标对话组
if (selectedOption.targetGroupId > 0)
{
StartDialogueGroup(selectedOption.targetGroupId);
}
else
{
// 没有目标组,结束对话
EndDialogueChapter();
}
}
事件触发与系统集成
事件配置格式
我们设计了一套灵活的事件配置格式:
"事件类型,事件数据"
常见的事件类型:
1,StartTask_101- 启动任务1012,GiveItem_sword_001- 给予物品3,ChangeScene_town- 切换场景4,PlaySound_victory- 播放音效
事件解析与触发
private void TriggerGroupEvent(string eventConfig)
{
if (string.IsNullOrEmpty(eventConfig)) return;
var events = eventConfig.Split('|'); // 支持多个事件用|分隔
foreach (string eventStr in events)
{
ProcessSingleEvent(eventStr);
}
}
private void ProcessSingleEvent(string eventStr)
{
var parts = eventStr.Split(',');
if (parts.Length != 2)
{
Debug.LogError($"事件配置格式错误: {eventStr}");
return;
}
int eventType = int.Parse(parts[0]);
string eventData = parts[1];
switch (eventType)
{
case 1: // 启动任务
TriggerStartTask(eventData);
break;
case 2: // 给予物品
TriggerGiveItem(eventData);
break;
case 3: // 切换场景
TriggerChangeScene(eventData);
break;
default:
Debug.LogWarning($"未知的事件类型: {eventType}");
break;
}
}
具体事件处理
private void TriggerStartTask(string taskData)
{
// 解析任务数据: "StartTask_101" -> taskId = 101
var taskId = int.Parse(taskData.Split('_')[1]);
// 发送启动任务事件
UniEvent.SendMessage(new TaskStartEvent { TaskId = taskId });
}
private void TriggerGiveItem(string itemData)
{
// 解析物品数据: "GiveItem_sword_001_5" -> 物品ID, 数量
var parts = itemData.Split('_');
string itemId = parts[1];
int count = parts.Length > 2 ? int.Parse(parts[2]) : 1;
// 发送给予物品事件
UniEvent.SendMessage(new GiveItemEvent
{
ItemId = itemId,
Count = count
});
}
UI实现细节
对话UI组件结构
public class TestDialogueView : MonoBehaviour, IDialogueUI
{
[Header("UI组件")]
public GameObject dialoguePanel;
public Text characterNameText;
public Image characterIcon;
public Text dialogueText;
public GameObject continueHint;
public Transform optionsParent;
public GameObject optionButtonPrefab;
[Header("动画组件")]
public Animator panelAnimator;
public CanvasGroup canvasGroup;
public DialogueUIStateMachine StateMachine { get; private set; }
private void Awake()
{
StateMachine = new DialogueUIStateMachine();
StateMachine.Init();
}
}
对话显示实现
public void ShowDialogue(DialogueData dialogueData)
{
CurrentDialogue = dialogueData;
// 设置角色信息
characterNameText.text = dialogueData.characterName;
LoadCharacterIcon(dialogueData.characterIcon);
// 设置对话位置
SetDialoguePosition(dialogueData.position);
// 准备显示文本(状态机会处理打字机效果)
dialogueText.text = "";
}
private void LoadCharacterIcon(string iconPath)
{
if (string.IsNullOrEmpty(iconPath))
{
characterIcon.gameObject.SetActive(false);
return;
}
// 异步加载头像
var sprite = Resources.Load<Sprite>(iconPath);
if (sprite != null)
{
characterIcon.sprite = sprite;
characterIcon.gameObject.SetActive(true);
}
}
private void SetDialoguePosition(DialoguePosition position)
{
var rectTransform = dialoguePanel.GetComponent<RectTransform>();
switch (position)
{
case DialoguePosition.Left:
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(0.6f, 0.3f);
break;
case DialoguePosition.Right:
rectTransform.anchorMin = new Vector2(0.4f, 0);
rectTransform.anchorMax = new Vector2(1, 0.3f);
break;
case DialoguePosition.Center:
rectTransform.anchorMin = new Vector2(0.1f, 0);
rectTransform.anchorMax = new Vector2(0.9f, 0.3f);
break;
}
}
选项UI实现
public void ShowOptions(List<DialogueOption> options)
{
// 清除现有选项
ClearOptions();
// 创建新选项按钮
for (int i = 0; i < options.Count; i++)
{
CreateOptionButton(options[i], i);
}
// 显示选项面板
optionsParent.gameObject.SetActive(true);
}
private void CreateOptionButton(DialogueOption option, int index)
{
var buttonObj = Instantiate(optionButtonPrefab, optionsParent);
var button = buttonObj.GetComponent<Button>();
var text = buttonObj.GetComponentInChildren<Text>();
text.text = $"{index + 1}. {option.content}";
// 添加点击事件
button.onClick.AddListener(() => {
DialogueManager.Instance.SelectOption(index);
});
// 添加快捷键提示
var shortcutText = buttonObj.transform.Find("ShortcutText")?.GetComponent<Text>();
if (shortcutText != null)
{
shortcutText.text = $"[{index + 1}]";
}
}
性能优化与最佳实践
1. 对象池优化
public class DialogueUIPool : MonoSingleton<DialogueUIPool>
{
private Queue<GameObject> _optionButtonPool = new Queue<GameObject>();
public GameObject GetOptionButton()
{
if (_optionButtonPool.Count > 0)
{
return _optionButtonPool.Dequeue();
}
return Instantiate(optionButtonPrefab);
}
public void ReturnOptionButton(GameObject button)
{
button.SetActive(false);
_optionButtonPool.Enqueue(button);
}
}
2. 资源异步加载
private async ETTask LoadCharacterIconAsync(string iconPath)
{
var request = Resources.LoadAsync<Sprite>(iconPath);
await request;
if (request.asset != null)
{
characterIcon.sprite = request.asset as Sprite;
characterIcon.gameObject.SetActive(true);
}
}
3. 内存管理
public void OnDialogueEnd()
{
// 清理引用
CurrentDialogue = null;
CurrentOptions = null;
// 停止所有协程
StopAllCoroutines();
// 清理UI对象池
DialogueUIPool.Instance.ClearPool();
}
调试与测试工具
对话编辑器工具
#if UNITY_EDITOR
[CustomEditor(typeof(DialogueManager))]
public class DialogueManagerEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
var manager = target as DialogueManager;
GUILayout.Space(10);
GUILayout.Label("测试工具", EditorStyles.boldLabel);
if (GUILayout.Button("测试对话章节1001"))
{
manager.StartDialogueChapter(1001);
}
if (GUILayout.Button("跳过当前对话"))
{
manager.NextDialogue();
}
}
}
#endif
运行时调试信息
private void OnGUI()
{
if (!Application.isPlaying || !showDebugInfo) return;
GUILayout.BeginArea(new Rect(10, 10, 300, 200));
GUILayout.Label($"当前章节: {CurrentChapter?.id}");
GUILayout.Label($"当前对话组: {CurrentGroup?.id}");
GUILayout.Label($"对话索引: {_currentDialogueIndex}");
GUILayout.Label($"UI状态: {_dialogueUI.StateMachine.CurrentState}");
GUILayout.EndArea();
}
小结
本篇详细介绍了对话系统的核心实现,主要包括:
- 配置表设计:三层数据结构,支持复杂对话流程
- UI状态机:清晰的状态管理,支持各种交互
- 流程控制:完整的对话推进和分支处理逻辑
- 事件系统:灵活的事件配置和触发机制
- 性能优化:对象池、异步加载等优化手段
下一篇将介绍任务系统的具体实现,包括任务收集器、进度追踪、数据持久化等核心功能。
本篇涉及的关键技术点:
- 状态机模式在UI中的应用
- 协程实现打字机效果
- 条件表达式解析
- 事件驱动的系统集成
- 配置表驱动的内容管理
下期预告:
《对话与任务系统基础架构 (三) - 任务系统实现详解》将深入介绍任务系统的收集器机制、进度追踪、数据持久化等核心实现。
1万+

被折叠的 条评论
为什么被折叠?



