客户端物品
客户端物品(Client Item)在代码中表示 ItemStack 应如何提交给游戏渲染,指定不同状态下使用哪些模型。文件位于 assets 下的 items 子目录,相对位置由 DataComponents#ITEM_MODEL 指定,默认使用对象注册名。例如 minecraft:apple 默认位于 assets/minecraft/items/apple.json。
客户端物品存储于 ModelManager,可通过 Minecraft.getInstance().modelManager 访问。然后用 Identifier 调用 ModelManager#getItemModel 或 getItemProperties 获取相关信息。
警告 不要将客户端物品与游戏中实际烘焙并渲染的模型混淆。
概览
JSON 分为两部分:model 定义在给定上下文中提交物品堆叠渲染时使用哪些模型 JSON;properties 定义渲染器使用的设置。
JSON
// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
// Defines the model to submit for rendering
"model": {
"type": "minecraft:model",
// Points to a model JSON relative to the 'models' directory
// Located at 'assets/examplemod/models/item/example_item.json'
"model": "examplemod:item/example_item"
},
// Defines some settings to use during the rendering process
"properties": {
// When false, disables the animation where the item is raised
// up towards its normal position on item swap
"hand_animation_on_swap": false,
// When true, allows the model to render outside its defined
// slot bounds (defined in GuiItemRenderState#bounds) in a GUI
// instead of being scissored
"oversized_in_gui": false,
// Applies the scalar to the height of the hand when swapping
"swap_animation_scale": 1.0
}
}
数据生成
// Assume there is some DeferredItem<Item> EXAMPLE_ITEM
// Within an extended ModelProvider
@Override
protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) {
itemModels.itemModelOutput.register(
EXAMPLE_ITEM.get(),
// Defines the model to submit for rendering
new CuboidItemModelWrapper.Unbaked(
// Points to a model JSON relative to the 'models' directory
// Located at 'assets/examplemod/models/item/example_item.json'
ModelLocationUtils.getModelLocation(EXAMPLE_ITEM.get()),
Optional.empty(),
Collections.emptyList()
),
// Defines some settings to use during the rendering process
new ClientItem.Properties(
// When false, disables the animation where the item is raised
// up towards its normal position on item swap
false,
// When true, allows the model to render outside its defined
// slot bounds (defined in GuiItemRenderState#bounds) in a GUI
// instead of being scissored
false,
// Applies the scalar to the height of the hand when swapping
1.0F
)
);
}
关于物品模型如何提交渲染的更多信息,见下文。
基础模型
model 中的 type 决定如何选择提交渲染的模型。最简单的类型是 minecraft:model(即 CuboidItemModelWrapper),直接指定相对于 models 目录的模型 JSON,例如 assets/<namespace>/models/<path>.json。
JSON
// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "minecraft:model",
// Points to a model JSON relative to the 'models' directory
// Located at 'assets/examplemod/models/item/example_item.json'
"model": "examplemod:item/example_item"
}
}
数据生成
// Assume there is some DeferredItem<Item> EXAMPLE_ITEM
// Within an extended ModelProvider
@Override
protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) {
itemModels.itemModelOutput.accept(
EXAMPLE_ITEM.get(),
new CuboidItemModelWrapper.Unbaked(
// Points to a model JSON relative to the 'models' directory
// Located at 'assets/examplemod/models/item/example_item.json'
ModelLocationUtils.getModelLocation(EXAMPLE_ITEM.get()),
Optional.empty(),
Collections.emptyList()
)
);
}
局部变换
多数客户端物品模型都可以像模型 JSON 一样指定 Transformation。该变换在模型 JSON 对相应显示上下文的变换之后应用,通过 minecraft:model 类型的 transformation 字段设置。
JSON
// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item.json'
"model": "examplemod:item/example_item",
// The transformations to apply after the model JSON transforms.
"transformation": {
// The translation of the client item, specified as `[x, y, z]`.
"translation": [
0.5,
0.0,
0.5
],
// The initial rotation of the client item, specified as:
// - `[x, y, z, w]`
// - { angle, [x, y, z] rotation axis }
"left_rotation": [
1.0,
0.0,
0.0,
0.0
],
// The scale of the client item, specified as `[x, y, z]`.
"scale": [
1.0,
1.0,
1.0
],
// The rotation of the client item after scaling, specified as:
// - `[x, y, z, w]`
// - { angle, [x, y, z] rotation axis }
"right_rotation": {
"angle": 0,
"axis": [
0.0,
0.0,
0.0
]
}
}
}
}
数据生成
// Assume there is some DeferredItem<Item> EXAMPLE_ITEM
// Within an extended ModelProvider
@Override
protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) {
itemModels.itemModelOutput.accept(
EXAMPLE_ITEM.get(),
new CuboidItemModelWrapper.Unbaked(
// Points to 'assets/examplemod/models/item/example_item.json'
ModelLocationUtils.getModelLocation(EXAMPLE_ITEM.get()),
// The transformations to apply after the model JSON transforms.
Optional.of(new Transformation(
// The translation of the client item.
new Vector3f(0.5f, 0f, 0.5f),
// The initial rotation of the client item.
new Quaternionf(1f, 0f, 0f, 0f),
// The scale of the client item.
new Vector3f(1f, 1f, 1f),
// The rotation of the client item after scaling.
new Quaternionf(new AxisAngle4f(0f, 0f, 0f, 0f))
)),
Collections.emptyList()
)
);
}
着色
与多数模型一样,客户端物品可以根据堆叠属性改变指定纹理的颜色。minecraft:model 的 tints 字段定义应用的不透明颜色,称为 ItemTintSource,已有类型定义在 ItemTintSources 中。它们也通过 type 指定具体来源,在列表中的位置就是应用的 tintindex。
JSON
// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item.json'
"model": "examplemod:item/example_item",
// A list of tints to apply
"tints": [
{
// For when tintindex: 0
"type": "minecraft:constant",
// 0x00FF00 (or pure green)
"value": 65280
},
{
// For when tintindex: 1
"type": "minecraft:dye",
// 0x0000FF (or pure blue)
// Only is called if `DataComponents#DYED_COLOR` is not set
"default": 255
}
]
}
}
数据生成
// Assume there is some DeferredItem<Item> EXAMPLE_ITEM
// Within an extended ModelProvider
@Override
protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) {
itemModels.itemModelOutput.accept(
EXAMPLE_ITEM.get(),
new CuboidItemModelWrapper.Unbaked(
// Points to 'assets/examplemod/models/item/example_item.json'
ModelLocationUtils.getModelLocation(EXAMPLE_ITEM.get()),
Optional.empty(),
// A list of tints to apply
List.of(
// For when tintindex: 0
new Constant(
// Pure green
0x00FF00
),
// For when tintindex: 1
new Dye(
// Pure blue
// Only is called if `DataComponents#DYED_COLOR` is not set
0x0000FF
)
)
)
);
}
创建自定义 ItemTintSource 与其他基于 codec 的注册对象类似:实现该接口,创建负责对象编码与解码的 MapCodec,再通过 Mod 事件总线上的 RegisterColorHandlersEvent.ItemTintSources 注册 codec。接口只有 calculate 方法,接收当前堆叠、堆叠所在世界及持有实体,返回 ARGB 不透明颜色,最高 8 位为 0xFF。
public record DamageBar(int defaultColor) implements ItemTintSource {
// The map codec to register
public static final MapCodec<DamageBar> MAP_CODEC = ExtraCodecs.RGB_COLOR_CODEC.fieldOf("default")
.xmap(DamageBar::new, DamageBar::defaultColor);
public DamageBar(int defaultColor) {
// Make sure the passed in color is opaque
this.defaultColor = ARGB.opaque(defaultColor);
}
@Override
public int calculate(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity) {
return stack.isDamaged() ? ARGB.opaque(stack.getBarColor()) : defaultColor;
}
@Override
public MapCodec<DamageBar> type() {
return MAP_CODEC;
}
}
// In some event handler class
@SubscribeEvent // on the mod event bus only on the physical client
public static void registerItemTintSources(RegisterColorHandlersEvent.ItemTintSources event) {
event.register(
// The name to reference as the type
Identifier.fromNamespaceAndPath("examplemod", "damage_bar"),
// The map codec
DamageBar.MAP_CODEC
)
}
JSON
// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item.json'
"model": "examplemod:item/example_item",
// A list of tints to apply
"tints": [
{
// For when tintindex: 0
"type": "examplemod:damage_bar",
// 0x00FF00 (or pure green)
"default": 65280
}
]
}
}
数据生成
// Assume there is some DeferredItem<Item> EXAMPLE_ITEM
// Within an extended ModelProvider
@Override
protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) {
itemModels.itemModelOutput.accept(
EXAMPLE_ITEM.get(),
new CuboidItemModelWrapper.Unbaked(
// Points to 'assets/examplemod/models/item/example_item.json'
ModelLocationUtils.getModelLocation(EXAMPLE_ITEM.get()),
Optional.empty(),
// A list of tints to apply
List.of(
// For when tintindex: 0
new DamageBar(
// Pure green
0x00FF00
)
)
)
);
}
组合模型
有时需要为一个物品注册多个模型。虽然可以使用组合模型加载器,但物品有专用 minecraft:composite 类型,接收提交渲染的模型列表。
JSON
// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "minecraft:composite",
// The models to submit for rendering
// Will be drawn in the order they appear in the list
"models": [
{
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_1.json'
"model": "examplemod:item/example_item_1"
},
{
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_2.json'
"model": "examplemod:item/example_item_2"
}
]
}
}
数据生成
// Assume there is some DeferredItem<Item> EXAMPLE_ITEM
// Within an extended ModelProvider
@Override
protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) {
itemModels.itemModelOutput.accept(
EXAMPLE_ITEM.get(),
new CompositeModel.Unbaked(
// The models to submit for rendering
// Will be drawn in the order they appear in the list
List.of(
new CuboidItemModelWrapper.Unbaked(
// Points to 'assets/examplemod/models/item/example_item_1.json'
Identifier.fromNamespaceAndPath("examplemod", "item/example_item_1"),
Optional.empty(),
Collections.emptyList()
),
new CuboidItemModelWrapper.Unbaked(
// Points to 'assets/examplemod/models/item/example_item_2.json'
Identifier.fromNamespaceAndPath("examplemod", "item/example_item_2"),
Optional.empty(),
Collections.emptyList()
)
)
)
);
}
来源: NeoForged/Documentation(26.1,原文第 4–451 行)。中文翻译依据提交
4a680cdd4d15。