GDevelop 嵌入式 Yarn 对话树编辑器:外部编辑器集成机制与源码实现解析
2026/9/22 18:17:10 网站建设 项目流程

GDevelop 嵌入式 Yarn 对话树编辑器:外部编辑器集成机制与源码实现解析

【免费下载链接】GDevelop🎮 Open-source, cross-platform 2D/3D/multiplayer game engine designed for everyone.项目地址: https://gitcode.com/GitHub_Trending/gd/GDevelop

导读

GDevelop 内置了 Yarn Spinner 编辑器(YarnEditor),让开发者无需离开 IDE 即可创建和编辑对话树(Dialogue Tree)。本文以 newIDE/app/public/external/yarn/README.md 为主线,深入剖析该编辑器的嵌入架构、下载构建流程、浏览器/桌面双端桥接协议,并结合 Extensions/DialogueTree 扩展的源码说明 Yarn JSON 对话数据如何在游戏运行时被解析与播放。读完本文,你将掌握 GDevelop 外部编辑器(External Editor)机制的工作原理,并能在自己的项目中正确使用"Create with Yarn / Edit with Yarn"流程。

1. 概览:Yarn 编辑器如何嵌入 GDevelop

newIDE/app/public/external/yarn/目录存放的是用于嵌入 Yarn Spinner 编辑器的全部宿主代码,目标是让 Yarn 编辑器能直接从 GDevelop 中被打开,用来编辑/创建对话树资源。目录结构如下:

  • README.md:目录说明(即本文依据的关联文档);
  • yarn-main.js:运行编辑器的主要代码,负责加载 Yarn、注入外部编辑器头部、处理消息通信;
  • yarn-index.html:浏览器(Web 版 GDevelop)使用的入口页面;
  • yarn-electron-index.html:桌面版(Electron)使用的入口页面;
  • yarn-style.css:宿主页面的样式。

从源码结构看,这一模式并非 Yarn 独有:GDevelop 采用统一的"外部编辑器"框架,将 Piskel(像素画)、Jfxr(音效)和 Yarn(对话树)三类资源编辑器统一接入,三者的宿主页面、消息协议与资源读写流程完全一致。

2. 构建流程:import-zipped-editor.js 下载与校验

2.1 Yarn 源码的获取方式

按 README 所述,Yarn 编辑器的源码并非手工维护在仓库中,而是由 newIDE/app/scripts/import-zipped-editor.js 脚本从 Yarn 编辑器官方构建产物中下载,且保持原样、不做任何修改,解压后存放在yarn-editor文件夹内(即newIDE/app/public/external/yarn/yarn-editor/)。

该脚本的核心逻辑:

  1. 计算目标文件夹的 SHA256 哈希(hashElement(basePath, { algo: 'sha256', encoding: 'hex' }),见import-zipped-editor.js#L24-L40);
  2. 若文件夹已存在且哈希与期望值一致,则跳过下载("already existing ... up-to-date");
  3. 否则从https://github.com/4ian/GDevelop/releases/download/v<gitRelease>/<editor>-editor.zip下载压缩包(#L69-L77),失败时自动重试最多 3 次(retryIfFailed({ times: 3, backoff: { initialDelay: 400, factor: 2 } }));
  4. 使用AdmZip解压到public/external/<editor>/目录并删除压缩包;
  5. 解压后再计算一次哈希,若与期望值不一致则打印警告,提示"第三方编辑器可能被篡改"(#L104-L113)。

2.2 命令行调用与校验值

在 newIDE/app/package.json 中定义了import-zipped-external-editors脚本,Yarn 相关的调用为:

cd scripts && node import-zipped-editor.js yarn 5.0.134 ba8558cad00ec9b18cf3c6fd8647f8c1478ca67c894bca94a152a3740af209cc

三个参数含义分别是:

参数含义
editoryarn编辑器标识,同时决定目录名yarn-editor与压缩包名yarn-editor.zip
gitRelease5.0.134编辑器在 GDevelop 仓库 Release 中发布的版本号,用于拼接下载 URL
expectedFolderHashba8558cad00ec9b18cf3c6fd8647f8c1478ca67c894bca94a152a3740af209cc解压后yarn-editor文件夹期望的 SHA256 哈希,用于完整性校验

升级编辑器时的注意点:若要升级第三方编辑器的 zip 包,必须同时更新 package.json 中的版本号和哈希值,否则脚本会因哈希不匹配而反复重新下载并给出篡改警告。这一机制既保证了构建可复现,也降低了供应链被投毒的风险。

3. 宿主页面与编辑器加载

3.1 浏览器入口 yarn-index.html

newIDE/app/public/external/yarn/yarn-index.html 是一个极简页面:

<div id="external-editor-header"></div> <iframe id="yarn-frame"></iframe> <script type="module" src="yarn-main.js"></script>
  • external-editor-header:用于渲染由 external-editor-header.js 生成的"保存/取消"头部;
  • yarn-frame:承载 Yarn 编辑器本体(指向yarn-editor/index.html)的 iframe;
  • yarn-main.js:以 ES Module 方式加载的主控逻辑。

3.2 桌面版入口 yarn-electron-index.html

newIDE/app/public/external/yarn/yarn-electron-index.html 与浏览器版几乎一致,区别在于所有静态资源通过gdide://自定义协议加载(如gdide://external/yarn/yarn-main.js)。该协议由 Electron 主进程注册,用于在桌面应用中访问 IDE 的静态资源。其<title>GDevelop Dialogue Tree Editor (Yarn),源码注释特别说明:桌面应用会依据该标题判断用户当前焦点在主窗口(IDE)还是外部编辑器窗口,若修改标题需同步修改 ElectronMainMenu.js 中的判断逻辑。

4. 消息桥接:GDevelop 与 Yarn 编辑器的通信协议

4.1 三方协作架构

整个外部编辑器链路由三部分协作完成(对应 README 中提到的三个文件):

文件角色
yarn-main.js运行在 Yarn 编辑器窗口中,直接操作 Yarn 编辑器的内部数据 API
BrowserResourceExternalEditors.jsWeb 版的桥接层,用window.open+postMessage与编辑器窗口通信
LocalResourceExternalEditors.jsElectron 桌面版的桥接层,通过 IPC(ipcRenderer.invoke)与主进程通信
LocalExternalEditorWindow.jsElectron 主进程侧,负责真正创建承载编辑器的 BrowserWindow

4.2 消息协议

编辑器窗口与父窗口通过三类消息完成一次完整的"打开-编辑-保存"会话(协议实现见yarn-main.js#L18-L97BrowserResourceExternalEditors.js#L53-L97):

external-editor-ready(编辑器 → 父窗口)Yarn 编辑器加载完毕后触发yarnReady事件,宿主调用yarn.data.restoreFromLocalStorage(false)恢复编辑器状态,然后向父窗口发送就绪信号。父窗口收到该信号后才认为编辑器真正可用(BrowserResourceExternalEditors.js#L66-L86中注释说明:Safari 可能不触发load事件,因此以该消息作为加载完成的权威依据)。

open-external-editor-input(父窗口 → 编辑器)父窗口通过postMessage把待编辑资源传给编辑器,负载为一个ExternalEditorInput对象(类型定义见 ResourceExternalEditor.js),其中resourcesExternalEditorBase64Resource数组,每个资源包含:

  • name:资源名,为空表示需要新建资源;
  • dataUrl:base64 编码的data:URL,承载资源内容;
  • localFilePath:本地文件路径(桌面版),用于覆盖已有文件而非新建;
  • extension:新建文件时使用的扩展名。

收到消息后,yarn-main.js依次执行:

// 渲染头部(保存/取消按钮) const externalEditorHeader = createExternalEditorHeader({ ... }); // 克隆 Yarn 编辑器自带搜索框的结构,注入一个自定义 "Apply" 按钮 const saveToGdButton = yarn.document .getElementsByClassName('search-tags')[0] .cloneNode(true); saveToGdButton.onclick = () => saveAndClose(); saveToGdButton.childNodes[2].innerHTML = 'Apply';

这展示了嵌入式集成的常见技巧:不改动第三方编辑器源码,而是通过 DOM 操作在其界面上注入自己的入口按钮。

// 切换编辑模式并装载数据 yarn.data.editingPath(''); yarn.data.editingType('json'); if (isOverwritingExistingResource) { const response = await fetch(resource.dataUrl); const resourceData = await response.json(); yarn.data.loadData(JSON.stringify(resourceData), 'json', true); externalEditorHeader.setOverwriteExistingResource(); }

save-external-editor-output(编辑器 → 父窗口)点击 "Apply" 或头部"保存"按钮时触发saveAndClose()

const jsonString = yarn.data.getSaveData('json'); const dataUrl = convertJsonStringToDataUrl(jsonString); // 文本 → base64 data URL sendMessageToParentEditor('save-external-editor-output', { resources: [{ name, localFilePath, extension: '.json', dataUrl }], baseNameForNewResources: state.name, externalEditorData: null, }); closeWindow();

同时还有一条close消息用于通知父窗口编辑器窗口已关闭。

超时兜底:Web 版若 10 秒内编辑器既未加载也未关闭,会强制关闭窗口,避免阻塞用户(BrowserResourceExternalEditors.js#L135-L146)。

4.3 保存回写:data URL → Blob URL → 项目资源

父窗口收到输出后,由 ResourceExternalEditor.js 中的saveBlobUrlsFromExternalEditorBase64Resources完成回写:

  1. 将 data URL 转成 Blob,再创建blob:URL;
  2. 若资源名不存在,则用newNameGenerator生成唯一名(基于baseNameForNewResources,默认Untitled),创建新资源并写入扩展名等元数据;
  3. 若资源已存在,则更新其文件内容;
  4. 随后调用resourceManagementProps.onFetchNewlyAddedResources()让项目持久化这些资源(云端上传或本地写盘);
  5. 最后freeBlobsAndUpdateMetadata调用URL.revokeObjectURL释放临时 Blob URL,防止内存泄漏,并触发"资源被外部修改"事件刷新编辑器界面(triggerOnResourceExternallyChanged)。

4.4 浏览器版与桌面版的差异

  • 浏览器版仅允许云项目使用:BrowserResourceExternalEditors.js中 piskel/jfxr/yarn 三个编辑器在getStorageProvider().internalName !== 'Cloud'时会弹出cloudProjectWarning提示并中止;窗口打开必须在用户点击的同步时机内完成(immediatelyOpenLoadingWindowForExternalEditor),否则会被 Safari 等浏览器拦截弹窗。
  • 桌面版没有该限制,本地文件直接通过fs.promises.readFile读取为data:text/plain;base64,...(见LocalResourceExternalEditors.js#L128-L146),并通过 IPC 的yarn-load通道(ipcRenderer.invoke('yarn-load', externalEditorInput))把数据交给主进程。

5. 对话树扩展:运行时如何消费 Yarn JSON

编辑器产出的是 Yarn JSON 格式的对话数据,运行时的消费端是 Extensions/DialogueTree 扩展。其 JsExtension.js 将该扩展描述为:

"Handle dialogue trees, made using Yarn Spinner. Useful to make complex dialogues with multiple choices. The Yarn Spinner editor is embedded in GDevelop so you can edit your dialogues without leaving GDevelop."

扩展声明kind: 'json'、元数据键yarn,资源默认名为New dialogue tree(见BrowserResourceExternalEditors.js#L444-L468)。扩展提供的主要动作(均依赖Extensions/DialogueTree/dialoguetools.jsExtensions/DialogueTree/bondage.js/dist/bondage.min.js两个运行时文件):

动作说明
LoadDialogueFromSceneVariable从场景变量加载 Yarn JSON 对话数据,通常在游戏开始时调用
LoadDialogueFromJsonFile从 JSON 文件加载 Yarn JSON 对话数据
StartDialogueFromBranch从指定分支(branch)开始对话
StopRunningDialogue中断当前对话解析

其中 bondage.js 是 Yarn 对话格式的解析引擎(同一目录下还包含覆盖赋值、命令与函数、条件、链接、快捷键等场景的 tests 测试 JSON),dialoguetools.ts则提供了运行时管理对话状态、推进节点、处理选项的辅助逻辑。

由此形成的完整数据链路为:

Yarn 编辑器(yarn-editor,第三方源码) ↓ yarn-main.js 宿主注入 Apply 按钮 + data API 读写 ↓ postMessage / IPC 消息协议 GDevelop 资源系统(json 资源,metadata 键 "yarn") ↓ 导出游戏时打包 dialoguetools.js + bondage.min.js 游戏运行时 gdjs.dialogueTree.* 动作 ↓ 解析 Yarn JSON 分支对话、多选项、命令与变量

6. 在 GDevelop 中使用与二次开发 Yarn 编辑器

6.1 使用者视角

在 GDevelop 资源列表中,对 JSON 资源执行Create with YarnEdit with Yarn,即可打开内置的 Yarn 编辑器编辑对话树,保存后资源被回写为项目 JSON 资源;随后在事件编辑器中通过 Dialogue Tree 扩展的动作(加载、启动分支、停止)驱动对话播放。桌面版(Electron)与 Web 版(云项目)均支持,使用体验一致。

6.2 二次开发者视角

若要在此仓库中构建或更新 Yarn 编辑器,只需执行:

cd newIDE/app npm run import-zipped-external-editors

该命令会依次拉取并校验 piskel、jfxr、yarn 三个编辑器(Yarn 版本 5.0.134,见 package.json)。升级 Yarn 编辑器时,需把新的 zip 上传到 GDevelop 仓库的 Release 中,更新脚本的gitReleaseexpectedFolderHash参数,并重新运行构建。

6.3 关键源码索引

关注点文件
目录说明(本文主体)newIDE/app/public/external/yarn/README.md
编辑器宿主主逻辑newIDE/app/public/external/yarn/yarn-main.js
浏览器/桌面入口yarn-index.html、yarn-electron-index.html
下载与校验脚本newIDE/app/scripts/import-zipped-editor.js
资源类型与回写工具newIDE/app/src/ResourcesList/ResourceExternalEditor.js
Web 桥接层newIDE/app/src/ResourcesList/BrowserResourceExternalEditors.js
桌面桥接层newIDE/app/src/ResourcesList/LocalResourceExternalEditors.js
Electron 主进程窗口newIDE/electron-app/app/LocalExternalEditorWindow.js
运行时对话扩展Extensions/DialogueTree/JsExtension.js、dialoguetools.ts
Yarn JSON 解析引擎Extensions/DialogueTree/bondage.js

7. 小结

从 newIDE/app/public/external/yarn/README.md 这份简短的目录说明出发,可以看到 GDevelop 在集成第三方编辑器上的完整工程实践:下载脚本 + SHA256 校验保证第三方源码不被篡改;统一的外部编辑器头部与消息协议(ready / open-input / save-output / close)让不同编辑器复用同一套宿主框架;data URL 与 Blob URL 的转换链路确保资源在浏览器与桌面双端都能安全回写;运行时的 Yarn 解析由 DialogueTree 扩展独立承担,编辑器与运行时彻底解耦。理解这一机制,无论是使用 Yarn 编辑对话树,还是为 GDevelop 接入新的外部编辑器,都能事半功倍。

【免费下载链接】GDevelop🎮 Open-source, cross-platform 2D/3D/multiplayer game engine designed for everyone.项目地址: https://gitcode.com/GitHub_Trending/gd/GDevelop

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询