模型数据生成
与多数 JSON 数据一样,方块模型、物品模型,以及必需的方块状态文件和客户端物品,都可以通过数据生成创建。相关工作由原版 ModelProvider 处理,NeoForge 则通过 ExtendedModelTemplateBuilder 提供扩展。由于方块和物品模型的 JSON 相似,它们的数据生成代码也比较接近。
模型模板
每个模型都从 ModelTemplate 开始。原版模板以预先生成的模型文件为基础,定义父模型、必需的纹理槽和文件名后缀。NeoForge 的 ExtendedModelTemplate 通过 ExtendedModelTemplateBuilder 构建,可以一直生成到底层元素和面,并使用 NeoForge 新增功能。
可以调用 ModelTemplates 中的方法或构造函数创建模板。构造函数接受相对于 models 目录的可选父模型 Identifier、追加到路径末尾的可选字符串(例如按下的按钮使用 _pressed),以及生成时必须定义的可变数量 TextureSlot。纹理槽本质上是 textures 映射中的键,也可以指定父槽,在自身未指定纹理时回退。例如 TextureSlot#PARTICLE 先查 particle,再查 texture,最后查 all。如果槽及其父槽都没有定义,数据生成会崩溃。
// Assumes there is a texture referenced as '#base'
// Can be resolved by either specifying 'base' or 'all'
public static final TextureSlot BASE = TextureSlot.create("base", TextureSlot.ALL);
// Assume there exists some model 'examplemod:block/example_template'
public static final ModelTemplate EXAMPLE_TEMPLATE = new ModelTemplate(
// The parent model location
Optional.of(
ModelLocationUtils.decorateBlockModelLocation("examplemod:example_template")
),
// The suffix to apply to the end of any model that uses this template
Optional.of("_example"),
// All texture slots that must be defined
// Should be as specific as possible based on what's undefined in the parent model
TextureSlot.PARTICLE,
BASE
);
NeoForge 的扩展模板可以用 ExtendedModelTemplateBuilder#builder 创建,也可以对已有原版模板调用 ModelTemplate#extend,最后调用 #build 生成模板。构建器提供完整的模型 JSON 控制:
| 方法 | 效果 |
|---|---|
#parent(Identifier parent) |
设置相对于 models 目录的父模型位置。 |
#suffix(String suffix) |
将字符串追加到模型文件路径末尾。 |
#requiredTextureSlot(TextureSlot slot) |
添加生成时必须在 TextureMapping 定义的纹理槽。 |
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) |
通过 consumer 配置并添加新 ElementBuilder,相当于添加模型元素。 |
#customLoader(Supplier customLoaderFactory, Consumer action) |
用工厂指定自定义加载器及其构建器,再由 consumer 配置。此操作会改变构建器类型,因此可用方法取决于加载器实现。NeoForge 内置若干加载器,详见链接文档,包含数据生成说明。 |
#rootTransforms(Consumer<RootTransformsBuilder> action) |
配置在物品显示变换和方块状态变换之前应用的根变换。 |
建议 数据生成虽能创建复杂模型,但更建议使用 Blockbench 等建模软件制作复杂模型,再直接使用导出的文件,或将其作为其他模型的父模型。
创建模型实例
有了模板后,调用某个 ModelTemplate#create* 方法即可生成模型。各方法参数不同,但核心都包括:表示文件名的 Identifier、将 TextureSlot 映射到相对于 textures 目录的纹理标识符的 TextureMapping,以及作为模型输出的 BiConsumer<Identifier, ModelInstance>。方法创建用于生成模型的 JsonObject,若有重复则报错。
提示 基础
create方法不会应用已保存的后缀。只有接受方块或物品的create*方法会应用后缀。
// Given some BiConsumer<Identifier, ModelInstance> modelOutput
// Assume there is a DeferredBlock<Block> EXAMPLE_BLOCK
EXAMPLE_TEMPLATE.create(
// Creates the model at 'assets/minecraft/models/block/example_block_example.json'
EXAMPLE_BLOCK.get(),
// Define textures in slots
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")),
// The consumer of the generated model json
modelOutput
);
一些生成模型使用相似模板及纹理命名规则,例如普通方块纹理直接使用方块名。这时可以创建 TexturedModel.Provider 减少重复。它是接收 Block、返回 TexturedModel(模板与纹理映射的组合)的函数式接口。通过 TexturedModel#createDefault 创建,传入从方块到纹理映射的函数以及模板,再用 TexturedModel.Provider#create 传入目标方块生成模型。
public static final TexturedModel.Provider EXAMPLE_TEMPLATE_PROVIDER = TexturedModel.createDefault(
// Block to texture mapping
block -> new TextureMapping()
.put(TextureSlot.PARTICLE, TextureMapping.getBlockTexture(block))
.put(TextureSlot.BASE, TextureMapping.getBlockTexture(block, "_base")),
// The template to generate from
EXAMPLE_TEMPLATE
);
// Given some BiConsumer<Identifier, ModelInstance> modelOutput
// Assume there is a DeferredBlock<Block> EXAMPLE_BLOCK
EXAMPLE_TEMPLATE_PROVIDER.create(
// Creates the model at 'assets/minecraft/models/block/example_block_example.json'
EXAMPLE_BLOCK.get(),
// The consumer of the generated model json
modelOutput
);
ModelProvider
方块和物品的数据生成分别使用 registerModels 提供的 BlockModelGenerators 和 ItemModelGenerators。它们同时生成模型 JSON 及方块状态、客户端物品等所需文件,并提供便捷方法,将一整组文件的构建合为一次调用。例如,ItemModelGenerators#generateFlatItem 配合 ModelTemplates#FLAT_ITEM 可以创建基础 item/generated 模型;BlockModelGenerators#createTrivialCube 可以创建基础 block/cube_all 模型。
public class ExampleModelProvider extends ModelProvider {
public ExampleModelProvider(PackOutput output) {
// Replace "examplemod" with your own mod id.
super(output, "examplemod");
}
@Override
protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) {
// Generate models and associated files here
}
}
与其他数据提供器一样,别忘了通过事件注册:
@SubscribeEvent // on the mod event bus
public static void gatherData(GatherDataEvent.Client event) {
event.createProvider(ExampleModelProvider::new);
}
方块模型数据生成
实际生成方块状态和模型时,可以在 ModelProvider#registerModels 中调用 BlockModelGenerators 的各种公共方法,也可以自行把生成内容传给对应输出:方块状态使用 blockStateOutput,非简单客户端物品使用 itemModelOutput,模型 JSON 使用 modelOutput。
提示 若方块注册了关联的
BlockItem,但没有生成客户端物品,ModelProvider会自动创建,使用默认方块模型assets/<namespace>/models/block/<path>.json。
public class ExampleModelProvider extends ModelProvider {
public ExampleModelProvider(PackOutput output) {
// Replace "examplemod" with your own mod id.
super(output, "examplemod");
}
@Override
protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) {
// Placeholders, their usages should be replaced with real values. See above for how to use the model builder,
// and below for the helpers the model builder offers.
Block block = MyBlocksClass.EXAMPLE_BLOCK.get();
// Create a simple block model with the same texture on each side.
// The texture must be located at assets/<namespace>/textures/block/<path>.png, where
// <namespace> and <path> are the block's registry name's namespace and path, respectively.
// Used by the majority of (full) blocks, such as planks, cobblestone or bricks.
blockModels.createTrivialCube(block);
// Overload that accepts a `TexturedModel.Provider` to use.
blockModels.createTrivialBlock(block, EXAMPLE_TEMPLATE_PROVIDER);
// Block items have a model generated automatically
// But let's assume you want to generate a different item, such as a flat item
blockModels.registerSimpleFlatItemModel(block);
// Adds a log block model. Requires two textures at assets/<namespace>/textures/block/<path>.png and
// assets/<namespace>/textures/block/<path>_top.png, referencing the side and top texture, respectively.
// Note that the block input here is limited to RotatedPillarBlock, which is the class vanilla logs use.
blockModels.woodProvider(block).log(block);
// Like WoodProvider#logWithHorizontal. Used by quartz pillars and similar blocks.
blockModels.createRotatedPillarWithHorizontalVariant(block, TexturedModel.COLUMN_ALT, TexturedModel.COLUMN_HORIZONTAL_ALT);
// Using the `ExtendedModelTemplate` to specify the render type to use.
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()
)
);
// Specifies a horizontally-rotatable block model with a side texture, a front texture, and a top texture.
// The bottom will use the side texture as well. If you don't need the front or top texture,
// just pass in the side texture twice. Used by e.g. furnaces and similar blocks.
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"))
)
);
// Specifies a horizontally-rotatable block model that is attached to a face, e.g. for buttons.
// Accounts for placing the block on the ground and on the ceiling, and rotates them accordingly.
blockModels.familyWithExistingFullBlock(block).button(block);
// Create a model to use for blockstatefiles
Identifier modelLoc = TexturedModel.CUBE.create(block, blockModels.modelOutput);
// Create a common variant to transform
Variant variant = new Variant(modelLoc);
// Basic single variant model
blockModels.blockStateOutput.accept(
MultiVariantGenerator.dispatch(
block,
new MultiVariant(
WeightedList.of(
new Weighted<>(
// Set model
variant
// Set rotations around the x and y axes
.with(VariantMutator.X_ROT.withValue(Quadrant.R90))
.with(VariantMutator.Y_ROT.withValue(Quadrant.R180))
// Set a uvlock
.with(VariantMutator.UV_LOCK.withValue(true)),
// Set a weight
5
)
)
)
)
);
// Add one or multiple models based on the block state properties
blockModels.blockStateOutput.accept(
MultiVariantGenerator.dispatch(
block,
// Create the basic multi-variant
BlockModelGenerators.variant(variant)
).with(
// Apply a property dispatch
// Will mutate the variant based on the provided mutators
PropertyDispatch.modify(BlockStateProperties.AXIS)
.select(Direction.Axis.Y, BlockModelGenerators.NOP)
.select(Direction.Axis.Z, BlockModelGenerators.X_ROT_90)
.select(Direction.Axis.X, BlockModelGenerators.X_ROT_90.then(BlockModelGenerators.Y_ROT_90))
)
);
// Generate a multipart
blockModels.blockStateOutput.accept(
MultiPartGenerator.multiPart(block)
// Provide the base model
.with(BlockModelGenerators.variant(variant))
// Add conditions for variant to appear
.with(
// Add conditions to apply
new CombinedCondition(
CombinedCondition.Operation.OR,
List.of(
// Where at least one of the conditions are true
BlockModelGenerators.condition().term(BlockStateProperties.FACING, Direction.NORTH, Direction.SOUTH)
// Can nest as many conditions or groups as necessary
new CombinedCondition(
CombinedCondition.Operation.AND,
List.of(
BlockModelGenerators.condition().term(BlockStateProperties.FACING, Direction.NORTH)
)
)
)
),
// Supply variant to mutate
BlockModelGenerators.variant(variant)
)
);
}
}
物品模型数据生成
由于 ItemModelGenerators 和处理属性信息的 ItemModelUtils 提供了许多便捷方法,物品模型生成简单得多。与上文类似,可以在 ModelProvider#registerModels 中调用生成器的公共方法,也可以自行把非简单客户端物品输出给 itemModelOutput,把模型 JSON 输出给 modelOutput。
public class ExampleModelProvider extends ModelProvider {
public ExampleModelProvider(PackOutput output) {
// Replace "examplemod" with your own mod id.
super(output, "examplemod");
}
@Override
protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) {
// The most common item
// item/generated with the layer0 texture as the item name
itemModels.generateFlatItem(MyItemsClass.EXAMPLE_ITEM.get(), ModelTemplates.FLAT_ITEM);
// A bow-like 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(),
// Conditional model for item
ItemModelUtils.conditional(
// Checks if item is being used
ItemModelUtils.isUsingItem(),
// When true, select model based on use duration
ItemModelUtils.rangeSelect(
new UseDuration(false),
// Scalar to apply to the thresholds
0.05F,
pullingBow0,
// Threshold when 0.65
ItemModelUtils.override(pullingBow1, 0.65F),
// Threshold when 0.9
ItemModelUtils.override(pullingBow2, 0.9F)
),
// When false, use the base bow model
bow
),
// 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
)
);
}
}
来源: NeoForged/Documentation(26.1)。中文翻译依据提交
4a680cdd4d15。