> 技术文档 > 【Unity】对话与任务系统基础架构 (一)_unity任务系统

【Unity】对话与任务系统基础架构 (一)_unity任务系统


对话与任务系统基础架构 (一)

前言

最近在做一个剧情向的游戏项目,需要实现一套完整的对话和任务系统。经过一段时间的开发和迭代,总算是搭建出了一套相对完善的架构。想着分享出来,一方面是做个记录,另一方面也希望能给有类似需求的朋友一些参考。

这个系列预计会分3篇来写:

  • 第一篇(本篇):整体架构设计和核心思路
  • 第二篇:对话系统的具体实现细节
  • 第三篇:任务系统的具体实现细节

项目背景

我们的游戏是一个剧情向的RPG,对话和任务是核心玩法。在设计之初就明确了几个需求:

对话系统需求

  • 支持多角色对话,带头像、姓名等信息
  • 支持选择分支,不同选择有不同后续
  • 支持对话完成后触发各种事件(任务、跳转等)
  • 策划能够方便地配置对话内容
  • 支持跳过、快进等用户交互

任务系统需求

  • 支持多种任务类型(对话、收集、交互等)
  • 任务之间有前置关系,支持任务链
  • 任务进度要能保存和加载
  • UI要能实时显示任务状态
  • 方便后续扩展新的任务类型

整体架构思路

经过一番思考,我们采用了分层架构 + 事件驱动的设计模式。为什么这么选择呢?

分层架构的好处

  1. 职责清晰:每一层只管自己的事情,数据层管数据,UI层管显示
  2. 易于维护:修改某一层的逻辑不会影响其他层
  3. 便于测试:可以单独测试每一层的功能

事件驱动的好处

  1. 解耦合:系统之间通过事件通信,不直接依赖
  2. 易扩展:新增功能只需要监听相应事件即可
  3. 灵活性:可以动态添加或移除事件监听器

系统架构图

先上个整体的架构图,让大家有个直观的认识:

┌─────────────────┐ ┌─────────────────┐│ UI Layer │ │ Event System ││ (对话UI/任务UI) │◄──►│ (UniFramework) ││  │ │  │└─────────────────┘ └─────────────────┘ ▲ ▲ │ │┌─────────────────┐ ┌─────────────────┐│ Manager Layer │ │ Logic Layer ││ (对话管理器 │◄──►│ (任务收集器 ││ 任务管理器) │ │ 事件处理器) │└─────────────────┘ └─────────────────┘ ▲ ▲ │ │┌─────────────────┐ ┌─────────────────┐│ Data Layer │ │ Storage Layer ││ (配置表数据 │◄──►│ (存档系统 ││ 运行时数据) │ │ 数据持久化) │└─────────────────┘ └─────────────────┘

各层职责说明

UI Layer(UI层)

  • 负责界面显示和用户交互
  • 接收用户输入,显示系统状态
  • 通过事件与下层通信

Manager Layer(管理层)

  • 系统的核心控制逻辑
  • 协调各个子系统的工作
  • 处理业务流程

Logic Layer(逻辑层)

  • 具体的业务逻辑实现
  • 各种策略和算法
  • 事件的具体处理

Data Layer(数据层)

  • 配置表数据管理
  • 运行时数据结构
  • 数据的增删改查

Storage Layer(存储层)

  • 数据持久化
  • 存档加载
  • 数据序列化

核心设计原则

在架构设计过程中,我们遵循了几个核心原则:

1. 单一职责原则

每个类只负责一件事情。比如:

  • DialogueManager 只管对话流程控制
  • TaskDataManager 只管任务数据
  • DialogueUI 只管对话界面显示

2. 开闭原则

对扩展开放,对修改封闭。比如:

  • 新增任务类型不需要修改现有代码
  • 新增对话事件类型通过配置即可

3. 依赖倒置原则

高层模块不依赖低层模块,都依赖抽象。比如:

// 管理器依赖接口,不依赖具体实现public class DialogueManager{ private IDialogueUI _dialogueUI; // 依赖抽象 // 而不是依赖具体的 TestDialogueView}

4. 接口隔离原则

接口要小而专,不要大而全。比如:

// 分离不同的接口public interface ITaskCollector { }public interface ITaskEventListener { } public interface ITaskCollectorUpdate { }

数据驱动设计

我们的系统是完全数据驱动的,所有的内容都通过配置表来管理。

配置表结构设计

对话系统采用三级结构:

DialogueMain (章节) ├── DialogueGroup (对话组)│ ├── DialogueData (单条对话1)│ ├── DialogueData (单条对话2) │ └── DialogueOption[] (选择选项)└── DialogueGroup (对话组2)

任务系统也是三级结构:

TaskGroup (任务组)├── TaskData (任务1)│ ├── TaskCompleteCondition (完成条件)│ └── TaskCollector (收集器实例)└── TaskData (任务2)

数据驱动的好处

  1. 策划友好:策划可以直接修改配置表,不需要程序员参与
  2. 热更新支持:配置表可以热更新,不需要重新打包
  3. 版本控制:配置表变更可以通过版本控制系统管理
  4. 本地化支持:多语言版本只需要替换配置表

事件系统设计

我们使用了UniFramework的事件系统作为基础,设计了一套完整的事件驱动架构。

事件分类

// 对话相关事件public class StartDialogueChapter : IEventMessage{ public int ChapterId { get; set; } public int GroupId { get; set; }}// 任务相关事件 public class TaskDialogueCollectorEvent : IEventMessage{ public int DialogueGroupId { get; set; }}

事件流转示例

用户点击NPC → 发送StartDialogue事件 → DialogueManager接收 → 显示对话UI → 用户选择选项 → 发送TaskEvent事件 → TaskManager接收 → 更新任务进度 → 发送UI更新事件 → UI刷新

扩展性设计

工厂模式支持扩展

// 任务收集器工厂public static class TaskCollectorFactory{ public static TaskCollectorBase CreateTaskCollector(TaskData taskData) { return taskData.taskType switch { TaskType.Dialogue => new TaskDialogueCollector(taskData), TaskType.Collection => new TaskCollectionCollector(taskData), TaskType.Interaction => new TaskInteractionCollector(taskData), _ => null }; }}

策略模式支持多样化

// 不同类型的任务有不同的处理策略public abstract class TaskCollectorBase{ public abstract void OnTaskEventReceived(IEventMessage eventMessage); public abstract bool CheckTaskComplete();}

开发过程中的坑

分享几个开发过程中踩过的坑:

1. 事件监听器的生命周期管理

问题:事件监听器没有正确释放,导致内存泄漏和重复触发。

解决方案:使用EventGroup统一管理事件监听器的生命周期。

public class TaskCollectorBase{ protected EventGroup eventGroup; public virtual void Init() { eventGroup = new EventGroup(); eventGroup.AddListener<TaskDialogueCollectorEvent>(OnTaskEventReceived); } public virtual void Release() { eventGroup?.RemoveAllListener(); }}

2. 配置表数据的循环引用

问题:任务A的完成条件依赖任务B,任务B的完成条件又依赖任务A,形成死锁。

解决方案:在数据加载时进行循环依赖检测,并提供清晰的错误提示。

3. UI状态同步问题

问题:任务状态更新了,但UI没有及时刷新。

解决方案:建立完善的UI事件通知机制,确保数据变更时UI能及时响应。

性能优化考虑

1. 事件过滤

不是所有收集器都需要监听所有事件,要做好事件过滤:

// 只监听相关的事件类型if (eventMessage is TaskDialogueCollectorEvent dialogueEvent){ // 处理对话事件}

2. 数据懒加载

配置表数据按需加载,避免一次性加载所有数据:

public TaskData GetTaskData(int taskId){ if (!_taskDataCache.ContainsKey(taskId)) { _taskDataCache[taskId] = LoadTaskDataFromTable(taskId); } return _taskDataCache[taskId];}

3. UI更新优化

避免频繁的UI更新,使用脏标记机制:

private bool _isDirty = false;public void MarkDirty(){ _isDirty = true;}private void Update(){ if (_isDirty) { RefreshUI(); _isDirty = false; }}

小结

本篇主要介绍了对话与任务系统的整体架构设计思路。核心要点总结如下:

  1. 分层架构:清晰的职责分离,便于维护和扩展
  2. 事件驱动:系统间松耦合,提高灵活性
  3. 数据驱动:配置表管理内容,策划友好
  4. 扩展性设计:工厂模式和策略模式支持功能扩展
  5. 性能优化:合理的缓存和更新策略

下一篇文章将详细介绍对话系统的具体实现,包括UI状态机、配置表设计、选择分支处理等细节。如果大家有什么问题或建议,欢迎在评论区讨论!


相关资源:

  • Unity版本:2022.3 LTS
  • 事件系统:UniFramework
  • 异步框架:ET Framework
  • 配置表:Excel + 代码生成

下期预告:
《对话与任务系统基础架构 (二) - 对话系统实现详解》将深入介绍对话系统的UI状态机、配置表解析、选择分支逻辑等核心实现。