资讯动态

Godot 4对话系统开发指南:无状态设计与分支对话实现

发布时间:2026/8/22 20:25:40 来源:尧图企业网站定制
1. 项目概述一个为Godot 4.6量身打造的对话系统如果你正在用Godot引擎开发游戏尤其是那些需要大量角色对话、分支剧情或者复杂叙事的项目那么“对话”这个环节的处理绝对能让你头疼一阵子。原生的Godot引擎虽然强大但在对话系统的构建上更多是提供了一些基础的UI和节点工具你需要自己从零开始搭建一套逻辑如何管理对话文本如何处理分支选择如何根据游戏状态比如玩家是否完成了某个任务来动态改变对话内容这些工作不仅繁琐而且容易让游戏逻辑变得混乱不堪。这就是我今天想详细聊聊的nathanhoad/godot_dialogue_manager以下简称Dialogue Manager存在的意义。它不是一个简单的文本显示插件而是一个完整的、无状态stateless的、支持分支对话的编辑器和运行时解决方案。简单来说它帮你把游戏里所有“说话”的事情都包揽了从编写、管理到最终在游戏里播放提供了一套优雅的流水线。我花了相当长的时间在几个不同类型的叙事项目中深度使用了它从简单的NPC闲聊到复杂的、影响剧情走向的多分支对话树它都表现得相当稳健。接下来我会结合自己的实战经验为你拆解这个工具的核心设计、为什么它值得你花时间学习以及如何避开那些我踩过的坑。2. 核心设计哲学为什么是“无状态”与“类脚本”语法在深入代码之前理解Dialogue Manager的设计哲学至关重要这直接决定了你使用它的舒适度和项目的可维护性。它的两个核心关键词是“无状态Stateless”和“类脚本Script-like语法”。2.1 无状态运行时的优势很多传统的对话系统或者我们自己手搓的方案往往会维护一个复杂的“对话状态机”。比如用一个变量记录当前对话进行到第几句用另一个字典记录哪些选项被选择过哪些NPC的对话已经触发过。这种方式在小型项目中尚可但随着对话树变得庞大状态管理会像滚雪球一样越来越复杂极易出现Bug比如对话卡住、选项重复出现或者该出现的对话没触发。Dialogue Manager采用了截然不同的思路运行时无状态。这意味着对话管理器本身不存储“当前进行到哪一步了”这样的信息。每一次对话的推进都完全由你提供的对话资源文件.dialogue和当前游戏的状态通过变量传递来决定。你可以把它想象成一个非常聪明的“对话播放器”你给它剧本.dialogue文件和当前演员的情绪、道具等上下文游戏变量它就能立刻计算出下一句该播什么该给出哪些选项。这样做带来的最大好处就是纯粹和可预测。调试对话变得异常简单因为任何时刻的对话输出都只取决于输入的游戏状态。你可以轻松地保存/加载游戏因为不需要额外保存复杂的对话进度只需要保存那些影响对话的游戏变量即可。同时它也天然支持对话的“跳转”和“测试”你可以在编辑器中随时从任意节点开始预览对话而不受之前状态的影响。2.2 类脚本语法的直观与强大Dialogue Manager让你在一个文本文件中编写对话其语法设计得非常直观类似于写电影剧本或编程脚本。这种设计极大地提升了编写效率和可读性。# 这是一个简单的.dialogue文件示例 Nathan: 你好啊旅行者。今天天气真不错。 Player: * 是啊适合出去冒险。 Nathan: 说到冒险我听说东边的森林里最近不太平。 some_other_dialogue * 我赶时间再见。 Nathan: 好吧祝你好运。 END上面这段代码即使你没接触过这个插件也能大概看懂一个叫Nathan的角色说了一句话然后玩家有两个选择每个选择会触发Nathan不同的回应并导向不同的结果跳转到其他对话或结束。这种写法让编剧、策划和程序员能在同一个文件上高效协作策划可以直接撰写分支程序员则专注于背后的变量逻辑。更重要的是这种语法支持丰富的内联指令比如条件判断、变量变更、调用游戏函数等这些都会在后面的章节详细展开。这种将逻辑与内容紧密结合的方式避免了在多个不同文件如JSON数据表、脚本文件之间来回切换的麻烦。3. 从安装到第一个对话完整实操指南理论说得再多不如亲手搭一个。下面我就带你从零开始在Godot 4.6项目中集成Dialogue Manager并创建你的第一段可运行对话。3.1 插件的安装与项目设置安装Dialogue Manager主要有两种方式我强烈推荐第一种因为最方便管理更新。方法一通过Godot资产库安装推荐打开你的Godot项目。点击顶部菜单栏的项目Project-项目设置Project Settings。切换到插件Plugins标签页。点击右上角的安装Install按钮这会打开在线资产库。在搜索框中输入 “Dialogue Manager”你应该能很快找到它。确认作者是nathanhoad。点击下载并安装。安装完成后回到插件管理页面找到 “Dialogue Manager”点击旁边的启用Enable复选框。Godot可能会要求你重启编辑器照做即可。方法二手动下载安装访问项目的GitHub仓库https://github.com/nathanhoad/godot_dialogue_manager下载最新的main分支ZIP包。解压后将addons/dialogue_manager文件夹完整地复制到你Godot项目的addons/目录下。如果项目没有这个目录就新建一个。同方法一在项目设置 - 插件中启用它。注意确保你的Godot版本是4.6 或更高。虽然文档提到有适用于旧版Godot的v3版本但为了获得最新特性和稳定性强烈建议使用匹配的版本。我曾在4.5的项目里尝试用4.6的插件遇到了一些不兼容的API问题折腾了半天。启用插件后你会在Godot编辑器的顶部工具栏看到一个新增的对话Dialogue菜单同时在场景编辑器的节点创建对话框中也能找到新增的DialogueManager和DialogueLabel等节点类型。这标志着插件安装成功。3.2 创建你的第一个对话资源与场景现在我们来创建最基础的对话。创建对话资源文件在Godot的文件系统面板中右键点击你想保存的文件夹例如res://dialogues/选择新建资源New Resource。在弹出窗口的搜索框里输入 “Dialogue”选择Dialogue Resource然后点击创建Create。给它起个名字比如first_conversation.dialogue。双击这个文件Godot会打开一个内置的、语法高亮的文本编辑器这就是你编写对话的地方。编写基础对话在打开的.dialogue文件中输入以下内容~ 这是一个注释行以波浪号开头。 Nathan: 欢迎使用Dialogue Manager Player: 这看起来很简单。 Nathan: 是的但它的能力远不止于此。让我们继续吧 END每一行都是一个“对话行”。角色名后接冒号和空格然后是台词。 END是一个特殊指令表示对话结束。构建播放对话的场景创建一个新场景添加一个Node2D或Node3D作为根节点命名为TestDialogueScene。为这个场景添加一个脚本。在场景中添加一个Button节点我们将用它来触发对话。在场景中添加一个ColorRect或Panel节点作为对话框背景然后在它里面添加一个Label节点来显示文字。不过更推荐使用插件提供的DialogueLabel节点因为它内置了逐字显示打字机效果的功能。你可以从节点添加面板搜索DialogueLabel添加。将DialogueLabel节点命名为DialogueLabel并将Button节点的文本改为“开始对话”。编写场景脚本以触发对话附上以下GDScript代码到场景根节点extends Node2D onready var dialogue_label $DialogueLabel onready var start_button $Button func _ready(): start_button.pressed.connect(_on_start_button_pressed) func _on_start_button_pressed(): # 禁用按钮防止重复点击 start_button.disabled true # 加载我们刚才创建的对话资源 var dialogue_resource load(res://dialogues/first_conversation.dialogue) # 使用 DialogueManager 显示对话 # show_dialogue_balloon 是插件提供的最便捷的方法它会创建一个悬浮对话框 DialogueManager.show_dialogue_balloon(dialogue_resource) # 对话结束后重新启用按钮我们需要知道对话何时结束 # 我们可以连接 DialogueManager 的信号或者用其他方式判断。 # 这里为了简单我们假设对话很短直接延迟启用按钮实际项目不推荐。 await get_tree().create_timer(5.0).timeout start_button.disabled false运行场景点击按钮你应该能看到一个默认风格的对话框弹出并逐句显示我们写的对话。恭喜你第一个对话系统跑通了实操心得DialogueManager.show_dialogue_balloon()是插件提供的“开箱即用”的快速方法它自动处理了对话框的创建、显示和销毁。对于快速原型和简单对话非常有用。但在实际游戏中你通常需要自定义UI。这时你需要使用DialogueManager.show_example_dialogue_balloon()的变体或者更底层地使用DialogueManager.get_next_dialogue_line()方法来手动控制每一句对话的获取和显示。我们会在后续章节深入自定义UI的部分。4. 对话语法的深度解析与高级应用掌握了基础之后我们来深入挖掘.dialogue文件语法的强大之处。这是Dialogue Manager的核心理解它能让你设计出极其复杂的叙事逻辑。4.1 角色对话、选择与跳转最基本的元素就是角色对话和玩家选择。Guard: 站住此路不通。 Player: * 我是国王的朋友。 Guard: 哦失敬失敬您请过。 gate_opened * 我有通行证。 Guard: 让我看看...嗯是真的。过去吧。 gate_opened * 默默掏出10枚金币 Guard: 左右张望...快过去吧。 ~ 这里可以内联改变一个变量比如减少玩家金币 - money 10 gate_opened * 那我走别的路。 Guard: 随你的便。 END gate_opened Narrator: 守卫让开了道路。Player:下面缩进的*列表代表了玩家的选项。是指令用于跳转。 END结束对话 gate_opened跳转到标签为gate_opened的位置。 gate_opened定义了一个标签可以作为跳转的目标。~是行内注释。- money 10是一个“突变Mutation”它会在对话运行时执行这里是将变量money的值减少10。突变是动态改变游戏状态的关键。4.2 条件判断让对话“活”起来对话能否出现选项是否可用都可以基于游戏状态进行条件判断。这是实现分支叙事的核心。Blacksmith: 需要我为你做点什么吗 Player: * 我想买一把剑。 Blacksmith: 好的这把铁剑售价100金币。 Player: * 我买了。需要 money 100 Blacksmith: 成交祝您武运昌隆。 - money 100 has_iron_sword true END * 太贵了我买不起。 Blacksmith: 那就没办法了。 END * 我想卖一些矿石。需要 has_ore true Blacksmith: 让我看看你的矿石...嗯这些我给你50金币。 money 50 - has_ore true END * 只是看看。 Blacksmith: 随时欢迎。 END注意需要 money 100和需要 has_ore true。这些写在选项文本后面的括号里就是条件Conditions。只有当条件满足时这个选项才会显示给玩家。条件可以使用丰富的比较运算符,!,,,,和逻辑运算符and,or,not。4.3 变量与突变连接对话与游戏逻辑对话不是孤立的它需要读取和改变游戏世界。这是通过变量Variables和突变Mutations实现的。变量可以在对话中直接引用它们通常来自你的游戏全局状态。你需要在游戏启动时将变量注入到Dialogue Manager的上下文里。# 在你的游戏全局脚本如 autoload 的单例中 func start_game_dialogue(): var game_state { “player_name”: “阿强” “money”: 150, “has_key”: false, “quest_completed”: { “find_cat”: true, “deliver_letter”: false } } DialogueManager.set_game_state(game_state)在对话文件中你可以这样使用~ 读取变量 Innkeeper: 欢迎光临{player_name}。住店一晚20金币。 ~ 条件判断 {if money 20} Player: 好的给我一间房。 Innkeeper: 二楼左手边第一间。 - money 20 {else} Player: 呃...我钱不够。 Innkeeper: 那就抱歉了。 {endif} ~ 突变可以操作复杂结构 {if not quest_completed.deliver_letter} quest_completed.deliver_letter true Innkeeper: 对了如果你要去集市能帮我把这封信带给卖面包的玛莉吗 {endif}突变的语法非常灵活/-增加/减少数值或设置布尔值/字符串。赋值。可以操作嵌套的字典属性如quest_completed.deliver_letter。4.4 内联逻辑与函数调用除了简单的变量操作你还可以在对话中执行更复杂的逻辑甚至调用你游戏中的GDScript函数。Scientist: 这个实验装置的能量等级是 {calculate_energy_level()}%。 Player: 怎么才能提高它 Scientist: 注入一些冷却剂试试。 Player: * 注入冷却剂。 ~ 调用一个无参函数 do_inject_coolant() Scientist: 很好能量等级稳定在 {calculate_energy_level()}% 了。在游戏脚本中你需要注册这些函数func _ready(): # 注册一个返回值的函数 DialogueManager.add_function(“calculate_energy_level”, calculate_energy_level) # 注册一个无返回值的函数 DialogueManager.add_function(“do_inject_coolant”, do_inject_coolant) func calculate_energy_level() - float: # 复杂的游戏逻辑计算 return some_global_energy_level func do_inject_coolant() - void: # 执行注入冷却剂的游戏内操作 Global.inject_coolant() emit_signal(“coolant_injected”)这个功能极其强大它意味着你的对话可以直接触发游戏中的任何事件播放动画、移动角色、切换场景、播放音效等等实现了对话与游戏玩法的深度集成。5. 自定义对话UI从气球对话框到视觉小说界面插件自带的show_dialogue_balloon虽然方便但99%的商业项目都需要完全自定义的UI来契合游戏美术风格。Dialogue Manager在设计上充分考虑到了这一点将对话逻辑数据层与UI表现视图层清晰地分离开。5.1 手动管理对话流程核心是使用DialogueManager.get_next_dialogue_line(resource, title, extra_game_states)方法。它会根据当前上下文返回下一句对话的信息或者一个选择列表。extends Control # 假设这是你的自定义UI场景 onready var speaker_label: Label $UILayer/SpeakerLabel onready var text_label: RichTextLabel $UILayer/TextLabel onready var choices_container: VBoxContainer $UILayer/ChoicesContainer onready var choice_button_scene preload(“res://ui/choice_button.tscn”) var current_dialogue: DialogueResource var current_title: String “start” func start_dialogue(resource: DialogueResource, starting_title: String “start”) - void: current_dialogue resource current_title starting_title show() await get_tree().process_frame show_next_line() func show_next_line() - void: # 清除上一句的选项 for child in choices_container.get_children(): child.queue_free() # 获取下一行 var line: DialogueLine await DialogueManager.get_next_dialogue_line(current_dialogue, current_title) if line null: # 对话结束 end_dialogue() return # 更新UI显示 speaker_label.text line.character text_label.text line.text # 处理玩家选择 if line.responses.size() 0: # 这是一个选择节点 text_label.visible false # 选择时可能不显示叙述文本依设计而定 for response in line.responses: if not response.is_allowed: continue # 跳过条件不满足的选项 var button_instance choice_button_scene.instantiate() button_instance.text response.text # 将下一行的标题response.next_id传递给按钮 button_instance.next_title response.next_id button_instance.pressed.connect(_on_choice_selected.bind(response.next_id)) choices_container.add_child(button_instance) else: # 这是一个普通对话行等待玩家点击继续 text_label.visible true # 例如可以设置一个“继续”按钮或者等待鼠标点击 $UILayer/ContinueButton.visible true func _on_choice_selected(next_title: String) - void: current_title next_title show_next_line() func _on_continue_button_pressed() - void: $UILayer/ContinueButton.visible false current_title current_line.next_id # current_line 需要在上一步保存 show_next_line() func end_dialogue() - void: hide() # 发出对话结束信号通知其他系统 dialogue_finished.emit()通过这种方式你获得了UI的完全控制权。你可以自由地设计对话框的样式、位置、动画如打字机效果、渐入渐出、头像显示、音效播放等等。5.2 实现视觉小说风格的对话系统视觉小说Visual Novel风格的对话通常包含角色立绘、姓名框、文本框和历史记录等功能。基于上面的手动管理框架我们可以这样扩展角色立绘管理在DialogueLine对象中可以通过line.character判断说话者从而切换显示的立绘图片。你甚至可以扩展语法在对话行中加入表情标签如Nathan (happy): 今天真开心然后在代码中解析(happy)来切换角色表情。func update_character_portrait(character_name: String, emotion: String “neutral”) - void: var portrait_texture: Texture2D match character_name: “Nathan”: match emotion: “happy”: portrait_texture preload(“res://art/portraits/nathan_happy.png”) “angry”: portrait_texture preload(“res://art/portraits/nathan_angry.png”) _: portrait_texture preload(“res://art/portraits/nathan_neutral.png”) “Player”: # ... 处理玩家立绘 $PortraitSprite.texture portrait_texture对话历史记录维护一个数组将每一句显示过的DialogueLine对象包括角色、文本、时间保存起来。当玩家打开历史记录界面时将这个数组的内容渲染出来。var dialogue_history: Array[Dictionary] [] func _add_to_history(line: DialogueLine) - void: var history_entry { “character”: line.character, “text”: line.text, “time”: Time.get_time_string_from_system() } dialogue_history.append(history_entry)文本效果与音效利用RichTextLabel的BBCode功能可以在对话文本中嵌入颜色、字体、图标等。同时在显示每个字符时打字机效果触发一个细小的打字音效能极大增强沉浸感。注意事项自定义UI时一定要处理好异步操作。get_next_dialogue_line是一个异步函数await确保你的UI状态机如等待输入、播放动画能与之正确配合。一个常见的错误是在动画播放完成前就允许玩家点击跳过导致状态混乱。建议使用信号finished信号或await来严格序列化这些操作。6. 高级特性与集成技巧当你熟悉了基础对话和自定义UI后以下这些高级特性可以帮助你构建更专业、更易维护的对话系统。6.1 翻译与本地化支持Dialogue Manager内置了对Godot国际化i18n的良好支持。这意味着你可以轻松地为游戏添加多语言。准备翻译文件在Godot中通过项目设置 - 本地化 - 翻译添加.po或.csv翻译文件。在对话中使用翻译键不要在.dialogue文件中直接写死文本而是使用Godot的翻译键。Merchant: “DIALOGUE_GREETING” Player: “DIALOGUE_RESPONSE_YES”在代码中设置语言游戏启动时或玩家切换语言时调用TranslationServer.set_locale(“zh_CN”)。对话管理器自动处理DialogueManager在获取对话行时会自动通过tr()函数处理这些键返回当前语言下的对应文本。对于选项条件中的文本比较也需要使用翻译键。实操心得对于包含变量插值的句子如“你好{player_name}”直接使用翻译键会有点麻烦。一种做法是将整个句子作为翻译键但变量部分用占位符如DIALOGUE_GREETING “你好%s”然后在代码中获取翻译后的字符串再用%格式化。Dialogue Manager的最新版本对此有更好的支持可以查阅其翻译文档。6.2 与C#项目的兼容性如果你的Godot项目使用的是C#作为脚本语言Dialogue Manager同样提供了完整的支持。插件作者提供了一个C#包装器Wrapper。你需要从插件的docs文件夹或GitHub仓库中找到DialogueManager.cs这个文件并将其添加到你的C#项目中。这个包装器暴露了与GDScript版本几乎相同的API让你可以在C#中以类型安全的方式调用所有功能。// C# 示例 using Godot; using DialogueManager; public partial class MyDialogueUI : Control { private DialogueResource _dialogueResource; public override void _Ready() { _dialogueResource GD.LoadDialogueResource(“res://dialogues/my_dialogue.dialogue”); // 注册游戏状态 var gameState new Godot.Collections.Dictionary{ {“playerName”, “CSharpHero”}, {“gold”, 999} }; DialogueManager.SetGameState(gameState); // 注册C#函数 DialogueManager.AddFunction(“MyCSharpFunction”, Callable.From(MyCSharpFunction)); } private void StartDialogue() { DialogueManager.ShowDialogueBalloon(_dialogueResource); } private Variant MyCSharpFunction() { GD.Print(“Called from dialogue!”); return “Success”; } }6.3 对话的测试与调试开发复杂分支对话时调试是必不可少的。Dialogue Manager提供了几种方式来帮助你编辑器内预览在编辑.dialogue文件时右侧有一个“测试Test”面板。你可以输入初始的游戏状态JSON格式然后运行测试它会模拟对话流程清晰地展示每一句对话和可用的选项。这是检查条件逻辑和突变效果最快的方式。运行时调试输出在项目设置 - 插件 - Dialogue Manager中可以启用调试日志。这样在游戏运行时所有对话的解析、变量查找、函数调用都会打印到Godot的输出控制台对于追踪难以发现的逻辑错误非常有帮助。“对话追踪”模式在一些社区教程中开发者会实现一个“对话追踪”功能即在游戏UI的角落显示当前对话的节点ID、变量状态等。这需要你自行扩展核心是在每次调用get_next_dialogue_line前后记录和显示相关信息。7. 性能优化与常见问题排查在大型项目中对话资源可能非常庞大。虽然Dialogue Manager本身很高效但不当的使用仍可能导致卡顿或内存问题。7.1 资源管理与加载优化避免重复加载将常用的DialogueResource在游戏启动时预加载并缓存而不是每次对话开始都去load()。# 在一个全局单例中 var dialogue_cache: Dictionary {} func get_dialogue(path: String) - DialogueResource: if not dialogue_cache.has(path): dialogue_cache[path] load(path) return dialogue_cache[path]分割对话文件不要把所有游戏的对话都塞进一个巨大的.dialogue文件里。按章节、区域或角色进行拆分。这样加载更快也更容易管理。注意突变函数的性能在对话中注册的函数如果执行非常耗时的操作如复杂的路径计算、大量数据库查询会阻塞对话线程。尽量让这些函数快速返回或将耗时操作异步化。7.2 常见问题与解决方案速查表下面是我在项目中遇到的一些典型问题及其解决方法希望能帮你节省时间。问题现象可能原因解决方案对话不显示或立即结束1. 对话资源路径错误。2.get_next_dialogue_line的title参数错误找不到起始点。3. 第一行就是条件不满足的选项导致无有效内容。1. 检查load()的路径使用绝对路径res://开头。2. 默认起始title是“start”确保文件开头有 start标签或调用时传入正确的标签名。3. 在测试面板中检查初始游戏状态确保条件能通过。玩家选项不出现1. 选项的所有条件都不满足。2. 自定义UI中没有正确遍历和创建选项按钮。3. 选项文本后的条件语法错误。1. 在测试面板中检查变量值。2. 调试代码确认line.responses数组不为空且其中response.is_allowed为true。3. 检查条件括号是否匹配变量名是否正确。变量突变不生效1. 变量名拼写错误。2. 突变语法错误如用于布尔值。3. 游戏状态没有正确注入或更新。1. 开启调试日志查看突变执行记录。2. 布尔值应用赋值如 has_key true。3. 确保在对话开始前调用了DialogueManager.set_game_state()并且传入的是可变的字典Dictionary而非Variant。调用游戏函数失败1. 函数未正确注册到DialogueManager。2. 函数签名参数、返回值与对话中调用方式不匹配。3. 函数内部抛出未处理的异常。1. 确认add_function在对话开始前被调用。2. 对话中调用无参函数用function_name()有参函数用function_name(arg)。确保注册的函数能接受这些参数。3. 在函数内部做好错误处理或开启调试日志查看错误信息。打字机效果与点击过快冲突玩家在文字逐字显示时快速点击导致文本显示不全或直接跳过。在自定义UI逻辑中需要区分“正在播放动画”和“等待输入”两种状态。播放打字机效果时禁用继续按钮或点击响应。效果播放完毕后再启用输入。可以提供一个“跳过动画”的快捷键。对话历史记录显示错乱历史记录中包含了系统指令、跳转标签等内容。在保存到历史记录前检查DialogueLine的类型。通常只保存type为Dialogue的行过滤掉Mutation、Condition等内部节点。7.3 我踩过的坑关于游戏状态管理的经验最大的一个坑是关于游戏状态字典的引用与更新。如果你像下面这样做var my_game_state { “score”: 0 } DialogueManager.set_game_state(my_game_state) # 然后在游戏其他地方修改 my_game_state my_game_state[“score”] 100你可能会发现对话中的score变量并没有更新这是因为set_game_state在某些实现中可能是复制了你的字典而不是持有它的引用。安全的做法是每次游戏状态发生重要变化后都重新调用一次set_game_state或者直接通过DialogueManager提供的方法来更新状态如果插件支持。最可靠的方式是将所有需要同步到对话中的游戏状态集中管理在一个全局单例中任何修改都通过这个单例的方法进行并在方法内自动更新对话管理器的状态。另一个小技巧是对于复杂的游戏状态如任务列表、物品清单建议将其序列化为字符串或简单的字典结构再存入对话变量。直接在对话条件里写if quests[“find_cat”].state “completed”可能会因为对象比较而失败。不如存为if find_cat_quest “completed”这样简单的键值对。经过在多个项目中的实践Dialogue Manager for Godot 4 已经成为了我叙事工具箱中的标配。它的“无状态”设计起初需要一点思维转换但一旦适应你会发现构建复杂、动态的对话树变得前所未有的清晰和可控。从简单的NPC交互到影响整个剧情走向的重大选择它都能优雅地胜任。更重要的是它将内容对话文本与逻辑条件、突变很好地结合在一起同时又通过清晰的API与游戏代码分离这使得策划、编剧和程序员可以并行工作极大地提升了开发效率。如果你正在为你的Godot游戏寻找一个强大而灵活的对话解决方案我强烈建议你投入时间学习并使用它它绝不会让你失望。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价