☰
VCMI 动画资源格式解析:用 JSON 替换 HoMM3 .def 动画文件的方法与实现原理
2026/10/10 9:01:32 网站建设 项目流程
  • 游戏开发

【免费下载链接】vcmi

Open-source engine for Heroes of Might and Magic III

项目地址:https://gitcode.com/gh_mirrors/vc/vcmi
点击查看免费下载

本篇以 VCMI 修改者文档 Animation Format 为核心,讲解如何用.json文件替代 Heroes of Might and Magic III 的.def动画文件:包括basepath、sequences、images三类配置的完整语法、按钮/城镇/生物动画的替换实例,以及生物动画 0–51 帧组的含义。结合渲染层源码(RenderHandler.cpp、ImageLocator.cpp)可以进一步说明引擎如何解析这些 JSON 配置、如何与原始.def回退共存,以及阴影/描边自动生成的运行时机制。

为什么需要 JSON 动画格式

VCMI 允许修改者用.json文件覆盖(override)HoMM3 的.def动画文件。相比.def,JSON 格式带来了三个实际好处(出自原文档):

  • 可以单独覆盖动画中的某一帧(例如只换某个图标、某个按钮状态);
  • 支持现代图片格式——TGA、PNG 等 VCMI 图片加载器支持的所有格式;
  • 不需要任何专用工具——一个文本编辑器加图片素材即可完成修改。

从源码结构看,覆盖的判定逻辑在渲染初始化阶段完成:引擎先读取.def得到各组的原始帧数,再查找同名.json资源逐条覆盖对应帧槽位。这一过程位于 RenderHandler::getAnimationLayout:

  1. 将动画路径规范化为SPRITES/(或SPRITES2X/、SPRITES3X/、SPRITES4X/)前缀,以支持不同缩放倍率的高清资源目录;
  2. 若存在.def,按defFile->getEntries()为每组resize出与原始文件相同的帧数——这保证了未覆盖的帧仍然来自原.def;
  3. 再加载同名.json,调用 initFromJson 逐帧写入布局表。

之后查询某帧时,getLocatorForAnimationFrame 优先返回 JSON 提供的ImageLocator;如果该槽位没有被 JSON 填写(或帧索引越界),则回退到默认的defFile:group:frame定位方式。这就是"只换部分帧、其余保持原样"的实现基础。

格式说明(Format Description)

JSON 动画文件支持两个互斥的顶层配置区:sequences(整组替换)和images(单帧替换),外加一个可选的basepath前缀。原文档给出的完整格式如下:

{ // 所有图片的基础路径,可选。 // 用于避免写很长的图片路径 "basepath" : "path/to/images/directory/", // 动画中的序列/分组列表 // 会用指定的文件列表替换原动画中的对应组 // 即使原动画更长也一样 "sequences" : [ { // 组索引,从 0 开始 "group" : 1, // 该组内的文件列表 "frames" : [ "frame1.png", "frame2.png" ... ], // 如需自动为此帧生成阴影。可选,0 = 无,1 = 普通阴影,2 = 剪切阴影(如冒险地图用) "generateShadow" : 1, // 如需自动为此帧生成覆盖层。可选,0 = 无,1 = 描边 "generateOverlay" : 1, }, ... ], // "sequences" 的替代方案。允许覆盖文件中的单个帧。 // 一般不应与 "sequences" 同时使用 "images" : [ { // 所属组。可选,默认 = 0 "group" : 0, // 该组内的帧索引 "frame" : 0, // 该帧对应的文件名 "file" : "filename.png", // 如需自动为此帧生成阴影。可选,0 = 无,1 = 普通阴影,2 = 剪切阴影 "generateShadow" : 1, // 如需自动生成覆盖层。可选,0 = 无,1 = 描边 "generateOverlay" : 1, }, ... ] }

basepath:路径前缀

basepath是可选字段,所有相对图片名都会拼接在其后。解析代码位于 initFromJson:basepath = config["basepath"].String(),随后无论是sequences中的帧还是images中的file/defFile,都会执行basepath + node["file"].String()拼接。若basepath指向目录,末尾应带/(官方示例均如此书写)。

值得注意的细节:initFromJson还会把顶层的margins、width、height通过JsonUtils::inherit(toAdd, base)继承到每一帧上——也就是说,顶层可以统一声明裁剪边距和尺寸,逐帧省略。

sequences:按组整体替换

sequences数组的每一项对应一个动画组:

  • group:组索引,从 0 开始;
  • frames:该组的新帧文件列表。解析时先source[groupID].clear()清空原组,再按列表顺序填入——这会替换整组内容,即使原动画更长(原文档明确说明此语义)。

images:按帧精准覆盖

images数组的每一项指定单个帧:

  • group:组索引,可选,缺省为 0;
  • frame:组内帧索引;
  • file:新图片文件名。

源码中有一个容易忽略的健壮性处理(RenderHandler.cpp 第 186–187 行):如果目标group在当前布局里还没有那么多帧(例如.def中该组不存在或帧数更少),引擎会source[group].resize(frame+1)自动扩容后再写入。此外,images条目还支持defFile字段——指向另一个.def文件的帧,解析时会同样拼上basepath,从而允许"从别的.def里借一帧"这类混合用法。

generateShadow 与 generateOverlay:运行时生成阴影和描边

这两个可选字段控制引擎是否对该帧在运行时自动生成阴影或描边。其取值与内部枚举严格对应,定义在 ImageLocator.h:

字段取值内部枚举说明
generateShadow0ShadowMode::SHADOW_NONE不生成阴影
1ShadowMode::SHADOW_NORMAL普通投影
2ShadowMode::SHADOW_SHEAR剪切投影(冒险地图单位常用)
generateOverlay0OverlayMode::OVERLAY_NONE不生成覆盖层
1OverlayMode::OVERLAY_OUTLINE白色 1px 描边
2OverlayMode::OVERLAY_FLAG旗色覆盖层

这些值在 ImageLocator 构造函数 中被static_cast成对应枚举。实际生成发生在渲染阶段(loadScaledImage):当以阴影层模式(ONLY_SHADOW_HIDE_SELECTION/ONLY_SHADOW_HIDE_FLAG_COLOR)取图且generateShadow有效时,调用img->drawShadow(是否剪切);当以覆盖层模式(ONLY_FLAG_COLOR/ONLY_SELECTION)取图且generateOverlay == OVERLAY_OUTLINE时,调用img->drawOutline(Colors::WHITE, 1)。

从源码结构看,还有一条命名约定式的替代路径:如果未声明generateShadow/generateOverlay,渲染器会在图片路径后追加-SHADOW或-OVERLAY后缀去查找预制的阴影/覆盖层图片(RenderHandler.cpp 第 407–418 行)。即"运行时算法生成"与"手工提供后缀图"两种做法并存。

实例一:替换按钮

按钮类动画固定需要 4 个状态帧(原文档 Examples / Replacing a button 一节):

  1. Active(激活):按钮可用,玩家可以按;
  2. Pressed(按下):玩家已按下但尚未松开;
  3. Blocked(禁用):按钮被阻塞、不可交互。注意部分按钮永远不会被禁用,可以不提供这张图;
  4. Highlighted(高亮):只有部分按钮在特定情况下使用。例如主菜单中鼠标悬停在按钮上时显示高亮;又如可切换设置的开/关状态按钮。

原文档给出的示例 JSON:

{ "basepath" : "interface/MyButton", // 所有图片都位于此目录 "images" : [ {"frame" : 0, "file" : "active.png" }, {"frame" : 1, "file" : "pressed.png" }, {"frame" : 2, "file" : "blocked.png" }, {"frame" : 3, "file" : "highlighted.png" }, ] }

这正是images按帧覆盖的典型用法:组 0(默认 group)的 0–3 帧分别对应四种状态。仓库内就存在多个同结构的真实文件,例如 checkbox.json 用两张 PNG 覆盖了大厅复选框的两个状态:

{ "basepath" : "lobby/", "images" : [ { "frame" : 0, "file" : "checkboxBlueOff.png"}, { "frame" : 1, "file" : "checkboxBlueOn.png"} ] }

更多同类文件可参考 Mods/vcmi/Content/Sprites/ 目录下的deleteButton.json、dropdown.json、rangeHighlightsGreen.json等,它们演示了按钮、下拉框、高亮遮罩等不同 UI 元素的覆盖方式。

实例二:替换简单动画

对于冒险地图对象或城镇建筑这类单组循环动画,用sequences定义一组帧即可(原文档示例):

{ "basepath" : "myTown/myBuilding", // 所有图片都位于此目录 "sequences" : [ { "group" : 0, "frames" : [ "frame01.png", "frame02.png", "frame03.png", "frame04.png", "frame05.png" ... ] } ] }

由于sequences的语义是"整组替换",这里的帧列表长度无需与原.def相同——即使原动画更长,也会被这份列表完全取代。

原文档中"Replacing creature animation(替换生物动画)"一节标记为 TODO,未给出完整示例;但方法一致:按下面的帧组编号为每组提供帧列表。下面一节给出了生物动画的完整组定义。

生物动画帧组(Creature Animation Groups)

生物动画由多个组(group)构成,每组代表一个特定动作。原文档完整列出了 VCMI 使用的组编号及语义,此处完整继承:

基础动画

  • [0] Movement(移动):生物移动时使用;
  • [1] Mouse over(鼠标悬停):随机待机动作,以及鼠标移过生物时播放;
  • [2] Idle(待机):生物堆不执行动作时持续播放的基础动画;
  • [3] Hitted(受击):生物堆被打中时播放;
  • [4] Defence(防御):防御姿态下的替代受击动画,近战命中且正在防御时播放;
  • [5] Death(死亡):生物堆死亡时播放;
  • [6] Death (ranged)(死亡·远程):替代死亡动画,被远程攻击击杀时播放。

转向动画

  • [7] Turn left(左转):旋转动画的前半部分,生物转向观察者一侧;
  • [8] Turn right(右转):旋转动画的后半部分;
  • [9]VCMI 未使用,存在于 H3 原始文件中;
  • [10]VCMI 未使用,存在于 H3 原始文件中。

近战攻击动画

  • [11] Attack (up):面朝上目标的攻击动画;
  • [12] Attack (front):面朝前方目标的攻击动画;
  • [13] Attack (down):面朝下目标的攻击动画。

远程攻击动画

  • [14] Shooting (up):面朝上目标的远程攻击动画;
  • [15] Shooting (front):面朝前方目标的远程攻击动画;
  • [16] Shooting (down):面朝下目标的远程攻击动画。

特殊动画

  • [17] Special (up):当找不到专用施法或群攻动画时使用的特殊动画;
  • [18] Special (front):同上,面朝前方;
  • [19] Special (down):同上,面朝下方。

H3 附加动画

  • [20] Movement start(移动开始):移动动画开始前播放;
  • [21] Movement end(移动结束):移动动画结束后播放。

VCMI 附加动画

  • [22] Dead(已死亡):生物死亡后的静止画面。若未提供,则由 "Death" 组的最后一帧构成;
  • [23] Dead (ranged)(已死亡·远程):远程攻击致死后的画面。若未提供,由 "Death (ranged)" 组最后一帧构成;
  • [24] Resurrection(复活):生物复活时播放。若未提供,由 "Death" 动画的反转序列构成。

施法动画

  • [30] Cast (up):面朝上目标施法时;
  • [31] Cast (front):面朝前方目标施法时;
  • [32] Cast (down):面朝下目标施法时。

群体攻击动画

  • [40] Group Attack (up):生物攻击多个目标且主目标在上方时使用(如龙息、九头蛇类生物);
  • [41] Group Attack (front):主目标在前方时使用;
  • [42] Group Attack (down):主目标在下方时使用。

H3 附加动画(传送)

  • [50] Teleportation start(传送开始):单位传送时在原位置播放。若未提供,将改用 movement start 动画;
  • [51] Teleportation end(传送结束):单位传送时在目标位置播放。若未提供,将改用 movement end 动画。

覆盖的加载与回退机制(源码佐证)

理解以下几个实现细节,可以避免修改时踩坑:

  1. JSON 只覆盖、不删除。布局初始化时先按.def的getEntries()把每组resize到原始帧数(RenderHandler.cpp 第 226–233 行),JSON 再覆盖指定槽位。因此未写进 JSON 的帧自动保持原样。
  2. sequences是破坏性覆盖:对命中的组先clear()再填帧,所以用sequences重写某一组后,该组未列出的原帧不会再出现。
  3. 多来源合并:getResourcesWithName(jsonResource)会收集同名 JSON(RenderHandler.cpp 第 235–247 行),多个 Mod 层级的同名动画配置按加载器顺序叠加处理,后写者覆盖先写者。
  4. 缩放倍率目录:SPRITES2X/、SPRITES3X/、SPRITES4X/前缀用于存放 2x/3x/4x 高清资源;只有开启 HD 贴图(settings["video"]["useHdTextures"])或缩放系数为 1 时才会使用带前缀的布局(getAnimationLayout)。源码注释也明确.def只按 1x 数据使用,放大素材应使用独立图片。
  5. 帧缺失时的兜底:查询不存在的组或越界帧会返回空ImageLocator;帧定位器既无image也无defFile时,回退为ImageLocator(path, frame, group, mode),即直接按.def原始帧渲染(getLocatorForAnimationFrame)。若.def里也没有该帧,则记录错误并加载占位图DEFAULT(loadImageFromFileUncached)。
  6. SDL2 客户端行为一致:clientsdl2 渲染路径中有对称的 initFromJson 实现,同一套 JSON 语法对两种渲染后端通用。

实战检查清单

按原文档骨架 + 源码行为,制作/审查一份动画覆盖 JSON 时可依次确认:

  • 文件是否为目标.def的同名.json(放在 Mod 资源目录中,引擎按资源名匹配);
  • basepath是否以/结尾且图片实际位于该目录;
  • 只换个别帧(按钮、图标)→ 用images;整组重做(建筑、单位动作)→ 用sequences;二者一般不要混用(原文档建议);
  • 组号是否按上面的生物帧组表填写(生物动画尤其注意 30/40/50 段的施法、群攻、传送组);
  • 需要运行时阴影/描边的帧(如冒险地图单位)是否声明了generateShadow: 2(剪切)或generateOverlay: 1(描边);
  • 目标帧在原.def中不存在时是否有意为之——引擎会自动扩容帧槽,不会报错。

小结

VCMI 的动画覆盖格式用一个轻量 JSON 就打通了"旧.def资产 + 现代图片格式 + 逐帧精准替换"的 Mod 工作流:basepath管路径、sequences管整组、images管单帧、generateShadow/generateOverlay管运行时生成的阴影与描边;生物动画则以 0–51 的固定组编号规范动作语义。格式语义由 docs/modders/Animation_Format.md 定义,解析与回退逻辑可在 clientsdl3/render/RenderHandler.cpp 与 client/render/ImageLocator.cpp 中逐行验证,真实用例可参考 Mods/vcmi/Content/Sprites/ 下的各 JSON 文件。

  • 游戏开发

【免费下载链接】vcmi

Open-source engine for Heroes of Might and Magic III

项目地址:https://gitcode.com/gh_mirrors/vc/vcmi
点击查看免费下载

相关推荐

上一篇:core-js 中 ECMAScript `globalThis` 的实现原理、入口与实战用法
下一篇:wp-calypso DateRange 组件指南:从 Trigger 到 Popover 的完整日期区间选择方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询