From dbf857769e7abb603968277d21bb3c2f65d56973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=93=E9=9B=A8=E9=B9=8F?= <846149189@qq.com> Date: Sat, 13 Jun 2026 10:52:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(timeline):=20P1=20=E7=99=BD=E6=A8=A1?= =?UTF-8?q?=E6=BC=94=E5=87=BA=E9=A2=84=E8=A7=88=E2=80=94=E2=80=94=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E7=BA=BF+=E4=BF=AF=E8=A7=86=E8=88=9E=E5=8F=B0?= =?UTF-8?q?=E6=92=AD=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /api/pointsets 增 anchors(含 pos/rot/kind/npc 坐标),保留 points 名字数组兼容下拉 - 新增 static/timeline.js:线性化主路径→按时长铺多轨时间线 + 2D 俯视白模舞台播放 (走位插值/对话打字机/镜头俯视框示意/playhead/点击跳转);战斗/选择/随机仅标点不模拟 - 工具栏加「演出预览」按钮 + 预览遮罩 + 暗金主题样式 - 时长模型:对话=字数×打字机速度,走位=坐标距离÷速度,动画/镜头给缺省时长 - node 离线测试两样张(含 out_ref+fight+skip)逻辑全过;修掉结局重复渲染 bug 设计文档见主仓 docs/plans/2026-06-13-story-timeline-editor-and-whitebox-preview-design.md (P1 期) --- web/app.py | 14 +- web/static/app.js | 5 +- web/static/index.html | 19 +++ web/static/style.css | 33 ++++ web/static/timeline.js | 362 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 web/static/timeline.js diff --git a/web/app.py b/web/app.py index 5c40fa9..c14b4ff 100644 --- a/web/app.py +++ b/web/app.py @@ -163,9 +163,21 @@ async def pointsets(): try: with open(os.path.join(_POINTSETS_DIR, fn), encoding="utf-8") as f: ps = json.load(f) + pts = ps.get("points", []) out[name] = { "mapId": ps.get("mapId", ""), - "points": [p.get("name") for p in ps.get("points", [])], + "points": [p.get("name") for p in pts], # 名字数组:兼容现有表单下拉 + # 含坐标的锚点:白模演出预览用(map-local pos[x,y,z] + rot) + "anchors": [ + { + "name": p.get("name"), + "pos": p.get("pos") or [0, 0, 0], + "rot": p.get("rot", 0), + "kind": p.get("kind", ""), + "npc": p.get("npc", ""), + } + for p in pts + ], } except Exception as e: out[name] = {"error": str(e)} diff --git a/web/static/app.js b/web/static/app.js index 1d1dd9b..d95cd83 100644 --- a/web/static/app.js +++ b/web/static/app.js @@ -80,7 +80,7 @@ App.current = group; App.ir = JSON.parse(JSON.stringify(d.ir)); App.status = d.status; App.selectedNode = null; App.dirty = false; $("graph-empty").style.display = "none"; - ["btn-save", "btn-validate", "btn-playtest", "btn-confirm", "btn-discard", "btn-addnode", "btn-autolayout", "btn-addsucc"].forEach(b => $(b).disabled = false); + ["btn-save", "btn-validate", "btn-playtest", "btn-timeline", "btn-confirm", "btn-discard", "btn-addnode", "btn-autolayout", "btn-addsucc"].forEach(b => $(b).disabled = false); renderAll(true); GraphUI.focusStart(App.ir); // 定位到开头节点 snapReset(); // 初始化撤销栈 @@ -194,6 +194,9 @@ // ---------- 试走 ---------- $("btn-playtest").onclick = () => Playtest.open(App.ir, App.dict); + // ---------- 演出预览(白模时间线)---------- + $("btn-timeline").onclick = () => Timeline.open(App.ir, App.dict, App.pointsets); + // ---------- 导入 ---------- let importFiles = []; // 当前已选文件 function renderImportFiles() { diff --git a/web/static/index.html b/web/static/index.html index 22bcc1c..c519fe4 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -26,6 +26,7 @@ + @@ -115,10 +116,28 @@ + +
+ +