下载 App
开发日志 · 2026-10-17
开发日志 · 2026-10-17
项目:烬织(Ember Weaving)— 卡牌动作 roguelike
主题:战斗表现重做 + 黑屏/解锁两处阻塞修复 + 渲染性能优化
规模:脚本 31 个文件、15,327 行;图片素材 294 张(角色图集 11 / 战斗特效图集 28)
概览
今天三条线并行:
表现层:逐帧重做刀光与六系元素,统一命中停顿时间线,放慢攻击节奏。
稳定性:修掉两处会让人"玩不下去"的阻塞——菜单永久黑屏、第二场景通关后进不去第三场景。
性能:角色图集去掉冗余放大,纹理预算降 84%;清理无用预载与重复绘制。
所有改动都走「定位根因 → 改代码 → 真实引擎实测」闭环,构建推进到 build 202。
一、战斗表现逐帧重做
1.1 从"贴片堆叠"到独立形态
上一版特效是发光圆环、薄光针、贴片堆叠,观感塑料。这次改为逐帧手工制作的像素材质,28 张图集(每张 6 帧、3×2 排布、单格 128px):
类别 数量 内容
刀光 3 slash1 下切 / slash2 上撩 / slash3 厚重斩
瞬间元素 15 火、水、荆棘、混沌、命中、死亡、雷、冰、风、蒸汽、治疗、护盾、符文
常驻主体 10 种子 / 植物 / 古树 / 荆棘王 / 符文 / 风 / 灰烬 / 火焰 / 水 / 蒸汽 / 荆棘丛
制作器 _proc/bake_combat_vfx.py 是确定性 Pillow 逐帧绘制——不调用任何 AI 生成服务,也不覆盖原素材。
1.2 常驻实体与瞬间施放分离
原先把常驻实体(火焰、水域)也塞进"六帧施放"序列,导致火焰看起来一直在"爆开"。现在拆开:
常驻主体:独立呼吸循环,只做细微亮度起伏,不再重播施放。
瞬间元素:六帧走"硬芯 → 展开 → 断形 → 碎散"。
瞬间素材的取帧阈值不再是均匀线性:
p < .08 → 帧0(硬接触芯)
p < .18 → 帧1
p < .43 → 帧2(展开主体)
p < .65 → 帧3
p < .84 → 帧4
否则 → 帧5(碎散收尾)
1.3 主次层级收敛
多个特效同点时,原来会叠出"白塑料片"。改为:
反应主爆发由唯一赢家承担(同强度选最新,大反应在短窗口内优先)。
卡牌落点只保留约 70ms 的小接触点,不重复爆一次。
实体主体在新爆发前段让出亮度,而不是消失或缩小。
连锁闪光若已有主反应,不再叠中心白芯。
二、命中停顿与时间线统一
原实现的问题:命中瞬间画面冻结,但 flash、碎片、卡牌等仍在消耗——冻结期间角色姿势和打击反馈对不上。
改为世界时间与真实时间双轨:
轨道 内容
世界时间(受停顿/慢动作影响) 受击闪、伤害数字、碎片、余刃、卡牌、残影、击杀、合成
真实时间(不受影响) 震屏、镜头推近、边缘反馈、提示、白闪
碎片的阻力也改为 0.90^(dt/fixedStep),避免慢动作下每步照旧衰减。
另修一处浮点漏刃:业务用 EPS 判命中时 age 可能略小于 windup,导致停顿整段不画接触刃。改为已 resolved 时允许 max(0, rawAge)。
三、镜头与放置坐标
镜头推近(punch)时,卡牌放置落点会漂移。
根因:绘制用了"震屏 + 中心缩放 + 镜头推近"三段变换,但输入反算只逆了其中一部分。
修复:把三者合并成单一 WorldTransform,并让 ScreenToWorld 严格逆同一变换;按下、拖拽、抬起三处共用。
顺带修一个既有缺口:旧重击强度 punchStrength 永不复位,导致 .152 的重击之后,.055 的轻击再也触发不了镜头反馈。现在 punch 归零时同步清强度。
四、Boss 表现层
Boss 姿态不再靠 fragmentTimer 猜,而是读真实蓄力进度与真实技能释放时刻。
表现图层从 WorldFeedback 移到玩家之后,主绘只有一处,预警边界画在最上层。
五、菜单永久黑屏(阻塞级)
现象
“游戏会时不时黑屏就没法玩了”——全屏遮罩一直不消失,菜单也点不动。
根因
一条确定的链路:
menu_view 在点击时创建 V.menuTransition。
main.lua 到 0.46 秒清空本地 menuTransition_。
MenuState() 构造的是普通 Lua table——nil 字段不会留下键。
View.Load() 只用 pairs(t) 覆盖,无法删除 V 里旧的 transition。
非战斗界面每帧 Load → Save → ApplyMenuState,又把旧 transition 恢复回来。
顶层遮罩原本随时间越画越黑(alpha 涨到 255),于是永久全黑。
菜单输入被 if V.menuTransition then return end 挡住。
触发条件解释了"偶发":从主菜单点故事/图鉴/难度/设置,或教程完成后"开始游戏 → 世界地图";而演示/自由模式暂时在战斗里不画这层罩,回菜单后才暴露。
修复
View.Load 显式同步 7 个可空字段(含 menuTransition),nil 能真正清掉。
过渡结束同时清本地与模块副本。
遮罩改为由黑淡入新画面((1-t)^3 渐透明)并限制寿命 t < 0.46。
六、第一章分镜空图
scenes.lua 第一章分镜首项写的是 story_ch1_creation,但资源表里的键是 story_ch1(只是文件路径含 creation)。DrawCinematic 在有分镜时只取 images[shots[idx]],首镜取到 nil,却仍返回 true 走电影字幕——于是 CG 空白且不进卷轴兜底。
修复:纠正分镜键;DrawCinematic 改为只在当前镜头有效时走电影形态,取不到才回退基图,两者都缺则返回 false 走卷轴。顺带去掉了"每章多载一张用不到的 7.9MiB 基图"。
七、渲染性能优化
7.1 角色图集去掉冗余放大(主要收益)
制作器原本把每格 320px 缩到 128px 后,又 nearest 放大回 320px 保存——放大只是重复像素,没有增加细节,却白占纹理解码和上传。
改为运行时图集直接保留 128 格:
图集 改前 改后
walk ×8 各 1280×2240 各 512×896
attack 1280×640 512×256
hurt / dodge 各 1280×320 各 512×128
合计 RGBA 预算 93.75 MiB 15 MiB(降 84%)
显示尺寸与足底锚点保持原 320 归一化比例,人物屏幕尺寸不变。原图不覆盖(制作器结尾校验哈希)。
7.2 清理无用预载与重复绘制
7 张旧 vfx_* 材质已被独立像素材质取代,全脚本 0 次引用,停止预载。
Vfx.Preload 加早退标志,不再每帧遍历 28 条路径。
Areas / Impacts / Cards 的去重从"每个元素扫全队列"改为只与已选中的预算内元素比较(上限 24 / 18 / 24),并倒序保留最新。
7.3 低帧率补偿
原来每帧最多补 4 个 1/120 步,只覆盖 33ms——15FPS 下模拟推进最多只有真实时间的一半,表现为"慢、跳、节奏不一致"。
改为可配置 maxCatchupSteps = 8(覆盖 15FPS),超出预算只丢弃整步余量,不再让之后几帧继续追赶。
八、移动动画修正
诊断出四个确定问题,逐一修正:
问题 根因 修复
起步约 59ms 画站姿但人已在移动 moving 用指数平滑,>0.5 才播步态 实际移动直接取步态单格
停步双腿重影 + 过渡发暗 idle/walk 双层交叉淡化,source-over 中点覆盖度仅 0.75 普通移动只画一个不透明单格
转向边界来回跳 八向量化无迟滞 加 6° 方向迟滞,急反向立即响应
停步仍有"全身回正"变形 restK 整体下移/旋转/非均匀缩放 取消,保留原足底比例
保留:dodge → hurt → attack → walk/idle 优先级、三段攻击格 1/0/4/5 且禁用烘焙光环格 3、命中停顿冻结、真实伤害与范围。
已知素材限制(不假装修好)
八张 walk 图集的格 0 都是同一正面站姿,walk_up 也不是真正背面上行。这属于既有美术限制,本轮没有重画,也不能靠代码修好。
九、攻击特效节奏放慢
原刀光只显示 115ms(三段)/ 140ms(第三段),玩家反馈"太快看不清"。
改为独立视觉收尾,不再随动作结束就消失:
项目 改前 改后
刀光一段/二段 115ms 300 / 320ms
刀光三段 140ms 380ms
卡牌展开 320ms 540ms
元素主反应 380ms 560ms
关键约束:伤害判定时点、范围、伤害值完全不动——50ms 命中判定与 150ms 恢复保持原样。刀光改由真实结算生成的 swordWave 驱动,动作结束后继续读世界时间收势;连击产生新主刃即替换旧收势,不叠多道月牙。
十、场景解锁:第二个通关后进不去第三个(阻塞级)
现象
“第二个完成后进不去第三个”。
定位过程
分三层排除,避免拍脑袋:
解锁逻辑隔离复现(25 项断言)——IsUnlocked / CurrentIndex / 地图命中全部正确。
真实通关流程复现(8 项断言)——场景二清波 → 收尾对话 → 结算写档,sceneCleared 正确变成 {1,2}。
真实 UI 全链路(16 项断言)——菜单 → 地图 → 场景二 → 清波 → 收尾 → 结算 → 地图 → 场景三,全部通过。
三条都过,说明问题不在这条链。于是转去查第一场景——它走的是教程线,不是普通波次通关线。
根因
教程收尾时只调 Meta.MarkTutorial(标记教程完成),从不调用 Scenes.MarkCleared。而下一场景的解锁条件是"前一场景已通关"——第一格的通关记录永远是空的。
所以:场景二能进(教程完成会直接送进地图),场景二也能通关并写 sceneCleared=2,但缺 1,CurrentIndex 一直退回 1,场景三永久锁死。
修复
main.lua:421 — NPC 教程收尾(mapAfter)分支补写场景通关记录。
menu_view.lua:910 — 点"开始游戏"进地图时补写场景一通关,让已有旧存档自动修复,不必重打。
演示模式不写通关记录,保持原语义。
十一、验证
全部通过真实引擎(.cli/UrhoXRuntime)实测,脚本放在 screenshots/(不入包):
验证 结果
菜单稳定性(真实 root、8 次菜单动作、遮罩递减、3 条缺图路径) 194/194
移动与刀光时间线(36 格:18 攻击 + 6 闪避 + 6 移动 + 6 停步) 75/75
元素与合成时间线(18 格,火水雷/荆棘冰蒸汽/最终合成) 617/617
完整战场 + 战斗业务回归 20/20 + 487 条日志断言
场景推进诊断(解锁表) 25/25
场景通关流程复现 8/8
场景推进真实 UI 全链路 16/16
场景解锁修复 9/9 × 连续 6 次
LSP:31 个文件、0 Error(logs/lua_errors.log 确认)。
性能对照(同负载、同测试程序字节一致)
指标 基线 优化后
实际加载图片 158 151
角色图集 RGBA 预算 93.75 MiB 15 MiB
全部加载图片 RGBA 预算 176.2 MiB 91.1 MiB
角色图集加载墙钟 849ms 167ms
角色图集加载进程 CPU 396ms 66ms
稳态 Canvas 中位 / P95 2 / 4 ms 2 / 4 ms





