跳到主要内容
版本:26.1

客户端物品

客户端物品(Client Item)是在代码中对某个 ItemStack 应如何被提交以在游戏内渲染的表示,用于指定在给定状态下使用哪些模型。客户端物品位于 assets 文件夹内的 items 子目录中,具体位置由 DataComponents#ITEM_MODEL 中的相对位置指定。默认情况下,这就是该对象的注册名(例如 minecraft:apple 默认位于 assets/minecraft/items/apple.json)。

客户端物品存储在 ModelManager 中,可通过 Minecraft.getInstance().modelManager 访问。随后,你可以调用 ModelManager#getItemModelgetItemProperties,按其 Identifier 获取客户端物品信息。

注意

不要把它们与游戏内实际被烘焙并真正渲染的模型相混淆。

概述

客户端物品的 JSON 可分为两部分:由 model 定义的模型,以及由 properties 定义的属性。model 负责定义在给定上下文中提交 ItemStack 进行渲染时使用哪些模型 JSON;而 properties 则负责定义渲染器所使用的各项设置。

// 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
}
}

关于物品模型如何被提交以进行渲染的更多信息,可参见下文

基础模型

model 中的 type 字段决定了如何为该物品选择要提交渲染的模型。最简单的类型由 minecraft:model(即 CuboidItemModelWrapper)处理,其作用是定义要提交渲染的模型 JSON,路径相对于 models 目录(例如 assets/<namespace>/models/<path>.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"
}
}

局部变换

大多数客户端物品模型都可以为物品模型指定一个 Transformation,类似于模型 JSON。这些 Transformation 会在对应显示上下文的模型 JSON 变换之后应用。它通过 minecraft:model 类型的 transformation 字段设置。

// 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
]
}
}
}
}

染色

和大多数模型一样,客户端物品可以根据物品堆叠的属性改变指定纹理的颜色。为此,minecraft:model 类型提供了 tints 字段,用于定义要应用的不透明颜色。它们被称为 ItemTintSource,定义在 ItemTintSources 中。它们同样有一个 type 字段来定义使用哪个来源。它们所应用到的 tintindex 由其在列表中的索引指定。

// 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
}
]
}
}

创建自己的 ItemTintSource 与创建其他任何基于 codec 的注册对象类似。你创建一个实现 ItemTintSource 的类,创建一个用于编码和解码该对象的 MapCodec,并在 mod 事件总线上通过 RegisterColorHandlersEvent.ItemTintSources 将该 codec 注册到其注册表。ItemTintSource 只包含一个方法 calculate,它接收当前的 ItemStack、该物品堆叠所在的世界,以及持有该物品堆叠的实体,返回一个 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
)
}
// 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
}
]
}
}

组合模型

有时,你可能想为单个物品注册多个模型。这虽然可以直接用组合模型加载器实现,但对于物品模型,还有一个自定义的 minecraft:composite 类型,它接收一个要提交渲染的模型列表。

// 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"
}
]
}
}

属性模型

某些物品会根据其物品堆叠中存储的数据改变状态(例如拉弓、鞘翅破损、时钟在特定维度中的表现等)。为了让模型能够依据状态变化,物品模型可以指定一个要追踪的属性,并据此条件选择模型。属性模型共有三种不同类型:范围分派(range dispatch)、选择(select)和条件(conditional)。它们分别相当于针对某个浮点数的表达式、switch case 以及布尔值。

范围分派模型

范围分派模型让其 type 定义某个 RangeSelectItemModelProperty,以获取一个用于切换模型的浮点数。随后每个条目都有一个阈值,浮点数必须大于该阈值才会被提交渲染。所选的模型是阈值最接近且不超过属性值的那一个(例如,若属性值为 4,阈值为 35,则绘制与 3 关联的模型;若属性值为 6,则绘制与 5 关联的模型)。可用的 RangeSelectItemModelProperty 可在 RangeSelectItemModelProperties 中找到。

// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "minecraft:range_dispatch",

// The `RangeSelectItemModelProperty` to use
"property": "minecraft:count",
// A scalar to multiply to the computed property value
// If count was 0.3 and scale was 0.2, then the threshold checked would be 0.3*0.2=0.06
"scale": 1,
"fallback": {
// The fallback model to use if no threshold matches
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item.json'
"model": "examplemod:item/example_item"
},

// Properties defined by `Count`
// When true, normalizes the count using its max stack size
"normalize": true,

// Entries with threshold information
"entries": [
{
// When the count is a third of its current max stack size
"threshold": 0.33,
"model": {
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_1.json'
"model": "examplemod:item/example_item_1"
}
},
{
// When the count is two thirds of its current max stack size
"threshold": 0.66,
"model": {
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_2.json'
"model": "examplemod:item/example_item_2"
}
}
]
}
}

创建自己的 RangeSelectItemModelProperty 与创建其他任何基于 codec 的注册对象类似。你创建一个实现 RangeSelectItemModelProperty 的类,创建一个用于编码和解码该对象的 MapCodec,并在 mod 事件总线上通过 RegisterRangeSelectItemModelPropertyEvent 将该 codec 注册到其注册表。RangeSelectItemModelProperty 只包含一个方法 get,它接收当前的 ItemStack、该物品堆叠所在的世界、持有该物品堆叠的实体,以及某个种子值,返回一个任意浮点数,供范围分派模型解读。

public record AppliedEnchantments() implements RangeSelectItemModelProperty {

public static final MapCodec<AppliedEnchantments> MAP_CODEC = MapCodec.unit(new AppliedEnchantments());

@Override
public float get(ItemStack stack, @Nullable ClientLevel level, @Nullable ItemOwner owner, int seed) {
return (float) stack.getTagEnchantments().size();
}

@Override
public MapCodec<AppliedEnchantments> type() {
return MAP_CODEC;
}
}

// In some event handler class
@SubscribeEvent // on the mod event bus only on the physical client
public static void registerRangeProperties(RegisterRangeSelectItemModelPropertyEvent event) {
event.register(
// The name to reference as the type
Identifier.fromNamespaceAndPath("examplemod", "applied_enchantments"),
// The map codec
AppliedEnchantments.MAP_CODEC
)
}
// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "minecraft:range_dispatch",

// The `RangeSelectItemModelProperty` to use
"property": "examplemod:applied_enchantments",
// A scalar to multiply to the computed property value
// If count was 0.3 and scale was 0.2, then the threshold checked would be 0.3*0.2=0.06
"scale": 0.5,
"fallback": {
// The fallback model to use if no threshold matches
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item.json'
"model": "examplemod:item/example_item"
},

// Entries with threshold information
"entries": [
{
// When there is at least one enchantment present
// Since 1 * the scale 0.5 = 0.5
"threshold": 0.5,
"model": {
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_1.json'
"model": "examplemod:item/example_item_1"
}
},
{
// When there are at least two enchantments present
// Since 2 * the scale 0.5 = 1
"threshold": 1,
"model": {
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_2.json'
"model": "examplemod:item/example_item_2"
}
}
]
}
}

选择模型

选择模型与范围分派模型类似,但它根据 SelectItemModelProperty 所定义的某个值来切换,就像针对枚举的 switch 语句。所选的模型是与 switch case 中的值精确匹配的那一个。可用的 SelectItemModelProperty 可在 SelectItemModelProperties 中找到。

// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "minecraft:select",

// The `SelectItemModelProperty` to use
"property": "minecraft:display_context",
"fallback": {
// The fallback model to use if no case matches
// Can be any unbaked model type
"type": "minecraft:model",
"model": "examplemod:item/example_item"
},

// Switch cases based on Selectable Property
"cases": [
{
// When the display context is `ItemDisplayContext#GUI`
"when": "gui",
"model": {
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_1.json'
"model": "examplemod:item/example_item_1"
}
},
{
// When the display context is `ItemDisplayContext#FIRST_PERSON_RIGHT_HAND`
"when": "firstperson_righthand",
"model": {
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_2.json'
"model": "examplemod:item/example_item_2"
}
}
]
}
}

创建自己的 SelectItemModelProperty 与创建基于 codec 的注册对象类似。你创建一个实现 SelectItemModelProperty<T> 的类,创建一个用于序列化和反序列化属性值的 Codec,创建一个用于编码和解码该对象的 MapCodec,并在 mod 事件总线上通过 RegisterSelectItemModelPropertyEvent 将该 codec 注册到其注册表。SelectItemModelProperty 带有一个泛型 T,代表用于切换的值。它只包含一个方法 get,它接收当前的 ItemStack、该物品堆叠所在的世界、持有该物品堆叠的实体、某个种子值以及该物品的显示上下文,返回一个任意的 T,供选择模型解读。

// The select property class
public record StackRarity() implements SelectItemModelProperty<Rarity> {

// The object to register that contains the relevant codecs
public static final SelectItemModelProperty.Type<StackRarity, Rarity> TYPE = SelectItemModelProperty.Type.create(
// The map codec for this property
MapCodec.unit(new StackRarity()),
// The codec for the object being selected
// Used to serialize the case entries ("when": <property value>)
Rarity.CODEC
);

@Nullable
@Override
public Rarity get(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int seed, ItemDisplayContext displayContext) {
// When null, uses the fallback model
return stack.get(DataComponents.RARITY);
}

@Override
public SelectItemModelProperty.Type<StackRarity, Rarity> type() {
return TYPE;
}
}

// In some event handler class
@SubscribeEvent // on the mod event bus only on the physical client
public static void registerSelectProperties(RegisterSelectItemModelPropertyEvent event) {
event.register(
// The name to reference as the type
Identifier.fromNamespaceAndPath("examplemod", "rarity"),
// The property type
StackRarity.TYPE
)
}
// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "minecraft:select",

// The `SelectItemModelProperty` to use
"property": "examplemod:rarity",
"fallback": {
// The fallback model to use if no case matches
// Can be any unbaked model type
"type": "minecraft:model",
"model": "examplemod:item/example_item"
},

// Switch cases based on Selectable Property
"cases": [
{
// When the rarity is `Rarity#UNCOMMON`
"when": "uncommon",
"model": {
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_1.json'
"model": "examplemod:item/example_item_1"
}
},
{
// When the rarity is `Rarity#RARE`
"when": "rare",
"model": {
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_2.json'
"model": "examplemod:item/example_item_2"
}
}
]
}
}

条件模型

条件模型是三者中最简单的。其 type 定义某个 ConditionalItemModelProperty,以获取一个用于切换模型的布尔值。所选的模型取决于返回的布尔值为 true 还是 false。可用的 ConditionalItemModelProperty 可在 ConditionalItemModelProperties 中找到。

// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "minecraft:condition",

// The `ConditionalItemModelProperty` to use
"property": "minecraft:damaged",

// What the boolean outcome is
"on_true": {
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_1.json'
"model": "examplemod:item/example_item_1"

},
"on_false": {
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_2.json'
"model": "examplemod:item/example_item_2"
}
}
}

创建自己的 ConditionalItemModelProperty 与创建其他任何基于 codec 的注册对象类似。你创建一个实现 ConditionalItemModelProperty 的类,创建一个用于编码和解码该对象的 MapCodec,并在 mod 事件总线上通过 RegisterConditionalItemModelPropertyEvent 将该 codec 注册到其注册表。RangeSelectItemModelProperty 只包含一个方法 get,它接收当前的 ItemStack、该物品堆叠所在的世界、持有该物品堆叠的实体、某个种子值以及该物品的显示上下文,返回一个任意布尔值,供条件模型解读(on_trueon_false)。

public record BarVisible() implements ConditionalItemModelProperty {

public static final MapCodec<BarVisible> MAP_CODEC = MapCodec.unit(new BarVisible());

@Override
public boolean get(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int seed, ItemDisplayContext context) {
return stack.isBarVisible();
}

@Override
public MapCodec<BarVisible> type() {
return MAP_CODEC;
}
}

// In some event handler class
@SubscribeEvent // on the mod event bus only on the physical client
public static void registerConditionalProperties(RegisterConditionalItemModelPropertyEvent event) {
event.register(
// The name to reference as the type
Identifier.fromNamespaceAndPath("examplemod", "bar_visible"),
// The map codec
BarVisible.MAP_CODEC
)
}
// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "minecraft:condition",

// The `ConditionalItemModelProperty` to use
"property": "examplemod:bar_visible",

// What the boolean outcome is
"on_true": {
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_1.json'
"model": "examplemod:item/example_item_1"

},
"on_false": {
// Can be any unbaked model type
"type": "minecraft:model",
// Points to 'assets/examplemod/models/item/example_item_2.json'
"model": "examplemod:item/example_item_2"
}
}
}

特殊模型

并非所有模型都能用基础的模型 JSON 表示。有些模型可能包含动态组件,或使用为 BlockEntityRenderer 创建的现有 Model。在这些情况下,有一种特殊模型类型,允许用户指定要提交渲染的 feature。它们被称为 SpecialModelRenderer,定义在 SpecialModelRenderers 中。

// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "minecraft:special",

// The parent model to read the particle texture and display transformation from
// Points to 'assets/minecraft/models/item/template_skull.json'
"base": "minecraft:item/template_skull",
"model": {
// The special model renderer to use
"type": "minecraft:head",

// Properties defined by `SkullSpecialRenderer.Unbaked`
// The type of the skull block
"kind": "wither_skeleton",
// The texture to use when rendering the head
// Points to 'assets/examplemod/textures/entity/heads/skeleton_override.png'
"texture": "examplemod:heads/skeleton_override",
// The animation float used to animate the head model
"animation": 0.5
}
}
}

创建自己的 SpecialModelRenderer 分为三部分:用于提交渲染物品所需 featureSpecialModelRenderer 实例、用于读写 JSON 的 SpecialModelRenderer.Unbaked 实例,以及将该渲染器注册为物品使用(必要时也注册为方块使用)。

首先是 SpecialModelRenderer。它的工作方式与其他任何渲染器类(例如方块实体渲染器、实体渲染器)类似。它应接收提交过程中使用的静态数据(例如 Model 子类、纹理的 SpriteId 等)。有两个方法需要注意。第一个是 extractArgument。它用于只从 ItemStack 中提供必要的数据,从而限制 submit 方法可用的数据量。

备注

如果你不确定可能需要哪些数据,可以让它直接返回相应的 ItemStack。如果你不需要来自物品堆叠的任何数据,则可以改用 NoDataSpecialModelRenderer,它已为你实现了该方法。

接下来是 submit 方法。它接收 extractArgument 返回的值、pose stack、用于提交所需 feature 的收集器、打包光照、覆盖层纹理、物品堆叠是否带有附魔光效(例如已附魔),以及轮廓颜色。所有 feature 的提交都应在该方法中进行。

public record ExampleSpecialRenderer(SpriteGetter spriteGetter, Model.Simple model, SpriteId sprite) implements SpecialModelRenderer<Boolean> {

@Nullable
public Boolean extractArgument(ItemStack stack) {
// Extract the data to be used
return stack.isBarVisible();
}

// Submit the features of the model
@Override
public void submit(Boolean argument, PoseStack poseStack, SubmitNodeCollector collector, int lightCoords, int overlayCoords, boolean hasFoil, int outlineColor) {
collector.submitModel(
this.model, Unit.INSTANCE,
poseStack, this.sprite.renderType(barVisible ? RenderType::entityCutout : RenderType::entitySolid),
lightCoords, overlayCoords, -1, this.spriteGetter.get(this.sprite), outlineColor, null
);
}
}

接下来是 SpecialModelRenderer.Unbaked 实例。它应包含可从文件读取的数据,用于决定要传入特殊渲染器的内容。它同样包含两个方法:bake,用于构造特殊渲染器实例;以及 type,用于定义读写文件时所使用的 MapCodec

public record ExampleSpecialRenderer(SpriteGetter spriteGetter, Model.Simple model, SpriteId sprite) implements SpecialModelRenderer<Boolean> {

// ...

public record Unbaked(Identifier texture) implements SpecialModelRenderer.Unbaked {

public static final MapCodec<ExampleSpecialRenderer.Unbaked> MAP_CODEC = Identifier.CODEC.fieldOf("texture")
.xmap(ExampleSpecialRenderer.Unbaked::new, ExampleSpecialRenderer.Unbaked::texture);

@Override
public MapCodec<ExampleSpecialRenderer.Unbaked> type() {
return MAP_CODEC;
}

@Override
public SpecialModelRenderer<?> bake(SpecialModelRenderer.BakingContext ctx) {
// Resolve resource location to absolute path
Identifier textureLoc = this.texture.withPath(path -> "textures/entity/" + path + ".png");

// Get the model and the sprites to render
return new ExampleSpecialRenderer(ctx.sprites(), ...);
}
}
}

最后,我们把这些对象注册到必要的位置。对于客户端物品,这通过 mod 事件总线上的 RegisterSpecialModelRendererEvent 完成。如果该特殊渲染器还应作为 BlockEntityRenderer 的一部分使用,例如在某种类似物品的上下文中渲染(如末影人手持方块),则应在 mod 事件总线上通过 RegisterBlockModelsEvent 为该方块注册一个 Unbaked 版本。

// In some event handler class
@SubscribeEvent // on the mod event bus only on the physical client
public static void registerSpecialRenderers(RegisterSpecialModelRendererEvent event) {
event.register(
// The name to reference as the type
Identifier.fromNamespaceAndPath("examplemod", "example_special"),
// The map codec
ExampleSpecialRenderer.Unbaked.MAP_CODEC
);
}

// For rendering a block in an item-like context
// Assume some DeferredBlock<ExampleBlock> EXAMPLE_BLOCK
@SubscribeEvent // on the mod event bus only on the physical client
public static void registerSpecialBlockRenderers(RegisterBlockModelsEvent event) {
event.register(
// The unbaked instance to use
new SpecialBlockModelWrapper.Unbaked(
new ExampleSpecialRenderer.Unbaked(Identifier.fromNamespaceAndPath("examplemod", "entity/example_special")),
Optional.empty()
),
// The block to render for
EXAMPLE_BLOCK.get()
);
}
// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "minecraft:special",

// The parent model to read the particle texture and display transformation from
// Points to 'assets/minecraft/models/item/template_skull.json'
"base": "minecraft:item/template_skull",
"model": {
// The special model renderer to use
"type": "examplemod:example_special",

// Properties defined by `ExampleSpecialRenderer.Unbaked`
// The texture to use
// Points to 'assets/examplemod/textures/entity/example/example_texture.png'
"texture": "examplemod:example/example_texture"
}
}
}

动态流体容器

NeoForge 添加了一种物品模型,用于构建动态流体容器,它能够在运行时重新贴图以匹配所容纳的流体。

备注

为了让流体染色作用于流体纹理,相应物品必须附加了 Capabilities.FluidHandler.ITEM。如果你的物品没有直接使用 BucketItem(也不是其子类型),那么你需要为你的物品注册该 Capability

// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "neoforge:fluid_container",

// The textures used to construct the container
// These are in reference to the block atlas, so they are relative to the `textures` directory
"textures": {
// Sets the model particle sprite
// If not set, uses the first texture that is not null:
// - Fluid still texture
// - Container base texture
// - Container cover texture, if not used as a mask
// Points to 'assets/minecraft/textures/item/bucket.png'
"particle": "minecraft:item/bucket",
// Sets the texture to use on the first layer, generally the container of the fluid
// If not set, the layer will not be added
// Points to 'assets/minecraft/textures/item/bucket.png'
"base": "minecraft:item/bucket",
// Sets the texture to use as the mask for the still fluid texture
// Areas where the fluid is seen should be pure white
// If not set or the fluid is empty, then the layer is not drawn
// Points to 'assets/neoforge/textures/item/mask/bucket_fluid.png'
"fluid": "neoforge:item/mask/bucket_fluid",
// Sets the texture to use as either
// - The overlay texture when 'cover_is_mask' is false
// - The mask to apply to the base texture (should be pure white to see) when 'cover_is_mask' is true
// If not set or no base texture is set when 'cover_is_mask' is true, then the layer is not drawn
// Points to 'assets/neoforge/textures/item/mask/bucket_fluid_cover.png'
"cover": "neoforge:item/mask/bucket_fluid_cover",
},

// When true, rotates the model 180 degrees for fluids whose density is negative or zero
// Defaults to false
"flip_gas": true,
// When true, uses the cover texture as a mask for the base texture
// Defaults to true
"cover_is_mask": true,
// When true, sets the lightmap of the fluid texture layer to its max value
// for fluids whose light level is greater than zero
// Defaults to true
"apply_fluid_luminosity": false
}
}

手动提交物品进行渲染

如果你需要提交一个物品 feature,例如在某个 BlockEntityRendererEntityRenderer 中,可以通过三个步骤实现。首先,相应的渲染器创建一个 ItemStackRenderState 来保存物品堆叠的状态。然后,ItemModelResolver 使用其某个方法更新 ItemStackRenderState,把状态更新为当前正在提交的物品。最后,通过 ItemStackRenderState#submit 提交该物品。

ItemStackRenderState 负责追踪绘制所用的数据。每个“模型”都拥有自己的 ItemStackRenderState.LayerRenderState,其中包含要渲染的 BakedQuad,以及它的渲染类型、附魔光效状态、染色信息、动画标志、范围(extents)以及所用的任何特殊渲染器。图层通过 newLayer 方法创建,并通过 clear 方法清空以便渲染。如果使用了预定数量的图层,则用 ensureCapacity 确保有足够数量的 LayerRenderStates 以正确渲染。

备注

界面使用子类 TrackingItemStackRenderState 来保存模型标识元素,以便跨帧缓存渲染状态。

ItemModelResolver 负责更新 ItemStackRenderState。这通过以下方法完成:生物实体持有的物品用 updateForLiving,其他类型实体持有的物品用 updateForNonLiving,其余所有情况用 updateForTopItem。这些方法接收渲染状态、要渲染的物品堆叠以及当前显示上下文。其余参数则更新关于所持之手、世界、物品持有者和种子值的信息。每个方法都会先调用 ItemStackRenderState#clear,再对从 DataComponents#ITEM_MODEL 获取的 ItemModel 调用 update。如果你不在某个渲染器上下文中(例如 BlockEntityRendererEntityRenderer),也总是可以通过 Minecraft#getItemModelResolver 获取 ItemModelResolver

自定义物品模型定义

创建自己的 ItemModel 分为三部分:用于更新渲染状态的 ItemModel 实例、用于读写 JSON 的 ItemModel.Unbaked 实例,以及注册以使用该 ItemModel

注意

请务必确认你所需的物品模型无法用上文已有的系统实现。在大多数情况下,无需创建自定义 ItemModel

首先是 ItemModel。它负责更新 ItemStackRenderState,使物品被正确绘制。它应接收提交过程中使用的静态数据(例如 BakedQuad 列表、属性信息等)。唯一的方法是 update,它接收渲染状态、物品堆叠、模型解析器、显示上下文、世界、物品持有者以及某个种子值,用于更新 ItemStackRenderStateItemStackRenderState 应是唯一被修改的参数,其余参数都应视为只读数据。

public record ExampleModelWrapper(QuadCollection quads, List<ItemTintSource> tints, ModelRenderProperties properties, Matrix4fc transformation) implements ItemModel {

// Update the render state
@Override
public void update(ItemStackRenderState state, ItemStack stack, ItemModelResolver resolver, ItemDisplayContext displayContext, @Nullable ClientLevel level, @Nullable ItemOwner owner, int seed) {
// Set the identity used by the model
state.appendModelIdentityElement(this);

// Create a new layer
ItemStackRenderState.LayerRenderState layerState = state.newLayer();

// Sets the foil to use
if (stack.hasFoil()) {
layerState.setFoilType(ItemStackRenderState.FoilType.STANDARD);
state.appendModelIdentityElement(ItemStackRenderState.FoilType.STANDARD);
}


// Apply the tint sources
int tintSize = this.tints.size();
int[] tintLayers = layerState.prepareTintLayers(tintSize);

for (int idx = 0; idx < tintSize; idx++) {
int tintColor = this.tints.get(idx).calculate(stack, level, owner.asLivingEntity());
tintLayers[idx] = tintColor;
state.appendModelIdentityElement(tintColor);
}

// Computes the bounds of the model
// Used for GUI render bounds (when oversized) and item entity bobbing
layerState.setExtents(CuboidItemModelWrapper.computeExtents(this.quads.getAll()));

// Set the local transforms to apply for the client item
layerState.setLocalTransform(this.transformation);

// Set other common model properties
this.properties.applyToLayer(layerState, displayContext);

// Adds the quads to submit
layerState.prepareQuadList().addAll(this.quads.getAll());

// Set animated if it has the associated material flag
if (this.quads.hasMaterialFlag(BakedQuad.FLAG_ANIMATED)) {
layerState.setAnimated();
}
}
}

接下来是 ItemModel.Unbaked 实例。它应包含可从文件读取的数据,用于决定要传入物品模型的内容。它同样包含两个方法:bake,用于构造 ItemModel 实例;以及 type,用于定义读写文件时所使用的 MapCodec

public record ExampleModelWrapper(QuadCollection quads, List<ItemTintSource> tints, ModelRenderProperties properties, Matrix4fc transformation) implements ItemModel {

// ...

public record Unbaked(Identifier model, List<ItemTintSource> tints, Optional<Transformation> transformation) implements ItemModel.Unbaked {
// The map codec to register
public static final MapCodec<ExampleModelWrapper.Unbaked> MAP_CODEC = RecordCodecBuilder.mapCodec(instance ->
instance.group(
Identifier.CODEC.fieldOf("model").forGetter(ExampleModelWrapper.Unbaked::model),
ItemTintSources.CODEC.listOf().optionalFieldOf("tints", List.of()).forGetter(ExampleModelWrapper.Unbaked::tints)
Transformation.EXTENDED_CODEC.optionalFieldOf("transformation").forGetter(ExampleModelWrapper.Unbaked::transformation)
)
.apply(instance, ExampleModelWrapper.Unbaked::new)
);

@Override
public void resolveDependencies(ResolvableModel.Resolver resolver) {
// Mark all dependencies for this item model
resolver.markDependency(this.model);
}

@Override
public ItemModel bake(ItemModel.BakingContext context, Matrix4fc parentTransform) {
// Get the baked quads and return
ModelBaker baker = context.blockModelBaker();
ResolvedModel resolvedModel = baker.getModel(this.model);
TextureSlots slots = resolvedModel.getTopTextureSlots();

return new ExampleModelWrapper(
resolvedModel.bakeTopGeometry(slots, baker, BlockModelRotation.IDENTITY),
this.tints,
ModelRenderProperties.fromResolvedModel(baker, resolvedModel, slots),
Transformation.compose(parentTransform, this.transformation)
);
}

@Override
public MapCodec<ExampleModelWrapper.Unbaked> type() {
return MAP_CODEC;
}
}
}

然后,我们在 mod 事件总线上通过 RegisterItemModelsEvent 注册该 map codec。

// In some event handler class
@SubscribeEvent // on the mod event bus only on the physical client
public static void registerItemModels(RegisterItemModelsEvent event) {
event.register(
// The name to reference as the type
Identifier.fromNamespaceAndPath("examplemod", "render_type"),
// The map codec
ExampleModelWrapper.Unbaked.MAP_CODEC
)
}

最后,我们就可以在 JSON 中使用该 ItemModel,或将其作为数据生成流程的一部分。

// For some item 'examplemod:example_item'
// JSON at 'assets/examplemod/items/example_item.json'
{
"model": {
"type": "examplemod:render_type",
// Points to 'assets/examplemod/models/item/example_item.json'
"model": "examplemod:item/example_item",
// Any tints to apply to the model texture
"tints": []
}
}