diff --git a/docs/EDITOR_ROADMAP.md b/docs/EDITOR_ROADMAP.md index 74c939f..3877a7f 100644 --- a/docs/EDITOR_ROADMAP.md +++ b/docs/EDITOR_ROADMAP.md @@ -1,84 +1,30 @@ -# 编辑器 Roadmap — 与引擎功能同步 +# 编辑器 Roadmap -P3 完成的编辑器支持基本的场景节点编辑。以下是与引擎 P4~P31 同步所需的编辑器功能迭代。 +编辑器核心定位:**图谱可视化 + 实时预览测试**。场景字段由创作者在 VS Code 中直接写 JSON,编辑器不做 GUI 表单。 --- -## 已完成(P3) +## 已完成 | 功能 | 完成 | |------|:--:| | 场景节点图(Vue Flow) | ✅ | -| 场景属性编辑(videoUrl, choices, nextScene, onEnter) | ✅ | -| QTE 参数编辑 | ✅ | | JSON 导入/导出 | ✅ | | 视频预览 | ✅ | +| Pinia 状态管理(editorStore) | ✅ | +| 纯函数数据层(GraphService) | ✅ | +| 图谱/预览切换 | ✅ | +| 节点右键测试(新标签页) | ✅ | +| 节点拖拽位置记忆 | ✅ | --- -## E1: 图片/视频热点编辑(对标 P4) +## E10: 内嵌快速测试 ✅ 部分完成 -- [ ] NodeEditor 新增 `type` 切换(video / image) -- [ ] `imageUrl` 字段 -- [ ] 热区列表编辑:id / label / targetScene / x / y / w / h -- [ ] 时间轴参数:showAt / hideAt -- [ ] conditions / effects 编辑 -- [ ] timeLimit 字段 - -## E2: 循环等待参数(对标 P5) - -- [ ] loopStart / loopEnd 两个数字字段 - -## E3: BGM 字段编辑(对标 P6) - -- [ ] bgmUrl / bgmVolume / bgmCrossFade -- [ ] bgmDuckLevel / bgmDuckFade -- [ ] videoMuted - -## E4: i18n Key 编辑(对标 P11/P31) - -- [ ] SceneNode: subtitleUrl → subtitles map(按语言编辑) -- [ ] Choice: textKey / promptKey -- [ ] Hotspot: labelKey -- [ ] ChapterInfo: labelKey -- [ ] AchievementDef: titleKey / descKey -- [ ] EndingDef: labelKey -- [ ] QTEDefinition: promptKey -- [ ] BattleHUD / BattleResult: labelKey / titleKey - -## E5: Choice 增强(对标 P13) - -- [ ] prompt 字段编辑 - -## E6: 顶级字段编辑(对标 P8/P14/P15/P20/P24) - -- [ ] chapters 列表编辑(id / label / startScene / thumbnail / defaultVariables) -- [ ] achievements 列表编辑(id / title / description / icon / hidden / condition) -- [ ] endings 列表编辑(id / label / sceneId / chapterId / thumbnail) -- [ ] introVideo / menuVideo -- [ ] streamingUrl map(三档画质 key-value) -- [ ] assetBase 字段 - -## E7: 条件路由编辑(对标 P25) - -- [ ] nextScene 类型切换(字符串 / 数组) -- [ ] 数组模式:逐条编辑 conditions + targetScene - -## E8: 关键节点编辑(对标 P26) - -- [ ] keyMoment 字段(三态:自动 / 强制 true / 强制 false) - -## E9: 战斗系统编辑(对标 P31) - -- [ ] battleHUD 列表编辑:每项含 label / portrait / stats 数组 -- [ ] battleHUD stats 编辑:variable / label / max / style -- [ ] battleResult 编辑:title / stats 数组 - -## E10: 内嵌快速测试 - -- [ ] 节点右键菜单 → "从此场景开始测试" -- [ ] PreviewPanel 切换为 iframe 内嵌游戏播放器(加载 `/?scene=`) -- [ ] Engine 支持 `?scene=` URL 参数指定起始场景 +- [x] 节点右键菜单 → "从此场景开始测试" +- [x] 新标签页打开游戏 +- [x] Engine 支持 `?startScene=` URL 参数 +- [ ] PreviewPanel 内嵌 iframe 游戏播放器(远期) ## E11: 场景列表 + 搜索 @@ -90,75 +36,33 @@ P3 完成的编辑器支持基本的场景节点编辑。以下是与引擎 P4~P - [ ] 导出/保存前实时检查 - [ ] 引用完整性:targetScene 指向不存在的场景 ID -- [ ] 死路检测:无 choices / 无 nextScene / 无 qte / 无 hotspots 的场景 +- [ ] 死路检测:无 choices / 无 nextScene / 无 qte / 无 hotspots - [ ] 变量引用:conditions 中的 variable 未在 `variables` 声明 ## E13: 撤销/重做 - [ ] 操作历史栈(add/delete/update/move node & edge) - [ ] Ctrl+Z 撤销 / Ctrl+Shift+Z 重做 -- [ ] `useGraphEditor` 中维护 `history[]` + `historyIndex` -## E14: 面板可折叠 + 标签页 +## E16: NodeEditor → JSON 编辑器 ✅ 已完成 -- [ ] NodeEditor 和 PreviewPanel 加折叠/展开按钮 -- [ ] NodeEditor 按类别分标签页:基本(id/type/videoUrl)/ 音频(bgmUrl 等)/ 战斗(battleHUD/battleResult)/ 高级(loopStart/keyMoment) -- [ ] 标签页间切换不丢失编辑状态 +- [x] 删除现有 GUI 表单,改为 `
@@ -224,6 +90,7 @@ function saveQTE() { justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid rgba(255,255,255,0.06); + flex-shrink: 0; } .editor-header h3 { @@ -234,7 +101,23 @@ function saveQTE() { .header-actions { display: flex; + align-items: center; gap: 6px; + min-width: 0; +} + +.saved-hint { + font-size: 11px; + color: #4caf50; +} + +.error-hint { + font-size: 11px; + color: #e74c3c; + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .icon-btn { @@ -246,140 +129,27 @@ function saveQTE() { border-radius: 4px; cursor: pointer; font-size: 13px; + flex-shrink: 0; } .icon-btn:hover { color: #ddd; border-color: rgba(255,255,255,0.25); } .icon-btn.danger:hover { color: #e74c3c; border-color: #e74c3c; } -.icon-btn.small { width: 22px; height: 22px; font-size: 11px; } -.editor-body { +.json-area { flex: 1; - overflow-y: auto; - padding: 12px 16px; - display: flex; - flex-direction: column; - gap: 14px; -} - -.field-group { - display: flex; - flex-direction: column; - gap: 6px; -} - -.field-group label { - font-size: 12px; - color: #888; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.field-row { - display: flex; - gap: 6px; -} - -input, select { - width: 100%; - padding: 8px 10px; - font-size: 13px; - background: rgba(255,255,255,0.05); - border: 1px solid rgba(255,255,255,0.1); - border-radius: 4px; - color: #ddd; + padding: 14px 16px; + background: #0a0a1a; + color: #ccc; + border: none; outline: none; -} - -input:focus, select:focus { - border-color: rgba(255,255,255,0.25); -} - -.qte-toggle { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; -} - -.qte-toggle input[type="checkbox"] { - width: auto; -} - -.qte-fields { - display: flex; - flex-direction: column; - gap: 8px; - padding: 10px; - background: rgba(255,255,255,0.03); - border-radius: 4px; -} - -.qte-row { - display: flex; - align-items: center; - gap: 8px; -} - -.qte-row span { + font-family: 'Menlo', 'Monaco', 'Courier New', monospace; font-size: 12px; - color: #777; - white-space: nowrap; - min-width: 80px; + line-height: 1.5; + resize: none; + tab-size: 2; } -.qte-row input, .qte-row select { - flex: 1; -} - -.choices-section { - border-top: 1px solid rgba(255,255,255,0.06); - padding-top: 12px; -} - -.add-btn { - padding: 6px 12px; - font-size: 12px; - color: #8cf; - background: rgba(100,200,255,0.08); - border: 1px solid rgba(100,200,255,0.2); - border-radius: 4px; - cursor: pointer; - transition: background 0.2s; -} - -.add-btn:hover { background: rgba(100,200,255,0.15); } - -.choice-item { - display: flex; - flex-direction: column; - gap: 6px; - padding: 8px; - background: rgba(255,255,255,0.02); - border: 1px solid rgba(255,255,255,0.05); - border-radius: 4px; -} - -.choice-header { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 11px; - color: #666; -} - -.choice-extra { - display: flex; - align-items: center; - gap: 8px; -} - -.inline-label { - font-size: 11px; - color: #666; - white-space: nowrap; -} - -.time-input { - width: 60px; +.json-area.error { + border-left: 3px solid #e74c3c; }