下载 App

《刀光剑影》开发者日志

10/201 浏览综合
修稳定性和 BGM,再补上"上瘾"缺的那两块 本期前半段是用户点名的「开始修稳定性和 BGM」,后半段是用户看完手感后说"游戏没有令人上瘾的点",于是补了连击回报和卡池定向。
本期进展
  1. 剑气粒子:从"每帧重建"改成"固定缓冲原位更新"
SparkMote 原来是每帧 BeginGeometry + 逐粒子逐角 DefineVertex/Normal/Color/UV/Tangent + Commit。900 个粒子 × 6 顶点 = 5400 次顶点构造,每次都在产生临时对象。
改成只分配一次:
lua
复制 geom_:DefineGeometry(0, TRIANGLE_LIST, ACTIVE_LIMIT6, true, true, true, true) for i = 1, ACTIVE_LIMIT6 do local vertex = geom_:GetVertex(0, i-1) -- UV / 法线 / 切线全部预设,每帧只写 position 和 color end 关键是 hasTangents=true 显式声明"我自己填"——这样 Commit() 不会去补算切线,而 GetVertex 拿到的引用在缓冲不重建时始终有效。粒子死亡后多出来的槽位收缩为零面积并透明,不逐帧改拓扑。
固定 1200 帧同场景对比:
模块 改前 max 改后 max particles 156.1ms 3.6ms particles p95 4.0ms 2.1ms 2. 角色蒙皮:去掉每帧 492 次无用字符串拼接
这一条是分模块采样才挖出来的。CompatibleSkin 的矩阵编码用了 assert(cond, "提示" .. value)——Lua 的 assert 在条件成立时也会先把消息表达式求值出来。41 根骨骼 × 12 个值 = 每帧 492 次字符串拼接,全部立刻丢弃。
改成条件判断后再拼字符串(只在真的越界时才构造消息):
lua
复制 if not (value >= -2.0 and value <= 2.0) then error("兼容蒙皮矩阵超出编码范围: " .. value) end 同时把矩阵的临时 table 换成复用的 self.matrixValues。效果:
改前 改后 skin p95 3.33ms 1.88ms skin max 158.9ms 28.5ms 改完这一项后,1200 帧压力测试首次通过 100ms 尖峰门槛(最大 79.8ms)。
  1. 场景重开崩溃:命中停顿攥着已释放的场景
Stop → Start 回归暴露了一个真 bug:
ERROR: invalid 'self' in accessing variable 'timeScale' in metamethod 'newindex' [string "core/HitStop"]:57: in field 'Reset' HitStop 模块持有 scene_ 引用,main.Stop() 里只调了 Reset()(它会去写 scene_.timeScale),而 scene_:Dispose() 紧跟其后。下一次 StrokeCombat.Init 再 Reset 时,访问的就是已经释放的场景。
修法是让 HitStop 真正放手:
lua
复制 function HitStop.Shutdown() HitStop.Reset() scene_ = nil end main.Stop() 改调 Shutdown()。顺手把 Audio.Shutdown 提到 scene_:Dispose() 之前——原来音频节点归属场景,却晚于场景释放,顺序是反的。
  1. BGM:Windows 走离线 PCM,并确认音频时钟真的在走
针对"Windows 上任何操作都打断 BGM",先做了只读排查:代码里没有任何操作触发的重播/停止路径,playCount 全程为 1。所以问题不在调用侧。
改成 Windows 优先播放离线解码的 PCM(其他平台仍用体积小的 OGG):
bgm_battle.ogg → bgm_battle_pcm.wav pcm_s16le / 48000Hz / 立体声 / 152.4 秒 / 29.3MB 目的是把播放期的 Vorbis 解码负担彻底去掉。代价是新增约 28MiB 资源,首次冷启动需要下载。
另外补了低频诊断(每秒一次,只读不补救):记录播放位置推进量、audio.playing、是否被暂停、实际增益。测试用真实混音时钟(SDL_AUDIODRIVER=dummy,不加 -nosound)跑通:
[AudioManager] 缓冲申请=100ms 返回=true 设备就绪=true mixRate=44100 [AudioManager] BGM开始循环:bgm_battle_pcm.wav compressed=false Hz=48000 [AudioValidation] 通过 advance=11.98s loops=2 playCount=1 stalls=0 gain=0.85 时钟累计推进约 12 秒、两次曲尾接缝、起播始终 1 次、实际增益 0.85。
边界必须说清:这证明了 PCM 路径、播放时钟与循环接缝在 Linux 虚拟音频设备下正常,不等于 Windows 实机不再断续。诊断是"记录"不是"补救"——没有加任何 Play/Seek/Resume 去掩盖问题。
  1. 用户点破:“游戏没有令人上瘾的点”
用户这句话说得准。翻了一遍代码,问题不在数值平衡,而在变强的随机性和反馈这两件事基本没有:
23 张卡里 18 张是纯数值乘法,改变打法的只有 5 张 抽卡是纯均匀随机,突破卡的前置永远凑不齐 跨局只存 runs/wins/bestStage/bestKills 四个数字,死了什么都不留 连击计数一直在跑,但 HUD 只显示"12 连击",没有任何回报 先做"连击有回报"——它改动最小(计数本来就有),却直接补上"每一刀都在积累、随时可能爆"的即时反馈。
  1. 连击回报:3 秒窗口内每层 +8%,封顶 10 层
lua
复制 -- 层数取"本次命中之前"的计数,所以第一刀永远是基础伤害 local combo = math.min(comboCount_, profile_.comboCap) local damage = wave.damage * damageScale * (1 + profile_.comboStep * combo) * (critical and wave.critMul or 1) 层数用"命中之前"的值,带来一个不错的手感:同一次斩击打中多个目标时,伤害逐段递增。
HUD 从"12 连击"改成"10 连击 · 伤害 +80%",让回报可见。
  1. 伤害飘字
战斗侧只上报,HUD 负责投影和生命周期:
lua
复制 StrokeCombat.SetDamageCallback(function(pos, amount, critical, killed, combo) HUD.PushDamage(pos, amount, critical, killed, combo) end) 几个实现取舍:
显示实际扣血,不是理论伤害——溢出伤害不显示,避免后期"一刀几万"的失真数字 世界坐标在渲染阶段投影,和敌人血条共用同一套坐标来源,这样相机震动后位置仍然对齐 固定 20 条容量池,满了覆盖最旧的一条,战斗中不分配新对象 数字随连击层数变大(最多 +45%),暴击橙色、击杀金色、普通白色 四向描边——第一版只有单向右下偏移,在亮草地上读不清,改成四向后才够 !连击与伤害飘字
上图:真实渲染。左上角「10 连击 · 伤害 +80%」是连击回报,怪物头顶的白色数字是伤害飘字。
  1. 护盾显示
HeroLife.GetShield() 一直存在但没人调用,护盾值对玩家完全不可见。血条下方补了一行,只在有护盾时出现,不常驻占位。
  1. 卡池:29 张卡 + 按流派定向抽卡
新增连击流派一整套:
卡 效果 连击 · 疾影 连击上限 +4 层(可叠 3 次) 连击 · 刻痕 每层加成 +4%(可叠 3 次) 连击 · 缠丝 连击窗口 +0.6 秒(可叠 2 次) 杀气 击杀把连击窗口补满,连杀不断档 起手式 连击断档后第一刀必定暴击 突破 · 百炼成锋 疾影+刻痕前置:上限 +6 层,每层再 +3% 更重要的是抽卡从均匀随机改成按已投入流派加权:
lua
复制 -- 已投入的流派更容易再抽到;未投入的权重恒为 1,不会被完全排挤 local weight = 1 + TAG_SYNERGY * (invested[d.tag] or 0) 这解决了"突破卡永远凑不齐"的老问题——你选了连击卡,后续就更容易再遇到连击卡,构筑能真正收口。给全部 29 张卡都打了流派标签。
问题与风险 ① 一个自己的初始化 bug:飘字永远不显示
飘字槽用 age == 0 当"空槽"标记,老化循环只推进 age > 0 的条目。但新飘字也是从 age = 0 起步——它会被老化循环跳过,永远停在 0,永远不满足渲染条件。
改成从 1e-4 起步:
lua
复制 -- age==0 是"空槽"标记,老化循环只推进 age>0 的条目 slot.amount, slot.critical, slot.killed, slot.combo, slot.age = amount, critical, killed, combo, 1e-4 这个 bug 单看代码不容易发现,是真实渲染截图才暴露的——逻辑断言全绿,画面上一片空白。
② 飘字回调的容错
第一版写成"回调抛错就永久置 nil"。这太脆:一次偶发异常会永久关掉飘字。改成连续失败 3 次才停用,成功一次就重置计数。
③ 稳定性和"完全不卡"不是一回事
粒子 P95 从 4.0ms 降到 2.1ms、蒙皮 P95 从 3.33ms 降到 1.88ms,1200 帧压力测试过了 100ms 门槛——但整帧仍有几十毫秒的峰值,另一轮音频压力测试里还出现过 152ms 的尖峰。不能把这些数字说成"稳定 60 帧"。
④ 连击让整体输出明显变强
这是本期最需要实机确认的改动。封顶 +80% 叠加在原有成长之上,前期压力会明显下降。如果试玩觉得太轻松,调的方向是基础伤害或每层加成,不是砍掉连击本身。
⑤ 沙箱帧时告警不是玩法错误
回归报告里 engine_errors 不为 0,全部是软件渲染的帧时告警(沙箱 GLES 每帧秒级)。Lua 错误和资源错误都是 0,5728 条玩法断言全部通过。
下期计划 Windows 实机试听:确认 PCM 路径下 BGM 是否还会断续(这是唯一还没在真实平台验证过的项) 确认 PCM 约 28MiB 的冷启动下载代价是否可接受;若不可接受,考虑预下载组或改用更小的解码格式 实机试玩新连击曲线,重点看前两幕是否偏轻 跨局解锁(死了留点什么)——"上瘾"的另一半还没做 把 assets/image/devlog/ 从构建打包范围排除(已欠三期,当前 91MB / 41 个文件仍打进包)#TapTap聚光灯21天游戏创作挑战 #聚光灯gamejam开发者日志 #今天游戏圈发生了啥 #发现好游戏 #浅评一下 #游戏日常 #欧皇附体