自定义 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 已经对齐到玩家右手的位置。
模型制作要求
- 遵循骨骼层级:模型的根骨骼应命名为物品相关的名称(如
sword),并放置在rightItem骨骼下 - pivot 对齐:物品模型的 pivot 应设置在手握处,确保手持时物品不会偏移
- 朝向:模型默认面朝 Z 轴正方向,握柄朝下
- 尺寸参考:可以参考原版钻石剑的模型大小作为基准(约 16×16×1 像素)
注意
模型文件中不需要包含完整的玩家骨骼层级(root/waist/body 等),只需要包含 rightItem 骨骼和物品自身的骨骼即可。完整层级由引擎在运行时自动拼接。
校正第三人称手持位置
在 Blockbench 中制作模型后,建议将模型导入到玩家骨架中检查第三人称手持效果。
操作步骤
- 在 Blockbench 中打开一个 基岩版实体模型 工程
- 导入官方玩家模型(或使用 Blockbench 内置的 Steve 模型)作为参考
- 在骨骼树中找到
rightArm → rightItem骨骼 - 将你的物品模型骨骼作为
rightItem的子骨骼添加进去 - 调整物品骨骼的 pivot 和 position,直到手持效果满意
调整要点
- 位置偏移:通过调整物品骨骼在
rightItem下的position值来修正物品在手中的位置 - 旋转校正:如果物品朝向不对,调整物品骨骼的
rotation值 - 导出时:最终导出的模型只需包含
rightItem及其子骨骼,不需要包含玩家的其他骨骼
TIP
Blockbench 的实时预览功能可以直接看到手持效果。建议配合玩家模型进行调整,避免反复进入游戏测试。
Attachable 配置详解
Attachable 的配置文件位于 resource_pack/attachables/ 目录下,文件名建议与物品 identifier 对应。
完整配置结构
{
"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 动画用于调整物品在第一人称下的位置和旋转:
{
"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 动画用于调整物品在第一人称下的缩放:
{
"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 条件来区分视角:
"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 控制可见性:
{
"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 脚本设置该值:
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 切换攻击/待机状态:
{
"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 动画,调整手臂在持剑状态下的旋转角度:
{
"format_version": "1.8.0",
"animations": {
"animation.custom_sword.third_hold": {
"loop": true,
"bones": {
"rightArm": {
"rotation": [-60, 0, 0]
}
}
}
}
}第三人称攻击动画
创建 third_attack 动画,实现手臂挥舞效果:
{
"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 接口将第三人称攻击动画挂载到玩家身上:
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"
)对应的第三人称攻击动画控制器:
{
"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_person | bool | 当前是否为第一人称视角 |
c.owning_entity | entity | 持有该物品的实体引用,可用于查询实体属性 |
c.item_slot | string | 物品所在的槽位(main_hand 或 off_hand) |
使用示例
"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:
{
"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:
{
"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:
{
"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:
{
"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 导出。以下为简化示例结构:
{
"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 动画中手臂骨骼的旋转角度,使手臂与武器模型不会产生穿插。
