Skip to content

自定义 3D 物品 ​

从 ModAPI 2.0 开始,资源包中的附着物(Attachable)文件可以将微软原版格式的 3D 模型绑定至自定义物品。本文将详细介绍如何将物品从 2D 贴图升级为 3D 模型,包括模型制作规范、骨骼层级要求、第一/第三人称适配、攻击动画效果,以及完整的附着物配置示例。

前置条件

请先阅读 自定义基础物品 和 自定义武器与工具。3D 物品基于 Attachable 机制实现,附着物 JSON 的 format_version 使用 1.10.0。建议使用 Blockbench 进行模型与动画编辑。

概述 ​

3D 物品通过 Attachable(附着物) 系统实现。Attachable 是一种特殊的实体定义文件,它将模型、贴图、动画等资源"附着"在玩家手上。当玩家手持具有 Attachable 定义的物品时,原本的 2D 贴图将被 3D 模型替代。

Attachable 的核心配置文件位于 resource_pack/attachables/ 目录下,其 identifier 必须与物品的 identifier 完全一致。引擎在检测到物品被手持时,会自动查找匹配的 Attachable 定义并渲染对应的 3D 模型。

Attachable 支持的功能包括:

  • 自定义 3D 模型(geometry)
  • 自定义贴图与材质(支持附魔光效)
  • 第一人称与第三人称分别设置手持姿态
  • 自定义攻击挥舞动画
  • 使用 context 变量进行条件渲染

模型制作规范 ​

3D 物品的模型必须遵循玩家骨骼层级规范,确保手持时位置正确且动画正常播放。

玩家骨骼层级 ​

玩家模型的骨骼树结构如下(仅列出与右手物品相关的部分):

root
└── waist
    └── body
        ├── rightArm
        │   └── rightItem    ← 右手物品挂载点
        └── leftArm
            └── leftItem     ← 左手物品挂载点

3D 物品的模型骨骼应放置在 rightItem 骨骼之下。rightItem 是引擎预定义的物品挂载骨骼,其 pivot 已经对齐到玩家右手的位置。

模型制作要求 ​

  1. 遵循骨骼层级:模型的根骨骼应命名为物品相关的名称(如 sword),并放置在 rightItem 骨骼下
  2. pivot 对齐:物品模型的 pivot 应设置在手握处,确保手持时物品不会偏移
  3. 朝向:模型默认面朝 Z 轴正方向,握柄朝下
  4. 尺寸参考:可以参考原版钻石剑的模型大小作为基准(约 16×16×1 像素)

注意

模型文件中不需要包含完整的玩家骨骼层级(root/waist/body 等),只需要包含 rightItem 骨骼和物品自身的骨骼即可。完整层级由引擎在运行时自动拼接。

校正第三人称手持位置 ​

在 Blockbench 中制作模型后,建议将模型导入到玩家骨架中检查第三人称手持效果。

操作步骤 ​

  1. 在 Blockbench 中打开一个 基岩版实体模型 工程
  2. 导入官方玩家模型(或使用 Blockbench 内置的 Steve 模型)作为参考
  3. 在骨骼树中找到 rightArm → rightItem 骨骼
  4. 将你的物品模型骨骼作为 rightItem 的子骨骼添加进去
  5. 调整物品骨骼的 pivot 和 position,直到手持效果满意

调整要点 ​

  • 位置偏移:通过调整物品骨骼在 rightItem 下的 position 值来修正物品在手中的位置
  • 旋转校正:如果物品朝向不对,调整物品骨骼的 rotation 值
  • 导出时:最终导出的模型只需包含 rightItem 及其子骨骼,不需要包含玩家的其他骨骼

TIP

Blockbench 的实时预览功能可以直接看到手持效果。建议配合玩家模型进行调整,避免反复进入游戏测试。

Attachable 配置详解 ​

Attachable 的配置文件位于 resource_pack/attachables/ 目录下,文件名建议与物品 identifier 对应。

完整配置结构 ​

json
{
  "format_version": "1.10.0",
  "minecraft:attachable": {
    "description": {
      "identifier": "mymod:custom_sword",
      "materials": {
        "default": "entity_alphatest",
        "enchanted": "entity_alphatest_glint"
      },
      "textures": {
        "default": "textures/items/custom_sword_3d",
        "enchanted": "textures/misc/enchanted_item_glint"
      },
      "geometry": {
        "default": "geometry.custom_sword"
      },
      "animations": {
        "first_hold": "animation.custom_sword.first_hold",
        "first_scale": "animation.custom_sword.first_scale",
        "third_hold": "animation.custom_sword.third_hold"
      },
      "scripts": {
        "animate": [
          {
            "first_hold": "c.is_first_person"
          },
          {
            "first_scale": "c.is_first_person"
          },
          {
            "third_hold": "!c.is_first_person"
          }
        ]
      },
      "render_controllers": [
        "controller.render.item_default"
      ]
    }
  }
}

各字段说明 ​

identifier ​

必须与物品的 identifier 完全一致。引擎通过 identifier 将物品与 Attachable 自动关联。

materials ​

定义渲染使用的材质。常用材质如下:

材质名说明
entity_alphatest基础材质,支持透明区域裁剪
entity_alphatest_glint带附魔光效的材质
entity不透明材质,不支持透明
entity_emissive_alpha发光材质(夜晚也可见)

通常配置 default(正常状态)和 enchanted(附魔状态)两种材质。

textures ​

定义模型使用的贴图。default 为正常贴图,enchanted 为附魔叠加层贴图(通常使用原版的 textures/misc/enchanted_item_glint)。

geometry ​

指定 3D 模型文件的 geometry 标识符。模型文件位于 resource_pack/models/entity/ 目录下。

render_controllers ​

控制渲染逻辑。推荐使用 controller.render.item_default,它会自动处理普通/附魔状态的材质和贴图切换。

如果需要自定义渲染逻辑(例如条件显示某些骨骼),可以编写自定义渲染控制器。

第一人称手持动画 ​

第一人称和第三人称的摄像机角度不同,因此物品在两种视角下通常需要不同的位置和大小调整。

first_hold 动画 ​

first_hold 动画用于调整物品在第一人称下的位置和旋转:

json
{
  "format_version": "1.8.0",
  "animations": {
    "animation.custom_sword.first_hold": {
      "loop": true,
      "bones": {
        "rightItem": {
          "position": [0, 5, -4],
          "rotation": [90, 45, 0]
        }
      }
    }
  }
}

first_scale 动画 ​

first_scale 动画用于调整物品在第一人称下的缩放:

json
{
  "format_version": "1.8.0",
  "animations": {
    "animation.custom_sword.first_scale": {
      "loop": true,
      "bones": {
        "sword": {
          "scale": [1.5, 1.5, 1.5]
        }
      }
    }
  }
}

使用条件播放 ​

在 Attachable 的 scripts/animate 中,通过 c.is_first_person 条件来区分视角:

json
"scripts": {
  "animate": [
    { "first_hold": "c.is_first_person" },
    { "first_scale": "c.is_first_person" },
    { "third_hold": "!c.is_first_person" }
  ]
}
  • c.is_first_person 为 true 时播放第一人称动画
  • !c.is_first_person 时播放第三人称动画

开发者调试工具 ​

在游戏中可以使用开发者工具实时调整动画参数:

按键功能
F3打开调试面板,显示坐标、帧率等信息
F11打开动画调试界面,可实时修改骨骼的 position、rotation、scale 值

通过 F11 界面调试到合适的参数后,将数值填写到动画 JSON 中即可。

第一人称攻击效果 ​

要实现第一人称下挥剑攻击的视觉效果,需要将手臂模型和武器模型分开处理,并通过自定义 Query 节点驱动攻击动画。

骨骼结构设计 ​

将模型拆分为两部分:

rightItem
├── arm        ← 手臂骨骼(仅第一人称显示)
│   ├── arm_upper
│   └── arm_lower
└── sword      ← 武器骨骼
    └── blade

手臂模型仅在第一人称时渲染,第三人称时隐藏(因为第三人称有玩家自身的手臂)。

自定义渲染控制器 ​

为手臂模型创建单独的渲染控制器,通过 c.is_first_person 控制可见性:

json
{
  "format_version": "1.8.0",
  "render_controllers": {
    "controller.render.custom_sword_arm": {
      "materials": [
        { "*": "Material.default" }
      ],
      "textures": ["Texture.arm"],
      "geometry": "Geometry.default",
      "part_visibility": [
        { "arm": "c.is_first_person" },
        { "arm_upper": "c.is_first_person" },
        { "arm_lower": "c.is_first_person" }
      ]
    }
  }
}

攻击动画与 query.mod ​

通过 query.mod.sword_attack_time 自定义 Query 节点来驱动攻击动画。当玩家点击攻击时,通过 Python 脚本设置该值:

python
import mod.client.extraClientApi as clientApi

class SwordClient(clientApi.GetClientSystemCls()):
    def __init__(self, namespace, systemName):
        super(SwordClient, self).__init__(namespace, systemName)
        self.ListenForEvent(
            clientApi.EngineNamespace,
            clientApi.EngineSystemName,
            "OnLocalPlayerStartAttackClientEvent",
            self,
            self.OnAttack
        )

    def OnAttack(self, args):
        playerId = clientApi.GetLocalPlayerId()
        comp = clientApi.GetEngineCompFactory().CreateQueryVariable(playerId)
        # 设置攻击时间为 1.0,动画控制器中会根据此值播放攻击动画
        comp.Set("query.mod.sword_attack_time", 1.0)

攻击动画控制器 ​

在动画控制器中根据 query.mod.sword_attack_time 切换攻击/待机状态:

json
{
  "format_version": "1.10.0",
  "animation_controllers": {
    "controller.animation.custom_sword.first_attack": {
      "initial_state": "idle",
      "states": {
        "idle": {
          "transitions": [
            { "attack": "query.mod.sword_attack_time > 0.0" }
          ]
        },
        "attack": {
          "animations": ["first_attack"],
          "transitions": [
            { "idle": "query.anim_time > 0.3" }
          ]
        }
      }
    }
  }
}

第三人称攻击效果 ​

第三人称的攻击动画需要作用于玩家自身的骨骼(而非 Attachable 的骨骼),因此需要通过 Python 脚本动态为玩家添加动画资源。

相关接口 ​

接口说明
AddPlayerAnimation为玩家添加自定义动画资源
AddPlayerAnimationController为玩家添加自定义动画控制器

第三人称手持动画 ​

创建 third_hold 动画,调整手臂在持剑状态下的旋转角度:

json
{
  "format_version": "1.8.0",
  "animations": {
    "animation.custom_sword.third_hold": {
      "loop": true,
      "bones": {
        "rightArm": {
          "rotation": [-60, 0, 0]
        }
      }
    }
  }
}

第三人称攻击动画 ​

创建 third_attack 动画,实现手臂挥舞效果:

json
{
  "format_version": "1.8.0",
  "animations": {
    "animation.custom_sword.third_attack": {
      "loop": false,
      "animation_length": 0.3,
      "bones": {
        "rightArm": {
          "rotation": {
            "0.0": [-60, 0, 0],
            "0.15": [-120, 30, 0],
            "0.3": [-60, 0, 0]
          }
        }
      }
    }
  }
}

通过脚本挂载玩家动画 ​

使用 AddPlayerAnimation 和 AddPlayerAnimationController 接口将第三人称攻击动画挂载到玩家身上:

python
import mod.client.extraClientApi as clientApi

class SwordClient(clientApi.GetClientSystemCls()):
    def SetupThirdPersonAnimation(self, playerId):
        comp = clientApi.GetEngineCompFactory().CreateActorRender(playerId)
        # 注册动画资源
        comp.AddPlayerAnimation(
            "third_attack",
            "animation.custom_sword.third_attack"
        )
        # 注册动画控制器
        comp.AddPlayerAnimationController(
            "third_attack_ctrl",
            "controller.animation.custom_sword.third_attack"
        )

对应的第三人称攻击动画控制器:

json
{
  "format_version": "1.10.0",
  "animation_controllers": {
    "controller.animation.custom_sword.third_attack": {
      "initial_state": "idle",
      "states": {
        "idle": {
          "transitions": [
            { "attack": "query.mod.sword_attack_time > 0.0" }
          ]
        },
        "attack": {
          "animations": ["third_attack"],
          "transitions": [
            { "idle": "query.anim_time > 0.3" }
          ]
        }
      }
    }
  }
}

context 变量 ​

Attachable 的 scripts/animate 中可以使用以下 context 变量(以 c. 前缀引用):

变量类型说明
c.is_first_personbool当前是否为第一人称视角
c.owning_entityentity持有该物品的实体引用,可用于查询实体属性
c.item_slotstring物品所在的槽位(main_hand 或 off_hand)

使用示例 ​

json
"scripts": {
  "animate": [
    { "first_hold": "c.is_first_person" },
    { "third_hold": "!c.is_first_person" },
    { "attack_ctrl": "c.item_slot == 'main_hand'" }
  ]
}

c.owning_entity 可以配合 Molang 查询使用,例如:

c.owning_entity->query.is_sneaking

表示当持有物品的实体正在潜行时返回 true。

完整附着物示例 ​

以下是一个完整的 3D 剑附着物配置,包含所有必要的文件。

文件结构 ​

resource_pack/
├── attachables/
│   └── custom_sword.json           # 附着物定义
├── models/entity/
│   └── custom_sword.geo.json       # 3D 模型
├── textures/items/
│   └── custom_sword_3d.png         # 3D 模型贴图
├── animations/
│   ├── custom_sword.animation.json         # 手持动画
│   └── custom_sword.attack.animation.json  # 攻击动画
├── animation_controllers/
│   └── custom_sword.attack.controller.json # 攻击动画控制器
└── render_controllers/
    └── custom_sword.render.json    # 自定义渲染控制器(可选)

附着物定义 ​

resource_pack/attachables/custom_sword.json:

json
{
  "format_version": "1.10.0",
  "minecraft:attachable": {
    "description": {
      "identifier": "mymod:custom_sword",
      "materials": {
        "default": "entity_alphatest",
        "enchanted": "entity_alphatest_glint"
      },
      "textures": {
        "default": "textures/items/custom_sword_3d",
        "enchanted": "textures/misc/enchanted_item_glint"
      },
      "geometry": {
        "default": "geometry.custom_sword"
      },
      "animations": {
        "first_hold": "animation.custom_sword.first_hold",
        "first_scale": "animation.custom_sword.first_scale",
        "third_hold": "animation.custom_sword.third_hold",
        "first_attack_ctrl": "controller.animation.custom_sword.first_attack"
      },
      "scripts": {
        "animate": [
          { "first_hold": "c.is_first_person" },
          { "first_scale": "c.is_first_person" },
          { "third_hold": "!c.is_first_person" },
          { "first_attack_ctrl": "c.is_first_person" }
        ]
      },
      "render_controllers": [
        "controller.render.item_default"
      ]
    }
  }
}

手持动画 ​

resource_pack/animations/custom_sword.animation.json:

json
{
  "format_version": "1.8.0",
  "animations": {
    "animation.custom_sword.first_hold": {
      "loop": true,
      "bones": {
        "rightItem": {
          "position": [0, 6, -5],
          "rotation": [90, 50, 0]
        }
      }
    },
    "animation.custom_sword.first_scale": {
      "loop": true,
      "bones": {
        "sword": {
          "scale": [1.4, 1.4, 1.4]
        }
      }
    },
    "animation.custom_sword.third_hold": {
      "loop": true,
      "bones": {
        "rightArm": {
          "rotation": [-50, 0, 0]
        }
      }
    }
  }
}

攻击动画 ​

resource_pack/animations/custom_sword.attack.animation.json:

json
{
  "format_version": "1.8.0",
  "animations": {
    "animation.custom_sword.first_attack": {
      "loop": false,
      "animation_length": 0.3,
      "bones": {
        "rightItem": {
          "rotation": {
            "0.0": [90, 50, 0],
            "0.1": [60, 70, 20],
            "0.2": [120, 30, -10],
            "0.3": [90, 50, 0]
          }
        }
      }
    }
  }
}

攻击动画控制器 ​

resource_pack/animation_controllers/custom_sword.attack.controller.json:

json
{
  "format_version": "1.10.0",
  "animation_controllers": {
    "controller.animation.custom_sword.first_attack": {
      "initial_state": "idle",
      "states": {
        "idle": {
          "transitions": [
            { "attack": "query.mod.sword_attack_time > 0.0" }
          ]
        },
        "attack": {
          "animations": ["first_attack"],
          "transitions": [
            { "idle": "query.anim_time > 0.3" }
          ],
          "on_entry": [
            "/say 攻击动画播放"
          ]
        }
      }
    }
  }
}

模型参考 ​

3D 物品的模型文件(geometry.custom_sword)应使用 Blockbench 导出。以下为简化示例结构:

json
{
  "format_version": "1.12.0",
  "minecraft:geometry": [
    {
      "description": {
        "identifier": "geometry.custom_sword",
        "texture_width": 32,
        "texture_height": 32,
        "visible_bounds_width": 2,
        "visible_bounds_height": 2,
        "visible_bounds_offset": [0, 0.5, 0]
      },
      "bones": [
        {
          "name": "rightItem",
          "pivot": [0, 0, 0]
        },
        {
          "name": "sword",
          "parent": "rightItem",
          "pivot": [0, 0, 0],
          "cubes": [
            {
              "origin": [-0.5, 0, -0.5],
              "size": [1, 16, 1],
              "uv": [0, 0]
            }
          ]
        }
      ]
    }
  ]
}

TIP

实际项目中模型会更加复杂,建议使用 Blockbench 可视化建模后导出 JSON。上面的示例仅展示骨骼层级和基本结构。

常见问题 ​

物品在手中看不到 3D 模型 ​

  • 确认 Attachable 的 identifier 与物品的 identifier 完全一致
  • 确认 Attachable JSON 文件放置在 resource_pack/attachables/ 目录下
  • 检查 geometry 字段指向的模型标识符是否正确

第一人称位置不对 ​

使用 F11 开发者工具实时调整 first_hold 动画的 position 和 rotation 参数。

附魔光效不显示 ​

确认 materials 中包含 "enchanted": "entity_alphatest_glint",且 textures 中包含 "enchanted": "textures/misc/enchanted_item_glint"。同时确认使用的渲染控制器支持附魔材质切换(如 controller.render.item_default)。

第三人称手臂穿模 ​

调整 third_hold 动画中手臂骨骼的旋转角度,使手臂与武器模型不会产生穿插。

下一步 ​