Skip to main content
Version: 1.21.4

模型数据生成(Model Datagen)

和大多数 JSON 数据一样,方块与物品的模型(model)、以及它们所需的方块状态(blockstate)文件和 客户端物品,都可以通过 数据生成(datagen) 自动生成。所有这些流程都由原版的 ModelProvider 处理,NeoForge 通过 ExtendedModelTemplateBuilder 提供了扩展功能。由于方块模型和物品模型的 JSON 结构非常相似,所以数据生成的代码也基本一致。

模型模板(Model Templates)​

每一个模型都以 ModelTemplate 作为起点。对于原版来说,ModelTemplate 充当某个预生成模型文件的父级,定义了父模型、所需的纹理槽(texture slot),以及需要添加的文件后缀。在 NeoForge 中,ExtendedModelTemplate 通过 ExtendedModelTemplateBuilder 构建,允许开发者精确地生成模型的基础元素和各个面(face),同时支持 NeoForge 新增的功能。

可以通过 ModelTemplates 提供的方法或直接调用构造函数来创建一个 ModelTemplate。构造函数参数包括:父模型在 models 目录下的可选 ResourceLocation,应用于文件路径末尾的可选字符串后缀(例如按钮被按下时会加上 _pressed),以及一个或多个必须定义的 TextureSlot,否则数据生成过程会崩溃。TextureSlot 本质上是一个字符串,定义了 textures 映射中的纹理“键”。每个键还可以有一个父级 TextureSlot,如果当前槽没有指定纹理,则会向上查找。例如,TextureSlot#PARTICLE 会优先查找定义的 particle 纹理,如果没有,则查找 texture,最后查找 all。如果该槽及其父级都未定义,数据生成时会直接崩溃。

// 假设存在一个名为 '#base' 的纹理引用
// 可以通过指定 'base' 或 'all' 来解析
public static final TextureSlot BASE = TextureSlot.create("base", TextureSlot.ALL);

// 假设存在一个模型 'examplemod:block/example_template'
public static final ModelTemplate EXAMPLE_TEMPLATE = new ModelTemplate(
// 父模型的位置
Optional.of(
ModelLocationUtils.decorateBlockModelLocation("examplemod:example_template")
),
// 应用于所有使用该模板模型文件末尾的后缀
Optional.of("_example"),
// 必须定义的所有纹理槽
// 应尽可能具体,依据父模型中未定义的部分指定
TextureSlot.PARTICLE,
BASE
);

NeoForge 新增的 ExtendedModelTemplate 可以通过 ExtendedModelTemplateBuilder#builder 创建,也可以对已有的原版模板调用 ModelTemplate#extend 进行扩展。通过 #build 方法将 builder 构建为模板。builder 的方法可以让你完全控制模型 JSON 的生成过程:

方法(Method)效果(Effect)
#parent(ResourceLocation parent)设置父模型的位置,相对于 models 目录。
#suffix(String suffix)在模型文件路径的末尾追加该字符串。
#requiredTextureSlot(TextureSlot slot)添加一个必须在 TextureMapping 中定义的纹理槽,用于生成模型。
#renderType(ResourceLocation renderType)设置渲染类型。该方法有一个重载版本,参数为 String。可用值请参阅 RenderType 类。
transform(ItemDisplayContext type, Consumer<TransformVecBuilder> action)添加一个通过 consumer 配置的 TransformVecBuilder,用于设置模型的 display。
#ambientOcclusion(boolean ambientOcclusion)设置是否使用环境光遮蔽。
#guiLight(UnbakedModel.GuiLight light)设置 GUI 光照。可选值为 GuiLight.FRONT 或 GuiLight.SIDE。
#element(Consumer<ElementBuilder> action)添加一个新的 ElementBuilder(相当于向模型添加一个新的元素),通过 consumer 进行配置。
#customLoader(Supplier customLoaderFactory, Consumer action)使用指定的 factory,让此模型使用自定义加载器,并因此获得一个通过 consumer 配置的自定义加载器构建器。这会改变构建器的类型,因此可能会根据加载器的实现使用不同的方法。NeoForge 默认提供了几个自定义加载器,更多信息(包括数据生成)请参阅相关文档。
rootTransforms(Consumer<RootTransformsBuilder> action)配置模型在物品显示和方块状态变换前应用的变换,通过 consumer 进行设置。
tip

虽然可以通过数据生成(datagen)创建复杂的模型,但更推荐使用像 Blockbench 这样的建模软件来制作复杂模型,然后将导出的模型直接使用,或作为其他模型的父模型。

创建模型实例(Creating the Model Instance)​

现在我们已经有了一个 ModelTemplate,可以通过调用 ModelTemplate#create* 方法之一来生成模型本身。虽然每个 create 方法接受的参数略有不同,但它们的核心参数都是:一个表示文件名的 ResourceLocation,一个将 TextureSlot 映射到相对于 textures 目录的某个 ResourceLocation 的 TextureMapping,以及作为模型输出的 BiConsumer<ResourceLocation, ModelInstance>。然后,该方法会实际创建用于生成模型的 JsonObject,如果有重复项会抛出错误。

note

调用基础的 create 方法不会应用存储的后缀。只有那些接收方块或物品作为参数的 create* 方法才会应用后缀。

// 给定一个 BiConsumer<ResourceLocation, ModelInstance> modelOutput
// 假设有一个 DeferredBlock<Block> EXAMPLE_BLOCK
EXAMPLE_TEMPLATE.create(
// 在 'assets/minecraft/models/block/example_block_example.json' 处创建模型
EXAMPLE_BLOCK.get(),
// 在各个槽位定义贴图
new TextureMapping()
// "particle": "examplemod:item/example_block"
.put(TextureSlot.PARTICLE, TextureMapping.getBlockTexture(EXAMPLE_BLOCK.get()))
// "base": "examplemod:item/example_block_base"
.put(TextureSlot.BASE, TextureMapping.getBlockTexture(EXAMPLE_BLOCK.get(), "_base")),
// 生成的模型 json 的消费者
modelOutput
);

有时,生成的模型会使用类似的模型模板和贴图命名模式(例如,一个普通方块的贴图就是方块的名字)。在这种情况下,可以创建一个 TextureModel.Provider 来帮助消除冗余。这个 provider 实际上是一个函数式接口,接收一个 Block 并返回一个 TexturedModel(即 ModelTemplate 和 TextureMapping 的组合),用于生成模型。该接口通过 TexturedModel#createDefault 构造,它接收一个将 Block 映射为其 TextureMapping 的函数,以及要使用的 ModelTemplate。然后可以通过调用 TexturedModel.Provider#create 并传入要生成的 Block,来生成模型。

public static final TexturedModel.Provider EXAMPLE_TEMPLATE_PROVIDER = TexturedModel.createDefault(
// 方块到材质的映射
block -> new TextureMapping()
.put(TextureSlot.PARTICLE, TextureMapping.getBlockTexture(block))
.put(TextureSlot.BASE, TextureMapping.getBlockTexture(block, "_base")),
// 用于生成的模板
EXAMPLE_TEMPLATE
);

// 假设有一个 BiConsumer<ResourceLocation, ModelInstance> modelOutput
// 假设存在一个 DeferredBlock<Block> EXAMPLE_BLOCK
EXAMPLE_TEMPLATE_PROVIDER.create(
// 在 'assets/minecraft/models/block/example_block_example.json' 位置创建模型
EXAMPLE_BLOCK.get(),
// 生成的模型 json 的消费者
modelOutput
)

ModelProvider​

无论是方块(block)还是物品(item)的模型数据生成(datagen),都依赖于 registerModels 方法提供的生成器(generator),分别名为 BlockModelGenerators 和 ItemModelGenerators。每个生成器不仅会生成模型 JSON,还会生成任何额外所需的文件(如 blockstate、客户端物品等)。这些生成器内置了多种辅助方法,可以将所有相关文件的构建批量合并为一个易于使用的方法,例如 ItemModelGenerators#generateFlatItem 用于创建一个基础的 item/generated 模型,或 BlockModelGenerators#createTrivialCube 用于生成一个基础的 block/cube_all 模型。

public class ExampleModelProvider extends ModelProvider {

public ExampleModelProvider(PackOutput output) {
// 将 "examplemod" 替换为你自己的 mod id
super(output, "examplemod");
}

@Override
protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) {
// 在这里生成模型及其相关文件
}
}

和所有数据提供器(data provider)一样,别忘了在事件中注册你的 provider:

@SubscribeEvent
public static void gatherData(GatherDataEvent.Client event) {
event.createProvider(ExampleModelProvider::new);
}

方块模型数据生成(Block Model Datagen)​

现在,要实际生成 blockstate 和方块模型文件,你可以在 ModelProvider#registerModels 中调用 BlockModelGenerators 里的众多公共方法之一,或者你也可以自己将生成的文件传递给 blockStateOutput(用于 blockstate 文件)、itemModelOutput(用于非基础客户端物品)、以及 modelOutput(用于模型 JSON)。

note

如果你的方块有一个已注册的 BlockItem,但没有生成对应的客户端物品(client item),ModelProvider 会自动使用默认的方块模型路径 assets/<namespace>/models/block/<path>.json 作为其模型来生成客户端物品。

public class ExampleModelProvider extends ModelProvider {

public ExampleModelProvider(PackOutput output) {
// 将 "examplemod" 替换为你自己的模组 id。
super(output, "examplemod");
}

@Override
protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) {
// 占位符,实际使用时应替换为真实的值。上文已介绍如何使用模型构建器,
// 下文会介绍模型构建器提供的辅助方法。
Block block = MyBlocksClass.EXAMPLE_BLOCK.get();

// 创建一个每一面都使用相同纹理的简单方块模型。
// 纹理文件必须放在 assets/<namespace>/textures/block/<path>.png,
// 其中 <namespace> 和 <path> 分别是方块注册名的命名空间和路径。
// 适用于大多数(完整)方块,例如木板、圆石或砖块。
blockModels.createTrivialCube(block);

// 接收 `TexturedModel.Provider` 的重载版本。
blockModels.createTrivialBlock(block, EXAMPLE_TEMPLATE_PROVIDER);

// 方块物品会自动生成模型
// 但如果你想为其他物品(如扁平物品)生成模型,可以这样做
blockModels.registerSimpleFlatItemModel(block);

// 添加一个原木方块模型。需要两个纹理文件,分别位于 assets/<namespace>/textures/block/<path>.png 和
// assets/<namespace>/textures/block/<path>_top.png,分别对应侧面和顶部纹理。
// 注意,这里的方块参数必须是 RotatedPillarBlock 类型,这也是原版原木使用的类。
blockModels.woodProvider(block).log(block);

// 类似于 WoodProvider#logWithHorizontal,用于石英柱子等类似方块。
blockModels.createRotatedPillarWithHorizontalVariant(block, TexturedModel.COLUMN_ALT, TexturedModel.COLUMN_HORIZONTAL_ALT);

// 使用 `ExtendedModelTemplate` 指定渲染类型。
blockModels.createRotatedPillarWithHorizontalVariant(block,
TexturedModel.COLUMN_ALT.updateTemplate(template ->
template.extend().renderType("minecraft:cutout").build()
),
TexturedModel.COLUMN_HORIZONTAL_ALT.updateTemplate(template ->
template.extend().renderType(this.mcLocation("cutout_mipped")).build()
)
);

// 指定一个可以水平旋转的方块模型,包含侧面纹理、正面纹理和顶部纹理。
// 底部同样会使用侧面纹理。如果你不需要正面或顶部纹理,
// 可以多次传入侧面纹理。例如用于熔炉等类似方块。
blockModels.createHorizontallyRotatedBlock(
block,
TexturedModel.Provider.ORIENTABLE_ONLY_TOP.updateTexture(mapping ->
mapping.put(TextureSlot.SIDE, this.modLocation("block/example_texture_side"))
.put(TextureSlot.FRONT, this.modLocation("block/example_texture_front"))
.put(TextureSlot.TOP, this.modLocation("block/example_texture_top"))
)
);

// 指定一个可水平旋转且依附于某个面的方块模型,例如按钮。
// 会考虑将方块放置在地面或天花板上,并相应地旋转模型。
blockModels.familyWithExistingFullBlock(block).button(block);

// 创建一个用于 blockstate 文件的模型
ResourceLocation modelLoc = TexturedModel.CUBE.create(block, blockModels.modelOutput);

// 基础的单一变体模型
blockModels.blockStateOutput.accept(
MultiVariantGenerator.multiVariant(
block,
Variant.variant()
// 设置并生成模型
.with(VariantProperties.MODEL, modelLoc)
// 设置绕 x 轴和 y 轴的旋转
.with(VariantProperties.X_ROT, VariantProperties.Rotation.R90)
.with(VariantProperties.Y_ROT, VariantProperties.Rotation.R180)
// 启用 uvlock
.with(VariantProperties.UV_LOCK, true)
// 设置权重
.with(VariantProperties.WEIGHT, 5)
)
);

// 根据方块状态属性添加一个或多个模型
blockModels.blockStateOutput.accept(
MultiVariantGenerator.multiVariant(block)
.with(
// 使用属性分派,可针对多个属性选择
PropertyDispatch.property(BlockStateProperties.AXIS)
// 选择属性并应用模型
.select(Direction.Axis.Y, Variant.variant().with(VariantProperties.MODEL, modelLoc))
.select(
Direction.Axis.Z,
Variant.variant().with(VariantProperties.MODEL, modelLoc)
.with(VariantProperties.X_ROT, VariantProperties.Rotation.R90)
)
.select(
Direction.Axis.X,
Variant.variant()
.with(VariantProperties.MODEL, modelLoc)
.with(VariantProperties.X_ROT, VariantProperties.Rotation.R90)
.with(VariantProperties.Y_ROT, VariantProperties.Rotation.R90)
)
)
);

// 通过属性分派修改简单模型设置
// 例如:根据方块的水平朝向旋转模型
blockModels.blockStateOutput.accept(
MultiVariantGenerator.multiVariant(
block,
Variant.variant().with(VariantProperties.MODEL, modelLoc)
.with(BlockModelGenerators.createHorizontalFacingDispatch())
)
);

// 生成一个多部件(multipart)模型
blockModels.blockStateOutput.accept(
MultiPartGenerator.multiPart(block)
// 提供基础模型
.with(Variant.variant().with(VariantProperties.MODEL, modelLoc))
// 添加变体出现的条件
.with(
// 添加应用条件
Condition.or(
// 至少有一个条件为 true 时应用
Condition.condition().term(BlockStateProperties.FACING, Direction.NORTH, Direction.SOUTH)
// 可以根据需要嵌套任意多的条件或组合
Condition.and(
Condition.condition().term(BlockStateProperties.FACING, Direction.NORTH)
)
),
// 提供要生成的变体
Variant.variant().with(VariantProperties.MODEL, modelLoc)
)
);
}
}

物品模型数据生成(Item Model Datagen)​

生成物品模型(item models)要简单得多,这主要归功于 ItemModelGenerators 和 ItemModelUtils 中用于属性信息的各种辅助方法。与前文类似,你可以在 ModelProvider#registerModels 方法中调用 ItemModelGenerators 提供的众多公共方法之一,或者对于更复杂的客户端物品,也可以自行将生成的文件传递给 itemModelOutput,模型 JSON 则传递给 modelOutput。

public class ExampleModelProvider extends ModelProvider {

public ExampleModelProvider(PackOutput output) {
// 将 "examplemod" 替换为你自己的 mod id。
super(output, "examplemod");
}

@Override
protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) {
// 最常见的物品
// 使用 item/generated,layer0 纹理为物品名
itemModels.generateFlatItem(MyItemsClass.EXAMPLE_ITEM.get(), ModelTemplates.FLAT_ITEM);

// 类似弓的物品
ItemModel.Unbaked bow = ItemModelUtils.plainModel(ModelLocationUtils.getModelLocation(MyItemsClass.EXAMPLE_ITEM.get()));
ItemModel.Unbaked pullingBow0 = ItemModelUtils.plainModel(this.createFlatItemModel(MyItemsClass.EXAMPLE_ITEM.get(), "_pulling_0", ModelTemplates.BOW));
ItemModel.Unbaked pullingBow1 = ItemModelUtils.plainModel(this.createFlatItemModel(MyItemsClass.EXAMPLE_ITEM.get(), "_pulling_1", ModelTemplates.BOW));
ItemModel.Unbaked pullingBow2 = ItemModelUtils.plainModel(this.createFlatItemModel(MyItemsClass.EXAMPLE_ITEM.get(), "_pulling_2", ModelTemplates.BOW));
this.itemModelOutput.accept(
MyItemsClass.EXAMPLE_ITEM.get(),
// 针对物品的条件模型
ItemModelUtils.conditional(
// 检查物品是否正在被使用
ItemModelUtils.isUsingItem(),
// 如果为真,根据使用时长选择模型
ItemModelUtils.rangeSelect(
new UseDuration(false),
// 应用于阈值的比例因子
0.05F,
pullingBow0,
// 阈值为 0.65 时
ItemModelUtils.override(pullingBow1, 0.65F),
// 阈值为 0.9 时
ItemModelUtils.override(pullingBow2, 0.9F)
),
// 如果为假,使用基础弓模型
bow
)
);
}
}

环境光遮蔽 Blockbench 编辑器 物品(items) 自定义模型加载器(Custom Model Loader) 数据生成(Data Generation) 元素(elements)