系统说明
在一款复杂RPG游戏中,任务往往伴随着各种复杂的对话,这些对话会跟随着任务的推进给玩家展示不同的文本内容与分支选项。
对话系统需要给玩家展示的部分包括:
-
NPC文本内容
-
玩家选项以及选择对应选项会触发的事件
对话是和任务强相关的,对话可以做到发放以及接取任务,而任务可以做到修改对话内容以及分支结构。所以对话系统其实是任务系统的一个附属系统。在设计本系统的时候需要考虑到和任务系统的兼容性。
系统特性
-
本系统是一个后端架构,不需要过于关注前端的表现形式,只需要用高效的方式解决数据结构方面的内容即可。
-
本系统需要给框架使用者
留下足够的拓展和定制化功能开发空间
,在遇到需要用对话系统触发的需求,并且此需求在整个游戏流程中使用次数不多,不值得写入对话系统本体的时候,需要留有接口让框架使用者可以定制化开发。 -
本系统主要
运行在客户端
,不太需要过于关注反作弊的内容,如果有作弊者操作客户端上对话达到给玩家发放稀有任务。这些反作弊操作应该交给任务系统进行管理。
系统框架设计
数据结构设计
简单需求使用栈可以解决问题
我在开发过程中参考过Unity
游戏开发:对话系统的实现_unity对话系统-CSDN博客中对话实现的方式,发现是用弹栈,压栈的方式操作对话。此方法可以一定程度上有较好的表现,维护起来也比较方便。
但是这个方法存在一些问题:
-
如果单纯用栈来存储对话,当NPC身上存在多个任务相关的对话的时候一个栈无法解决此问题,只能使用多个栈来进行存储。
-
此方法无法解决NPC的对话,玩家存在多个选择,玩家的选择导向了不同的结果的需求。
-
对话的
改
部分相当不好维护,假设我想在对话中间插入一句话,则需要一个个弹栈,然后压栈。
综上所述,用栈来存储对话只能解决一些不复杂的系统需求,如果是复杂的环境,栈处理起来还是比较乏力的。
数组链表
抛弃栈存储之后,想到《底特律:变人》是以剧情对话为主的游戏,其中的剧情流程图其实就是给玩家打明牌。他们表现出来的结构就是一个横着的多叉树。
但我最终并没有选择使用多叉树来设计数据结构,而是选择使用数组中模拟链表的形式来解决问题。
多叉树和数组链表都可以处理对话回退的需求,但是说实话,做这个系统的时候我其实是存了锻炼自己数据结构的设计能力的心思在里面的,所以就没有选择相对好维护的多叉树来解决。
此数据结构分两层,外层是外部数组,内层是链表。
数据结构详解
外层数组
外层的数组决定了这个对话最长的长度。
用lua的Table模拟出来是以下结构
local DialogueArray=
{
[1]={
...},
[2]={
...},
[3]={
...},
}
我们暂且不去考虑内层的链表的数据结构,只考虑外层的数组的功能和初始化方式。
-
对话的长度决定了数组的长度(就是NPC说了几句话)
-
可能会出现一个对话有多个分支选项,其中一个分支选项NPC说了10句话,另一个分支选项NPC只说了5句话。此时数组的长度是10
-
这个外层数组的最大作用是在出现回退对话选项的时候可以快速找到上一个选项有哪几个链表节点可供玩家选择
内层链表
-
数组元素内存储的是一个个链表的节点。
-
一个数组元素内可以同时存在多个链表节点。
-
数组下标为1的元素内存储的链表节点中存储着与链表节点2的元素中的链表节点之间的关系
第三点说起来有点绕口,但实际上很简单,就是常规的链表数据结构,在当前节点描述下一个节点的信息
用lua的Table模拟出来就是这样的
["节点ID"]=
{
Content="当前节点NPC的文本内容"
Next="下一个节点的ID"
Pre="上一个节点的ID"
}
但是以上的数据结构还比较简陋,并不具备解决多分支选项的能力。
所以此时需要给这个数据结构加点料!
其实想要解决多分支选项问题很简单,上文中的数据结构中的Next
以及Pre
字段是一个string
类型的变量。我们将其变为一个Table类型的变量即可解决这个问题。
["10003"]