原动画、动效与时间契约

研究日期:2026-10-09。本文只解析本地原包、原 Laya 引擎和静态解码源码;动画时长是资源声明或代码参数,不是录像测得的耗时。 原件复用应同时保留图像、atlas 帧顺序、pivot、骨骼、关键帧、时间源、Tween 和调用循环参数,不能只保留最终静态截图。

可复核的资源清单

原 effect 子包已在原首局实际加载后补提取;当前目录 res/effect 包含全部 8 个 atlas 与各自 PNG。它们之前未缓存的缺口已经补齐;是否每种模式动态加载到位仍需运行验证。

源码证据:原 artifacts/original/unpacked/game.js 的 libs/min/laya.core.min.js、libs/min/laya.ani.min.js define 模块;方便阅读的静态格式化副本 laya.core.module.formatted.js、laya.ani.module.formatted.js。游戏调用证据均来自 bundle.decoded.js 的 js/bundle.js,字符偏移基于该解码文件;格式化副本 可按方法名搜索。

.ani timeline 的帧语义

引擎 _setUp 把 interval 设置为 1000/frameRate(core 30059);_calculateDatas 扫描最高关键帧 index 后再加 1(30125–30140),_createGraphicData 逐帧建立 Graphics(32639–32661)。因此 index=15 的资源包含 0…15 共 16 帧;默认循环周期为 16/24秒,不是 15/24秒。最后一个关键帧到达时间仍为 15/24秒,这两个量必须区分。

JSON 的 action:0 不能单独证明循环与否。循环取决于调用 play(start,loop,name);Laya Animation.play 默认 loop=true(core 33105–33122),游戏可显式传 false,在 COMPLETE 后回收对象。资源的 tween:true/false、tweenMethod、alpha/scale/rotation/pivot 和 skin 换帧共同定义视觉。静态索引列出全部通道,以原 JSON 为准。

原资源(均在 gameAssets/asset/ 下) 动画名 最高 index 帧数 24 fps 名义周期
EffectAni/AddHpSkillAnim.ani ani1 15 16 666.667 ms
EffectAni/DarkSilkwormAni.ani move 10 11 458.333 ms
EffectAni/DarkSilkwormAni.ani attack 10 11 458.333 ms
EffectAni/DustbinFull.ani ani1 6 7 291.667 ms
EffectAni/VirtualShadowAnim.ani old 10 11 458.333 ms
EffectAni/VirtualShadowAnim.ani new 10 11 458.333 ms
EffectAni/boxgodeffect.ani ani1 17 18 750.000 ms
EffectAni/princessmirrorEffect.ani ani1 135 136 5666.667 ms
Prefabs/BuffUI.json ani1 80 81 3375.000 ms
devil1.ani ani1 8 9 375.000 ms
diceAni.ani ani1 15 16 666.667 ms
diceAni2.ani ani1 15 16 666.667 ms
diceAni3.ani ani1 15 16 666.667 ms
diceAni4.ani ani1 15 16 666.667 ms
haloStar.ani ani1 10 11 458.333 ms
meowAni.ani ani1 15 16 666.667 ms
speed.ani ani1 4 5 208.333 ms
stcAnim.ani dd 24 25 1041.667 ms
stcAnim.ani open 26 27 1125.000 ms
stcAnim.ani openLoop 2 3 125.000 ms
stcAnim.ani people 12 13 541.667 ms
tcAnim.ani dd 24 25 1041.667 ms
tcAnim.ani open 26 27 1125.000 ms
tcAnim.ani openLoop 26 27 1125.000 ms
trail.ani ani1 57 58 2416.667 ms

例如 speed.ani 在 index0/2/4 切换皮肤,pivot=(81,199),不能把它等同于匀速缩放。trail.ani 对多个 GraphicNode 进行位置/透明度变化;princessmirrorEffect.ani index135 是长达 136 帧的时间线。原引擎可直接解析这些 JSON,无需再造 CSS keyframes。

骨骼 .sk:名称和时长可静态读取

所有 42 个文件头是 LAYAANIMATION:1.7.0,动画类 DragonBone。原引擎 _onAsynLoaded 对除 LAYAANIMATION:02 外的版本调用 Parser01(ani 747–755)。Parser01(31–151)依次读取 UTF 版本/类/字符串表,animationCount、publicDataOffset、extensionOffset、useParent;每个动画读 name 索引、Float32 playTime、nodeCount,随后按 publicData 的 keyframeWidth 跳过各轨道数据。报告工具完全按此布局读取;不把从二进制中随便找到的字符串当作动画名。

getAniDuration 返回 playTime(ani 770–772);Skeleton 内也用 duration/1000 参与时间换算(3474)。所以本清单的单位是毫秒。骨骼采样/缓存频率、节点插值和状态中 playbackRate 仍由引擎和调用方决定,文件 playTime 不是最终可见动作耗时。

文件 动画及原 playTime
Skeleton/player/player_0.sk attack 1066.667;idle 2000;walk 1300;walk5 833.333 ms
Skeleton/player/player_1.sk attack 1000;idle/idle2/idle3 2000;walk/walk2 1233.333;walk5 766.667 ms
Skeleton/player/player_2.sk attack 1000;idle/idle3 2000;walk 1466.667;walk3 1466.7;walk5 566.667 ms
Skeleton/player/player_3.sk attack 2000;idle 2733.333;walk/walk3 1133.333;walk5 833.333 ms
Skeleton/player/player_4.sk attack 1333.333;idle/idle2 2000;idle3/idle4 1000;walk 2000;walk5 833.333 ms
Skeleton/player/player_5.sk attack 1000;idle/idle4 2033.333;idle3 2000;walk 2166.667;walk5 833.333 ms
Skeleton/StrongerTroll/mg_0.sk daiji/gongji/zoulu 各 2000 ms
Skeleton/StrongerTroll/mg_1.sk animation 0;gongji/idle/walk 各 2000 ms
Skeleton/StrongerTroll/mg_3.sk daiji 2000;gongji/walk 1000 ms
Skeleton/StrongerTroll/mg_4.sk daiji/zoulu 2000;gongji 3000 ms
Skeleton/StrongerTroll/mg_6.sk daji/gongji/zoulu 900;shouji 100;siwang 500 ms
Skeleton/muscleBadger/mihuan.sk gongji 433.333;gongji2 333.333;pao 666.667;shuizhao 1333.333 ms
Skeleton/Compound/dustbinOut.sk animation 200;animation2 366.667 ms
Skeleton/combatDoor/skeleton.sk animation 1900;smbf 2033.333 ms

来源为表列原 .sk,精确值及其轨道在 skeleton-animation-index.json。res/mg/mg_*.sk 与 res/Skeleton/StrongerTroll/mg_*.sk 是不同资源,例如 res/mg/mg_0 多有 zouluzhuqingting、zuoshanxian。不能按相同 mg_0 文件名互换;mounts 的 idle_0…5/walk_0…5 也对应各角色坐骑姿态。

明确的速率覆盖:玩家速度技能调用 _player.playbackRate(5),恢复调用 rate(1)(bundle 39476、39019);坐骑展示存在 rate(3)(347256)。某些怪物攻击 play(1,false) 并由逻辑计时决定再次出手(解码字符873800附近)。因此 2秒骨骼片段不代表2秒攻击间隔。

atlas 逐帧效果

Hp.vgg 创建 Laya.Animation、loadAtlas,对应路径 gameAssets/asset/res/effect/{name}.atlas;效果第一次加载后计算 getGraphicBounds,按 bounds/2 设置 pivot。Hp.ncm 将效果加到场景,普通 zOrder=pos.y;ahu 标记置 Number.MAX_VALUE。暴气 trollBaoqiEffect 缩放 2.67,并把目标位置减去 (80,80)(解码字符191030)。这些定位/层级/缩放是原视觉契约。

Laya 默认 animationInterval=50ms(core 行16),Hp 没有修改 interval,所以未被其它调用修改的这些新实例是 20 fps,和 .ani 的24 fps不同。

效果 原帧数 默认名义播放时长
bulletEffect 8 400 ms
trollAttackEffect 3 150 ms
trollBaoqiEffect 12 600 ms
trollZH 7 350 ms
buildDownEffect 7 350 ms
build_vertigo 5 250 ms
flowerAttack 5 250 ms
bomb 6 300 ms

Hp 默认 loop=0,调用 play(0,false),COMPLETE 后回收到对象池并 removeChild/offAll;非0 loop 参数走 play(0,true) 并在指定 loop 毫秒后移除停止(字符191030–192100)。它不是 JSON timeline 的循环标志。原 atlas 也保留裁切 frame/spriteSourceSize/sourceSize;渲染应让 Laya 执行 atlas,不能将透明裁切帧拉伸成相同 CSS 方框。

按钮与数值反馈 Tween

来源:bundle CM 的 ahk/ftt/xtl/gua/cxe/gzp,解码字符314604、320434、328468、302262、325606、306848;所有时长为传给 Laya.Tween 的毫秒参数。

交互 原参数 校准意义
按下按钮 ftt scaleX/scaleY→原scale×0.9,50ms 点击反馈不能只切换贴图
松开/移出 xtl/gua scaleX/scaleY→原scale,50ms 移出恢复也要执行;以绑定时原scale为基准
对象 cxe 弹跳 1→1+i,50ms;→1−2i,50ms;→1,50ms 总150ms,幅度由调用参数决定,常见炮台i=.2
Show visible=true,scale→1,200ms 显示时的展开反馈
Alpha alpha→目标,1000×传入秒数 是可参数化工具,不应固定所有对象1秒
+金币 gzp 初始alpha/scale=0;500ms→alpha1/scale e、上浮15;1000ms再上浮45;1000ms上浮45且淡出缩为0 连续2500ms,结束回对象池;持续收入可叠多条浮动数字
普通炮台枪口 udi alpha→1 20ms,→0 20ms,回收 总40ms,5秒抽帧看不到它并不代表没有

gzp 在UI文案显示前把64变65,也保留原浮字白色、+符号、层级和生成位置。所谓「升级动效」并非只有一个资源:扣款/换等级贴图、cxe、buildDownEffect、声音和菜单变化可以共同发生;应以原调用链为准。

炮台、炮弹与受击节奏

普通炮台一条代码路径(解码字符431439–445000,格式化34266–34680)初始化 uzd=1、yyp=0,但 uzd getter 是 1/(zpf+1+ulj),所以该普通路径基础出手检查间隔为 0.5秒。计时 Lx.rvy=mxj/1000,ubs(t,interval) 判断 interval<=rvy−t(字符245127、246672),单位是秒。高级/特殊炮台有不同 getter:基础 1/(zpf+yyp),rapid+3、morerapid+3或5、angel+1.5、crazy+5(字符953157),不可把全部炮台统一写成0.5秒。

普通炮台 attack→nad对象池→rfl 初始化90×90炮弹、pivot=(45,45)、scale=.2、ytl=500;jsx 先对准目标,再 G_.pqq(node,target,180,ytl),并 afj(node,1,1,.35)(字符437766、439101)。高级炮台另一条 rfl 设 ytl=900,cannon尺寸135、giantcannon171(952270)。

速度语义已查明: pqq 第3参保存 pvl(转向速度),第4参保存 suc(位移速度);G_.pgu 每更新用 suc/60 位移、pvl/60 转角(2334641)。普通弹500→每更新约8.333地图单位,高级弹900→15;180是转向,不是弹速。位移按 abs(dx)/(abs(dx)+abs(dy)) 与 y 分量分配,使用曼哈顿归一化;不能换成欧氏归一化而声称同轨迹。靠近目标两轴都<5时隐藏,击中检查另用平方距离<400,即20单位范围(普通炮台 onUpdate 字符435000附近)。

该移动器按 /60 的更新步长工作,最终每秒速度还依赖原更新调度。网页省电/后台降帧后不能自动声称500单位/实际秒保持不变;直接复用原更新器能保留其行为,若做dt修正会改变原契约,应单独证明。

门、怪物血条与离场

BuildBloodStrip.json 根90×20、pivot=(45,10);槽UI/HP.png为90×20、sizeGrid=3,9,6,7;内容UI/BuildHP.png为86×16、x2/y11、pivotY10、sizeGrid=2,10,2,10。门 yuj setter直接设置内容 scaleX=生命比例(字符560300–561450)。按门旋转0/90/180/270度,血条分别放(45,10)/(10,45)/(45,80)/(80,45),血条反转自身rotation保持水平;这条路径没有补间掉血动画。

TrollBloodStrip.json 同尺寸但红色TrollHP,有LV.标签、font20、描边1。怪物 yuj setter即时改实际 nlk 红条到比例,同时把 mah追随层 scaleX Tween到该比例,800ms(字符656500–658150);这不是门的反馈。原怪物方向翻转和血条层级也要保留。

标准怪物逻辑默认低血逃跑阈值 ydr=.3;初始化会从配置读取 zac,原配置ydr=[.3,.3,.3],重新选择/逃跑概率zde=[.5,.4,.3](字符870300、887946、2405100)。攻击中每 hfe 秒考虑改目标/逃跑;普通默认level<5时10秒、>=5时20秒,阶梯难度可覆盖 escapeAfterLev/escapeThinkingTime(字符895500附近)。gjt 在未逃跑且 hp<=ydr 调 arb;arb设置escape状态,寻路到guv位置,切移动/飞行动画(893954)。

逃到目标 btk开始回血阶段;sjx每1秒调用ghr,Normal按addHpSpeed回,Plot地图11每次+.2×最大血,其余+.1×最大血,满血后清逃跑标记、重新找目标(873439、888480)。Normal的addHpSpeed需要继续追配置才能给精确固定值;不能把所有模式写成「每秒10%」。

原骨骼mg_6带shouji100ms和siwang500ms,但这里只证明资源片段存在,尚未证明当前模式在每次受击/离场都播放这两段。不能把资源名存在写成实机必然触发。

录像时间边界与验收

当前 01-navigation/index.json 有51张导航帧,第一帧源时间0、最后362.126667秒;相邻最小间隔5秒,最大跳跃116.7秒(215.371667→332.071667)。这些数值来自索引记录,说明保留了原视频时间且存在空档,不能按文件序号×5给全部帧补时间。原MOV总时长364秒由录制侧报告,本文未独立量测MOV容器。

该稀疏导航序列只适合找阶段与关键状态,不足以测40/50/150/350/800ms动效。25–67秒的10fps开场抽帧正在由主代理生成;即使10fps也无法可靠判定40ms枪口闪现。后续若做像素/节奏验收,应从高帧率原录像选择同一源时刻,不要用两个不同时相截图计算视觉误差。

可校准顺序:同设备比例下固定原舞台640设计宽 → 对齐原存档/角色/模式 → 对齐资源版本/atlas/pivot → 对齐动画名与loop/playbackRate → 对齐同一个关键帧/出手/受击时刻 → 最后对比像素。静态原件复用能保留动作轨迹与美术细节;本报告不宣称已达到所有场景的动态像素一致。

人工验收的原暂停入口

原游戏管理器 T_.cfi() 调用 im.instance.cfi(),将更新开关 uhw=false,随后设置 Z_.instance.ciu.pause=true;T_.kzg() 做对应恢复(格式化 bundle 311060、310469)。im.tnz() 只有在 uhw 为真时遍历执行注册回调(6037)。原 Lx 注册的 TimeManager、HandleOnceEvent、HandleLoopEven 全在这个入口下(9783);时间累计 mxj += Laya.timer.delta 也在注册回调 krt 中(12899)。因此原暂停会冻结模拟时钟和对应游戏逻辑,适合暂停选房倒计时后人工检查。

这并不自动暂停 Laya 自身的 Animation、骨骼、Tween、全局 timer。截图验收中仍可能看到独立视觉动效继续运行;不能把逻辑暂停声称为渲染逐帧冻结。ciu.pause setter 在 Compound 模式为真时还派发 gm.lrs 事件;Normal 模式仍应走原方法,不能只修改 _pause。

构造入口在原 js/game.js 字符 2095803,唯一出现的 new T_(Dx[s[1886]])。在 web 适配构建时将该表达式桥接为 (window.__tpfyGame=new T_(Dx[s[1886]])),即可由 P 键调用 window.__tpfyGame.cfi() / .kzg()。解码副本对应字符 2774250 的唯一 new T_(Dx["mpv"]),格式化行 358599。桥接只暴露原实例;不重建管理器,不改倒计时或经济数值。按键状态由浏览器桥接单独保存,避免反复按下触发多次切换。