返回项目列表
PythonGodot 4GDScriptWebSocket自定义 DSL游戏开发

互动叙事引擎

基于自定义 DSL 的脚本驱动交互式小说游戏引擎

Aug 2024
GitHub

背景——为什么做这个

市面上的互动小说工具要么过于简单(只支持线性叙事),要么学习成本极高(需要掌握完整的游戏引擎)。我想构建一个"中间层":让创作者用简洁的脚本语言就能写出支持分支剧情、变量系统、骰子机制的复杂故事,同时拥有完整的多媒体展示能力。

问题拆解

  • 1如何设计一个足够简洁、创作者不需要编程基础也能上手的 DSL 脚本语言?
  • 2游戏状态(变量、场景、玩家选择历史)如何在 Python 后端与 Godot 前端之间可靠同步?
  • 3如何支持条件分支(-when 参数)、骰子机制(1d20、1d6+属性值)等复杂游戏逻辑?
  • 4多媒体资源(背景音乐、音效、配音、图片)如何与剧情脚本紧密结合?

解决方案与系统架构

设计了一套自定义 DSL 脚本语言,核心命令包括 say(对话)、choose(选项分支)、setVar(变量赋值)、jump(场景跳转)、roll(骰子判定)。Python 后端负责脚本解析、游戏逻辑计算和状态管理,Godot 4 前端负责 UI 渲染和多媒体播放,两者通过 WebSocket 实时通信,实现逻辑与展示的彻底分离。

系统架构

Python 后端引擎采用状态机模式管理游戏进程,每个场景(Scene)作为独立的状态节点。脚本解析器将 DSL 文本转换为可执行的指令队列,支持数学表达式计算(用于属性加成骰子)和条件求值(用于分支判断)。Godot 前端通过 WebSocket 接收指令,根据指令类型触发对应的 UI 动画、音频播放或用户输入收集,再将结果回传后端继续推进剧情。

技术栈

Python 3.8+(后端引擎)Godot 4.x / GDScript(前端 UI)WebSocket(前后端实时通信)自定义 DSL(故事脚本语言)Windows / Linux / macOS 跨平台
Prompt 工程

Prompt 设计

这个项目没有直接使用 LLM,但 DSL 的设计思路与 Prompt 工程高度相通:都是在设计一种"给非技术用户的结构化表达语言"。say、choose、setVar 这些命令的命名和语法,经过多次迭代,目标是让创作者"读脚本就像读剧本",降低认知负担。

系统工作流程

  1. 1
    创作者用 DSL 编写 .script 故事文件
  2. 2
    Python 引擎解析脚本,构建场景状态机
  3. 3
    启动 WebSocket 服务,等待 Godot 前端连接
  4. 4
    Godot 前端连接后,后端开始推送第一个场景指令
  5. 5
    前端渲染对话、选项、音效等多媒体内容
  6. 6
    玩家做出选择,前端将结果回传后端
  7. 7
    后端根据条件和变量计算下一个场景,继续推送

遇到的问题 & 优化过程

问题

WebSocket 通信中,偶发的消息乱序导致游戏状态与 UI 展示不一致,出现"对话跳帧"现象。

解决方案

为每条 WebSocket 消息增加序列号和确认机制(ACK),前端收到消息后回传确认,后端在收到确认前不推送下一条指令,彻底解决乱序问题。

问题

DSL 中骰子表达式(如 1d6+{strength})需要解析变量引用和动态计算,初版正则解析方案在复杂表达式下容易出错。

解决方案

重写为递归下降解析器(Recursive Descent Parser),支持嵌套表达式和变量内插,同时增加了友好的语法错误提示,帮助创作者快速定位脚本问题。

结果

  • 完整实现自定义 DSL 解释器,支持 8 种核心脚本命令
  • 支持条件分支、变量系统、骰子判定等复杂游戏机制
  • 实现图片、背景音乐、音效、配音的多媒体联动
  • 跨平台支持(Windows / Linux / macOS),提供可直接运行的 .exe
  • 附带示例脚本,创作者可零基础快速上手

反思

这个项目最大的收获是对"语言设计"的理解——哪怕是简单的 DSL,也需要在"表达力"和"简洁性"之间找到平衡。一开始我设计了太多命令,后来意识到大部分都可以用 setVar + jump 的组合实现,最终大幅精简了语法,让脚本更易读。前后端分离通过 WebSocket 通信的架构也让我真正理解了"状态同步"的复杂性。