下载 App
开发日志 · 2026-10-05
开发日志 · 2026-10-05
项目:烬织(Ember Weaving)— 卡牌动作 roguelike
主题:表现力大修 + 卡牌重叠/连锁系统完整落地
规模:脚本 19 个文件、11,719 行;当日新增素材 151 个(116 图 / 4 音效 / 31 配音)
概览
今天的工作分两条线:
表现层:角色动画、场景背景、反应特效——把"能看"提升到"好看"。
玩法层:卡牌重叠 → 多实体组合 → 连锁 → 实体行为 → 解锁与图鉴——把"能玩"提升到"有深度"。
两条线都经历了「发现问题 → 定位根因 → 重构 → 无头实测验证」的完整闭环。
一、角色动画重构
1.1 问题现象
玩家反馈三个具体症状:
动画"帧数太低、撕裂感严重,就像几张切换的图片"。
待机时"帽子一会儿带上一会儿没带"。
移动和攻击"太没有感觉"。
1.2 根因诊断
用 PIL 做了两轮量化分析:
(a) 轮廓一致性——统计待机帧顶部 25% 区域的实心像素占比:
帧 1 2 3 4 5 6
占比 15.94% 17.59% 17.56% 16.55% 18.57% 15.35%
(b) 包围盒——待机帧宽度在 123–243px 之间剧烈波动,中心 x 从 126 漂到 152;移动帧底部基线在 219–255px 之间跳。
结论:这些"帧"根本不是同一角色的连续动作,而是各自独立的插画——待机 #2 在挥剑、#5 变成白发无帽、移动 #2 上半身被裁切。帧数再多也没用,因为动作本身不连续。
1.3 解决方案:参考图约束的逐帧生成
重做生成管线,核心是每帧都带同一张基准立绘作为 reference_images,并在 prompt 中强约束:
必须与参考图完全相同的角色设计、服装、配色、比例、机位、大小与站位,只改变姿势。
产出两套动作:
奔跑循环(6 帧):右腿前伸着地 → 重心下沉承重 → 双腿交叉最高点 → 换腿 → 下沉 → 交叉。标准跑步循环,逐帧轮廓高度一致。
挥砍序列(6 帧):举剑蓄力 → 前劈拖影 → 水平斩 → 下扫巨弧 → 收招 → 归位。完整的挥砍弧线,含紫色能量拖影。
1.4 接入方式
ANIM_SEQ.player_move = { player_run_1 .. player_run_6 } -- 14fps 循环
ANIM_SEQ.player_attack = { player_slash_1 .. player_slash_6 } -- 按出招进度整段播放
移动:math.floor(time * 14) % 6 循环。
攻击:不用全局时间循环,而是按出招进度 ap 整段播放——windup + recovery = 0.2s 内从蓄力跑到收招,保证每一下挥砍都有起承转合,而不是随机抽帧。
程序化动画降权:逐帧动作自带肢体起伏,叠加的呼吸/摆动降到 30%(bAmp = ANIM_SEQ[animSeq] and 0.3 or 1),避免两套运动打架。
1.5 附带修复:朝向反转
基准立绘本身朝左(世界 -x)。原判断 flipX = cos(facing) < -0.1 是"朝左时翻转",正好弄反——朝左时翻成朝右。
改为 flipX = cos(facing) > 0.1:只有朝右时才翻转。
二、场景背景铺满
问题
背景图拉伸变形,玩家描述"根本就没铺匀"。
根因
nvgImagePatternTinted(ctx, ox, oy, ex, ey, angle, image, color) 的第 4、5 个参数 ex, ey 是平铺尺寸(pattern tile size),不是目标框宽高。
之前的代码把目标框宽高直接传进去:
nvgImagePatternTinted(ctx, bx, by, bw, bh, 0, bg, ...) -- bw≈屏幕宽, bh≈战斗区高
战斗区宽高比约 2.2:1,而图片是 1280×720(16:9)——图片被强行拉伸到战斗区比例,于是变形。
修复
改用 cover 等比铺满:统一缩放取两轴最大值,保证覆盖整框且不变形,多余部分裁切。
lua
复制
local scale = math.max(bw / imgW, bh / imgH)
local tw, th = imgW * scale, imgH * scale
local ox, oy = bx + (bw - tw) / 2, by + (bh - th) / 2
nvgImagePatternTinted(ctx, ox, oy, tw, th, 0, bg, ...)
三、卡牌重叠系统(核心模块)
3.1 架构:新增 scripts/overlap.lua(616 行)
设计为纯数据 + 纯逻辑,不依赖战斗/渲染,实体工厂与效果回调由调用方注入——因此可以用无头运行时独立验证。
3.2 卡牌数据:30 张
类别 数量 内容
基础牌 6 火种、水滴、种子、符文、风铃、灰烬
进阶牌 12 蒸汽、植物、荆棘、藤蔓、深潭、烈焰、藻类、余烬、沃土、风暴、混沌诱饵、混沌锁链
特殊牌 12 混沌转化、编织者之刃、结晶共鸣、混沌亲和、法则冲击、混沌漩涡、火球、冰锥、雷霆、治疗、战吼、护盾
按出牌类型:放置 16 / 直接 6 / 增强 5 / 控制 3。每张含 id / name / cost / entity_type / tag / description。
3.3 双实体规则:69 条(按卡牌 key 逐对定义)
系 条数
火系 14
水系 14
种子系 10
符文系 10
植物系 9
风系 7
灰烬系 4
蒸汽系 1
关键设计:keep(存活方)语义。规格里大量规则是"一方消失、另一方保留并获增益":
lua
复制
P("water", "seed", { keep = "种子", grow = 1 }) -- 种子生长1级,水消失
P("plant", "rune", { keep = "植物", damageBonus = 1, rangeBonus = 1 })
P("seed", "wind", { keep = "种子", push = true }) -- 种子被吹到远处
3.4 多实体规则:28 条
层级 条数 代表
三实体 12 火+水+种子 → 种子+2级+湿润;符文+火+风 → 伤害+3范围+2
四实体 5 火+植物+灰烬+符文 → 烈焰植物(附带火焰)
五实体 2 火+水+种子+灰烬+符文 → 古树+湿润+肥沃+范围+2
六实体 2 七实体 → 世界树(清空全场 + 20点全场伤害 + 闪白1秒 + 慢动作1秒)
同标签三连 7 火火火 → 6点范围伤害;种子种子种子 → 古树血量翻倍
另有 33 条标签兜底,保证任意两实体都有反应。
3.5 结果实体:47 种
蒸汽、森林、创世、荆棘墙、大蒸汽、深渊、世界树…… 每个结果映射到一种行为(fire/water/seed/plant/rune/wind/ash/steam)复用现有行为与渲染。
3.6 效果字段(17 个)
result 结果实体 key keep 存活方标签
damage/radius 范围伤害 wet/wetTime 湿润区域
slow 减速比例 heal 恢复血量
grow/growRate 立即生长/生长速度 damageBonus 伤害加成
rangeBonus 范围加成 push/spread 推动/扩散
pushChaos 可推动混沌 double 增强翻倍
fertile 肥沃区域 global 全场效果
clearAll 清空全场 hpDouble 血量翻倍
whiteFlash/slow 闪白/慢动作 big 强视觉
3.7 放置规则改造
放开重叠放置:Entities.CanPlace 从"拒绝重叠"改为"只判越界"。
放置瞬间结算:移除原来每 0.5 秒遍历检测的涌现逻辑(旧代码会在放置后延迟一拍才反应,且可能重复触发)。
递归连锁:结果实体与周围重叠实体继续组合,最多 8 层。
3.8 累积增益接入行为
keep 存活方获得的增益(comboDamage / comboRange / comboGrowRate / comboPushChaos / comboFire)折进 World.Mods,火焰伤害、植物缠绕、种子生长、风推混沌等行为自动生效。
四、连锁系统
4.1 倍率表
层 1 2 3 4 5 6 7 8
倍率 1× 1.5× 2× 3× 5× 8× 12× 20×
4.2 逐层视觉分级
层 视觉
1 白光
2 白光 + 轻微震屏
3 白光 + 震屏
4 金光 + 震屏
5 金光 + 强震屏 + 边缘泛红
6 红光 + 强震屏 + 边缘泛红
7 红光 + 全屏震动 + 慢动作 0.3s
8 白光爆闪 + 全屏震动 + 慢动作 0.5s + 屏幕闪白
震屏强度逐层递增(2 → 14),光晕颜色随层数白→金→红。
4.3 音效
每层触发 sfx_chain_trigger,音调随层数升高(音量 0.4 → 0.96)。
5 层以上叠加 sfx_chain_burst(新生成:爆发音)。
8 层叠加 sfx_chain_creation(新生成:创世音,宏大合唱 + 上升音阶)。
4.4 层数显示
屏幕中央上方,层数越高字号越大(17 → 40)、光晕越亮、颜色分级(1-3 白 / 4-5 金 / 6+ 红),带弹出缩放。
4.5 修复的 bug
7 实体整体匹配到"世界树"后,Resolve 仍走了"取一对"分支——旧代码写死 #current == 2 才整体消耗,导致深度 6 后结果变成蒸汽、世界树没生成。
改为整体匹配即全部消耗。
五、反应特效素材
5.1 从 8 类到 26 张的细分过程
批次 数量 内容
第一批 8 按标签家族:蒸汽/火/水/生长/植物/风/符文/土
第二批 8 按规模形态:火苗vs爆炸、涟漪vs漩涡、泥浆、藤蔓、森林、荆棘
第三批 8 小/大蒸汽、小/大符文、小/大风暴、治疗、沃土
第四批 8 种子发芽/共生/飞播、植物繁殖/播种、增益、浪涌、灼烧
清理 −6 删除被细分版取代的旧图
最终 26 张独立序列帧贴图(512×512,2×2 四帧)。
5.2 图集取帧
新增 DrawFxFrame:从 2×2 图集取第 N 帧,整图缩放到 2×size、平移使目标格中心落在原点。索引 1-4 → 左上/右上/左下/右下(逐格核对过)。
5.3 尺寸分级
每个特效有独立基础倍率:爆炸 ×1.35、森林 ×1.3、大蒸汽 ×1.25、漩涡 ×1.15;火苗 ×0.8、涟漪 ×0.85、小旋风 ×0.85。
5.4 文字反馈(三层)
反应名称飘字:位置上方浮现,上浮 + 淡出;强反应金色 21px + 光晕。
强反应中央横幅:森林/风暴/创世等弹出大号金字。
连锁 HUD:见 4.4。
六、实体行为系统
6.1 逐条对照与修正
行为 规格 原实现 修正
火焰 1.5 格 / 2 点 ✅ 一致 —
水珠 湿润 1 格 / 3 秒 ✅ 一致 —
种子 湿润 +0.5、灰烬 +1、到 3 变植物 ✅ 一致 —
符文 增强重叠实体 ✅ 一致 —
风铃 推 1 格内种子、随机 1 格 ✅ 一致 —
灰烬 肥沃 1 格 / 生长 +100% ✅ 一致 —
植物 1 点伤害/秒








