跳到主要内容

FreeMinecraftModels 模型创作注意事项

本页记录了在 FreeMinecraftModels 代码库中可见的当前创作详情。内容有意保持保守:重点关注导入/运行时契约,而非每个 Blockbench 工作流偏好。

源格式

FreeMinecraftModels 目前接受:

  • .bbmodel 文件,用于可编辑的源导入
  • .fmmodel 文件,用于精简的运行时就绪模型数据

正常的导入流程是:

  1. 将模型放置在 plugins/FreeMinecraftModels/imports
  2. 运行 /fmm reload
  3. 让 FreeMinecraftModels 将模型导入到活动模型集中并重建生成的资源包

文件夹角色

plugins/FreeMinecraftModels/imports
plugins/FreeMinecraftModels/models
plugins/FreeMinecraftModels/models_disabled
  • imports 是手动模型导入和官方包下载在处理前的接收文件夹
  • models 包含已激活安装的模型内容
  • models_disabled 包含当前已禁用的已下载或已安装的包内容

较旧的安装可能存在一个首字母大写的 Models 文件夹。FreeMinecraftModels 的处理方式是:当规范的小写 models 存在时优先使用它,只有在它不存在时才回退到旧的 Models。在 Windows 和 macOS 上这两个名字本来就是同一个目录;在区分大小写的 Linux 文件系统上,旧安装仍能正常工作,但如果两个目录同时存在,只有小写的 models 会被读取。如果你发现两者都在,请合并到 models 中。

模型 ID

  • 运行时模型 ID 来自文件名,不包含 .bbmodel.fmmodel 扩展名
  • 使用稳定、唯一的文件名,因为 ID 是命令和 API 调用解析的目标
  • Blockbench 动画引用基于名称,因此模型内重复或不清晰的命名比干净、明确的命名方案更容易造成问题
  • 扩展名匹配不区分大小写,因此 .BBModel.FMModel 也会被接受

ID 规范化与冲突

文件名在成为运行时模型 ID 和资源包文件名之前会先被规范化

  1. 转为小写
  2. 每一个不属于 a-z0-9._- 的字符都会被替换为 _

因此 My Table.bbmodelmy table.bbmodelmy_table.bbmodel 全都规范化成同一个 ID:my_table

在任何转换开始之前,FreeMinecraftModels 会扫描整个 models 目录树,检查是否有文件规范化后得到相同的 ID。当它发现冲突时:

  • 每一个发生冲突的文件都会被拒绝 —— 它们都不会被加载,因此不存在悄悄的"最后一个胜出"
  • 该模型 ID 在本次加载过程的剩余阶段被封锁
  • 被拒绝的模型也会被排除在基岩版自定义实体资源包的导出之外
  • 控制台会输出:
[FMM Models] Rejected normalized model ID collision '<id>'. These files normalize to the same ID: <paths>. Rename the files so every normalized model ID is unique; no colliding model was loaded.

修复办法始终是重命名文件,使规范化后的 ID 各不相同。最稳妥的习惯是从一开始就用小写加下划线来命名模型文件(stone_table.bbmodel),这样文件名与运行时 ID 就完全一致,也彻底消除了意外冲突的可能。

Blockbench 兼容性

  • FreeMinecraftModels 会从 .bbmodel 中读取 meta.format_version,并根据其版本号分支
  • 缺少 meta 块、缺少 format_version,或者值无法解析,都会回退到版本 4,并输出一条指明该模型的信息/警告
  • 缺少 textures 数组是被接受的,会被当作空纹理列表处理,而不会让导入器崩溃。此后基岩版自定义实体导出器会跳过该模型,因为基岩版导出至少需要一张纹理
  • 提取出的纹理文件名会被统一规范为单一的 .png 扩展名。诸如 body.jpgbody.PNGbody 这样的源名称,都会以 body.png 的形式写出并被引用
  • format_version 4.x 及更早:骨骼直接从 outliner 树中读取,该树内联携带骨骼名称
  • format_version 5.x 及更新:outliner 的结构发生了变化。outliner 现在是由纯 UUID 字符串和 {uuid, isOpen, children} 字典构成的嵌套树,其中没有 name 键,而真正的骨骼数据(包括 name)保存在一个独立的扁平 groups 数组中。FMM 通过 UUID 把两者连接起来 —— 每个 outliner 节点会被替换为它在 groups 中匹配的条目,然后递归合并子节点 —— 因此骨骼名称、原点和旋转都能正常解析
  • 手工编辑或程序生成 .bbmodel 文件时值得了解的影响:
    • 如果一个 v5 文件缺少 groups 数组,它会被原样透传而不做合并,于是它的骨骼会丢失名称,保留前缀(tag_h_b_m_hitbox)也就不再被识别
    • outlinergroups 之间的 UUID 必须完全一致;没有匹配 group 的 outliner 节点会被原样保留,而不是被丢弃
    • 不要把 v5 的 format_version 与 v4 形态的 outliner 混在一起,反之亦然 —— 分支是根据声明的版本选择的,而不是根据实际结构
  • 如果导入日志显示模型格式与 FreeMinecraftModels 不兼容,请首先将其视为模型格式问题,而不是 wiki 或命令问题

运行时重要的骨骼约定

当前转换器和骨架管线识别以下命名约定:

  • hitbox
    • 保留用于碰撞箱生成
    • 应该清晰地定义模型碰撞箱,而不是用作视觉骨骼
    • 必须是 outliner 中的顶级骨骼。导入器只在根层级查找它;嵌套在其他骨骼内部的 hitbox 组会被当作普通骨骼处理
    • 必须恰好包含一个立方体,用于定义运行时的宽(x)、深(z)和高(y)。多余的立方体会记录 has more than one value defining a hitbox! Only the first cube will be used;空的 hitbox 骨骼会记录 has a hitbox bone but no hitbox cube!,并且不会生成碰撞箱
    • 永远不会被动画化(指向 hitbox 的动画轨道会被跳过)、永远不会被渲染,并被排除在基岩版几何体导出之外。在基岩版上,没有 hitbox 骨骼的模型会回退到 1.0 x 2.0
  • tag_...
  • h_...
    • 被视为头部骨骼
  • b_...
    • 非显示骨骼(运行时隐藏)。用于不应在游戏中渲染的结构性或组织性骨骼。
  • m_...
    • 骑乘点骨骼。带有此前缀的每个骨骼会在模型上创建一个可骑乘的座位位置。玩家或实体可以在运行时被挂载到这些位置上。多个 m_ 骨骼会创建多个座位。由 MountPointManager 内部管理。

这些不仅仅是风格约定;它们会影响转换和运行时行为。

名牌需要一个 tag_ 骨骼

这是"发布出去的内容里怪物没有名字"最常见的单一原因,因此值得直说:

只有当模型中包含一根名称以 tag_ 开头的骨骼时,它才会获得名牌。 没有任何兜底机制。

它的工作方式:

  • 导入时,任何名为 tag_... 的骨骼都会获得一根平行的自动生成"元"骨骼(fmm_nametag_bone_<name>),并被标记为名牌骨骼。流水线中没有其他任何环节会设置该标记
  • 运行时,骨架会收集这些被标记的骨骼,并在 tag_ 骨骼的原点处生成一个文本显示实体。它的位置和文本跟随该骨骼
  • ModeledEntity.setDisplayName(...)setDisplayNameVisible(...) 会遍历这个集合。没有 tag_ 骨骼时,该集合为空,这两个调用都会静默地什么都不做 —— 没有错误、没有警告、也没有名字

为什么原版名牌无法替你兜底:

  • 动态模型会对客户端隐藏其底层的生物实体(setVisibleByDefault(false),外加隐身标志),因此原版怪物自己的名牌同样不会被渲染
  • 最终结果就是一只完全没有名字的怪物,尽管调用方插件确实成功设置了显示名称

实用规则:

  • 如果某个模型代表一个有名字的实体(EliteMobs Boss、任务 NPC,或任何插件会对其调用 setDisplayName 的对象),请添加一根 tag_ 骨骼
  • 把它放在名牌应当悬浮的位置 —— 通常就在头部正上方
  • 该骨骼不需要有立方体;它只是一个定位锚点。尤其是 tag_name,在生成物品模型定义时会被跳过,因此它永远不会作为几何体被渲染
  • 允许存在多根 tag_ 骨骼;它们中的每一根都会得到自己的文本显示,显示同一个名字
  • 如果你在控制台看到 nametag bone did not spawn name tag,说明该骨骼已被识别但它的文本显示未能生成 —— 这与完全没有 tag_ 骨骼是两个不同的问题

自由漂浮的立方体

在 outliner 顶部声明、不属于任何组的立方体,会以纯 UUID 字符串而不是骨骼条目的形式到达。FreeMinecraftModels 会把它们挂到自动生成的根骨骼(freeminecraftmodels_autogenerated_root)上,因此它们仍会被渲染。它们无法被动画寻址,所以任何你打算做动画的东西都要放进一个真正的组里。

IK、空对象和定位器

当前代码确认支持:

  • Blockbench 空对象作为 IK 控制器
  • IK 链蓝图和运行时 IK 求解(FABRIK)
  • 定位器解析

只有当空对象在 .bbmodel 中的条目同时带有 ik_source(骨链起始的骨骼)和 ik_target(骨链要够到的骨骼或定位器)时,它才会成为 IK 控制器。lock_ik_target_rotation 也会被读取。骨链是通过从目标沿骨骼层级向上走到源来发现的,因此两者必须真正连通。

重要的实际限制:

  • 源/目标之间的关联是通过 UUID 建立的,因此重命名不会破坏它 —— 但如果任一 UUID 在模型中缺失,控制台会记录 IK chain in model <model>: Could not find source bone with UUID ... / Could not find target with UUID ...,并跳过该骨链
  • 如果沿层级向上走找不到从源到目标的路径,你会得到 Could not find path from source to target,该骨链被跳过
  • 控制器的动画查找是基于名称的,因此控制器命名需要在模型结构和动画数据之间保持稳定
  • 空对象上只有位置关键帧有意义 —— 它们会成为 IK 的目标偏移。空对象上的旋转和缩放轨道会被忽略

关于 IK 是如何逐帧被驱动的,参见动画

1.21.4+ 输出拆分

FreeMinecraftModels 声明了 api-version: 1.21.4,因此 1.21.4 是最低服务器版本,你拿到的永远是现代的物品模型定义布局:

plugins/FreeMinecraftModels/output/FreeMinecraftModels/assets/freeminecraftmodels/items

旧版(1.21.4 之前)基于皮革马铠 override 的分支在代码中依然存在,但任何能加载当前插件的服务器都不会走到那里。如果你在看旧的笔记或旧的输出文件夹,你看到的差别就是这个。

展示模型 JSON(1.21.4+)

管理员可以在 .bbmodel.fmmodel 文件旁边放置一个同名的 .json 文件(例如 table.bbmodel + table.json)。此 JSON 应从 Blockbench 导出为"Java Block/Item"模型,定义物品在手持或在物品栏中显示时的外观。

在导入时,FMM 会将此 JSON 复制到资源包输出中,并自动将其中的裸纹理引用重写为指向模型提取的纹理。如果不存在配套 JSON,该物品在游戏中将显示为普通纸张。

YML 中的自定义物品配置

配套的 .yml 配置文件(与模型同名)现在支持可选的物品字段。如果设置了 material:,模型也可作为自定义可手持物品使用。完整的 YML 格式如下:

isEnabled: true
scripts:
- my_script.lua
material: DIAMOND_SWORD # optional — if set, model is also a custom item
name: '&b&lMy Custom Sword' # optional — display name
lore: # optional
- '&7A custom weapon'
enchantments: # optional — format: ENCHANTMENT_NAME,LEVEL
- SHARPNESS,5
- FIRE_ASPECT,2

material: 存在时,模型会与道具一起显示在管理员内容浏览器中,并可作为功能性物品给予玩家。

基岩版和渲染路径注意事项

  • 基岩版支持取决于 sendCustomModelsToBedrockClientsV2(默认值为 true;替代了较旧的 sendCustomModelsToBedrockClients 键)以及周围的 Floodgate/Geyser/资源包路径
  • 受支持版本上的 Java 客户端可以在启用 useDisplayEntitiesWhenPossible 时使用展示实体渲染
  • 不要假设在 Java 客户端上显示正确的模型对您的基岩版路径自动安全

实用创作建议

  • 保持文件名稳定,因为它们会成为运行时 ID
  • 保持控制器和动画命名明确,因为 Blockbench 动画查找是基于名称的
  • 有意识地使用保留的虚拟骨骼名称(hitboxtag_h_b_m_)—— 并且记住,任何需要显示名字的模型都必须有一根 tag_ 骨骼,否则它会悄无声息地没有名字
  • 如果你希望状态动画自动触发,就把它们命名为 spawnidlewalkattackdeath;其他一切都是需要你自己触发的自定义动画(参见动画
  • 打算用于 /fmm disguise 的模型至少也应当附带 idle,还可以加上 sneakjump,这两者只有伪装控制器才会识别(参见玩家伪装
  • /fmm reload 后验证导入的输出,而不仅仅是在 Blockbench 内部
  • 验证为目标 Minecraft 版本生成的包内容,尤其是 1.21.4+

弓和弩状态模型

FMM 支持自定义弓和弩物品的自动拉弓动画状态。无需配置 -- 只需使用正确的后缀命名模型文件,FMM 在资源包生成期间会自动检测状态集。

命名约定

后缀用途
_idle手持物品,未拉弓或未装填必需必需
_draw_start刚开始拉弓必需必需
_draw_half拉到一半必需必需
_draw_full完全拉满必需必需
_charged已装填的弩(箭或火箭)--必需

需要四个模型(除 _charged 外全部)。需要全部五个。

示例文件布局

plugins/FreeMinecraftModels/imports/
cool_bow_idle.bbmodel
cool_bow_draw_start.bbmodel
cool_bow_draw_half.bbmodel
cool_bow_draw_full.bbmodel
cool_bow.yml <-- config uses the base name, not _idle

对于弩,您需要添加第五个文件 cool_bow_charged.bbmodel

检测机制

  • 检测在资源包生成时(启动时或 /fmm reload)自动进行。
  • 只有 _idle 模型在输出包中获得物品定义 JSON。拉弓和装填状态作为该定义中的条件条目引用。
  • 只有基础名称获得 YML 配置文件。对于上述示例,配置是 cool_bow.yml,而不是 cool_bow_idle.yml

展示模型 JSON

每个状态模型可以有自己的配套 .json 展示模型(例如 cool_bow_idle.jsoncool_bow_draw_full.json)。FMM 会自动将它们接入生成的物品定义中。有关生成的确切 JSON 结构,请参阅资源包输出

超出范围

本页不尝试保证:

  • 确切的 Blockbench UI 步骤
  • 艺术工作流偏好
  • 旧版本地 README 材料中描述的每个遗留 .bbmodel 特性

这些细节的变化速度比上述已验证的运行时契约更快。