模型数据生成
与大多数 JSON 数据一样,方块和物品模型可以通过数据生成创建。两者有一些共通内容,数据生成代码也因此共享部分实现。
模型数据生成类
ModelBuilder
每个模型都从某种 ModelBuilder 开始,通常根据生成目标使用 BlockModelBuilder 或 ItemModelBuilder。它包含模型的父模型、纹理、元素、变换、加载器等所有属性,可通过方法设置:
| 方法 | 作用 |
|---|---|
#texture(String key, ResourceLocation texture) |
添加指定键与纹理位置的纹理变量。第二个参数也有字符串重载。 |
#renderType(ResourceLocation renderType) |
设置渲染类型,也有字符串重载。有效值见 RenderType 类。 |
#ao(boolean ao) |
设置是否使用环境光遮蔽。 |
#guiLight(GuiLight light) |
设置 GUI 光照,可为 GuiLight.FRONT 或 GuiLight.SIDE。 |
#element() |
添加并返回新的 ElementBuilder,相当于向模型添加元素,可继续修改。 |
#transforms() |
返回 TransformVecBuilder,用于设置模型的 display。 |
#customLoader(BiFunction customLoaderFactory) |
通过给定工厂使用自定义加载器及其构建器。这会改变构建器类型,具体可用方法由加载器决定。NeoForge 提供若干内置加载器,含数据生成说明,详见链接文章。 |
建议 虽然可以用数据生成创建复杂模型,但更建议在 Blockbench 等建模软件中制作,再直接使用导出的模型,或将其作为其他模型的父模型。
ModelProvider
方块和物品模型数据生成分别使用 ModelProvider 子类 BlockModelProvider 与 ItemModelProvider。物品模型直接继承后者;方块模型则使用 BlockStateProvider,其内部 BlockModelProvider 可通过 BlockStateProvider#models() 访问。它还包含内部 ItemModelProvider,可通过 BlockStateProvider#itemModels() 访问。最重要的方法是 getBuilder(String path),返回指定位置的 BlockModelBuilder(或 ItemModelBuilder)。
ModelProvider 还有多种辅助方法。其中最重要的可能是 withExistingParent(String name, ResourceLocation parent):通过 getBuilder(name) 返回新构建器,并将指定资源位置设为父模型。另两个常用方法是 mcLoc(String name) 与 modLoc(String name),前者创建 minecraft 命名空间和给定路径的资源位置,后者改用提供器的 Mod ID(通常是自己的 Mod ID)。此外还为台阶、楼梯、栅栏、门等提供 #withExistingParent 的快捷方法。
ModelFile
最后一个重要类是 ModelFile,表示磁盘上的模型 JSON。它是抽象类,有两个内部子类:ExistingModelFile 会通过 ExistingFileHelper 验证文件存在;UncheckedModelFile 则不检查,直接假定文件存在。ModelBuilder 本身也属于 ModelFile。
方块模型数据生成
要生成方块状态和方块模型文件,继承 BlockStateProvider 并重写 registerStatesAndModels()。方块模型始终放在 models/block 子文件夹,但引用相对于 models,所以必须带 block/ 前缀。大多数情况下可选用预定义辅助方法:
public class MyBlockStateProvider extends BlockStateProvider {
// Parameter values are provided by GatherDataEvent.
public MyBlockStateProvider(PackOutput output, ExistingFileHelper existingFileHelper) {
// Replace "examplemod" with your own mod id.
super(output, "examplemod", existingFileHelper);
}
@Override
protected void registerStatesAndModels() {
// 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.
ModelFile exampleModel = models().withExistingParent("example_model", this.mcLoc("block/cobblestone"));
Block block = MyBlocksClass.EXAMPLE_BLOCK.get();
ResourceLocation exampleTexture = modLoc("block/example_texture");
ResourceLocation bottomTexture = modLoc("block/example_texture_bottom");
ResourceLocation topTexture = modLoc("block/example_texture_top");
ResourceLocation sideTexture = modLoc("block/example_texture_front");
ResourceLocation frontTexture = modLoc("block/example_texture_front");
// 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.
simpleBlock(block);
// Overload that accepts a model file to use.
simpleBlock(block, exampleModel);
// Overload that accepts one or multiple (vararg) ConfiguredModel objects.
// See below for more info about ConfiguredModel.
simpleBlock(block, ConfiguredModel.builder().build());
// Adds an item model file with the block's name, parenting the given model file, for a block item to pick up.
simpleBlockItem(block, exampleModel);
// Shorthand for calling #simpleBlock() (model file overload) and #simpleBlockItem.
simpleBlockWithItem(block, exampleModel);
// 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.
logBlock(block);
// Like #logBlock, but the textures are named <path>_side.png and <path>_end.png instead of
// <path>.png and <path>_top.png, respectively. Used by quartz pillars and similar blocks.
// Has an overload that allow you to specify a different texture base name, that is then suffixed
// with _side and _end as needed, an overload that allows you to specify two resource locations
// for the side and end textures, and an overload that allows specifying side and end model files.
axisBlock(block);
// Variants of #logBlock and #axisBlock that additionally allow for render types to be specified.
// Comes in string and resource location variants for the render type,
// in all combinations with all variants of #logBlock and #axisBlock.
logBlockWithRenderType(block, "minecraft:cutout");
axisBlockWithRenderType(block, mcLoc("cutout_mipped"));
// 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.
horizontalBlock(block, sideTexture, frontTexture, topTexture);
// Specifies a horizontally-rotatable block model with a model file that will be rotated as needed.
// Has an overload that instead of a model file accepts a Function<BlockState, ModelFile>,
// allowing for different rotations to use different models. Used e.g. by the stonecutter.
horizontalBlock(block, exampleModel);
// Specifies a horizontally-rotatable block model that is attached to a face, e.g. for buttons or levers.
// Accounts for placing the block on the ground and on the ceiling, and rotates them accordingly.
// Like #horizontalBlock, has an overload that accepts a Function<BlockState, ModelFile> instead.
horizontalFaceBlock(block, exampleModel);
// Similar to #horizontalBlock, but for blocks that are rotatable in all directions, including up and down.
// Again, has an overload that accepts a Function<BlockState, ModelFile> instead.
directionalBlock(block, exampleModel);
}
}
BlockStateProvider 还为以下常见方块模型提供辅助方法:楼梯、台阶、按钮、压力板、告示牌、栅栏、栅栏门、墙、玻璃板、门和活板门。
有时方块状态不需要特殊处理,但模型需要。此时可使用 BlockStateProvider#models() 返回的 BlockModelProvider 中的额外方法。它们的首个参数都是名称,大多与完整立方体有关,通常作为 simpleBlock 等方法的模型参数使用。除了配合 BlockStateProvider 的方法外,还包括:
withExistingParent:返回带有指定父模型的新构建器。父模型必须已存在,或先于当前模型创建。getExistingFile:在提供器的ExistingFileHelper中查找并返回ModelFile,不存在则抛出IllegalStateException。singleTexture:接受父模型和一个纹理位置,设置父模型及texture纹理变量。sideBottomTop:接受父模型与侧面、底面、顶面三个纹理位置。cube:接受六个面的纹理位置,返回对应纹理的完整立方体。cubeAll:接受一个纹理位置,将它应用到完整立方体六面,可视为singleTexture与cube的组合。cubeTop:接受两个纹理位置,第一个用于侧面和底面,第二个用于顶面。cubeBottomTop:接受侧面、底面和顶面三个纹理位置,可视为cube与sideBottomTop的组合。cubeColumn与cubeColumnHorizontal:接受侧面与端面两个纹理,返回竖直或水平柱状立方体,供#logBlock、#axisBlock及其变体使用。orientable:接受侧面、正面、顶面三个纹理,返回带独立正面纹理的立方体。orientableVertical:省略顶面参数,顶面也使用侧面纹理。orientableWithBottom:在正面与顶面参数之间增加底面纹理参数。crop:使用指定纹理返回作物状模型,如原版四种作物。cross:使用指定纹理返回交叉模型,如花、树苗和许多植物。torch:使用指定纹理返回火把模型。wall_torch:使用指定纹理返回墙上火把模型(墙上火把与地面火把是不同方块)。carpet:使用指定纹理返回地毯模型。
最后,在事件中注册方块状态提供器:
@SubscribeEvent // on the mod event bus
public static void gatherData(GatherDataEvent event) {
DataGenerator generator = event.getGenerator();
PackOutput output = generator.getPackOutput();
ExistingFileHelper existingFileHelper = event.getExistingFileHelper();
// other providers here
generator.addProvider(
event.includeClient(),
new MyBlockStateProvider(output, existingFileHelper)
);
}
ConfiguredModel.Builder
如果默认辅助方法无法满足需求,可以直接通过 ConfiguredModel.Builder 构建模型对象,再交给 VariantBlockStateBuilder 创建 variants 状态文件,或交给 MultiPartBlockStateBuilder 创建 multipart 状态文件:
// Create a ConfiguredModel.Builder. Alternatively, you can use one of the ways demonstrated below
// (VariantBlockStateBuilder.PartialBlockstate#modelForState or MultiPartBlockStateBuilder#part) where applicable.
ConfiguredModel.Builder<?> builder = ConfiguredModel.builder()
// Use a model file. As mentioned previously, can either be an ExistingModelFile, an UncheckedModelFile,
// or some sort of ModelBuilder. See above for how to use ModelBuilder.
.modelFile(models().withExistingParent("example_model", this.mcLoc("block/cobblestone")))
// Set rotations around the x and y axes.
.rotationX(90)
.rotationY(180)
// Set a uvlock.
.uvlock(true)
// Set a weight.
.weight(5);
// Build the configured model. The return type is an array
// to account for multiple possible models in the same blockstate.
ConfiguredModel[] model = builder.build();
// Get a variant block state builder.
VariantBlockStateBuilder variantBuilder = getVariantBuilder(MyBlocksClass.EXAMPLE_BLOCK.get());
// Create a partial state and set properties on it.
VariantBlockStateBuilder.PartialBlockstate partialState = variantBuilder.partialState();
// Add one or multiple models for a partial blockstate. The models are a vararg parameter.
variantBuilder.addModels(partialState,
// Specify at least one ConfiguredModel.Builder, as seen above. Create through #modelForState().
partialState.modelForState()
.modelFile(models().withExistingParent("example_variant_model", this.mcLoc("block/cobblestone")))
.uvlock(true)
);
// Alternatively, forAllStates(Function<BlockState, ConfiguredModel[]>) creates a model for every state.
// The passed function will be called once for each possible state.
variantBuilder.forAllStates(state -> {
// Return a ConfiguredModel depending on the state's properties.
// For example, the following code will rotate the model depending on the horizontal rotation of the block.
return ConfiguredModel.builder()
.modelFile(models().withExistingParent("example_variant_model", this.mcLoc("block/cobblestone")))
.rotationY((int) state.getValue(BlockStateProperties.HORIZONTAL_FACING).toYRot())
.build();
});
// Get a multipart block state builder.
MultiPartBlockStateBuilder multipartBuilder = getMultipartBuilder(MyBlocksClass.EXAMPLE_BLOCK.get());
// Add a new part. Starts with .part() and ends with .end().
multipartBuilder.part()
// Step one: Build the model. multipartBuilder.part() returns a ConfiguredModel.Builder,
// meaning that all methods seen above can be used here as well.
.modelFile(models().withExistingParent("example_multipart_model", this.mcLoc("block/cobblestone")))
// Call .addModel(). Now that the model is built, we can proceed to step two: add the part data.
.addModel()
// Add a condition for the part. Requires a property
// and at least one property value; property values are a vararg.
.condition(BlockStateProperties.FACING, Direction.NORTH, Direction.SOUTH)
// Set the multipart conditions to be ORed instead of the default ANDing.
.useOr()
// Creates a nested condition group.
.nestedGroup()
// Adds a condition to the nested group.
.condition(BlockStateProperties.FACING, Direction.NORTH)
// Sets only this condition group to be ORed instead of ANDed.
.useOr()
// Creates yet another nested condition group. There is no limit on how many groups can be nested.
.nestedGroup()
// Ends the nested condition group, returning to the owning part builder or condition group level.
// Called twice here since we currently have two nested groups.
.endNestedGroup()
.endNestedGroup()
// End the part builder and add the resulting part to the multipart builder.
.end();
物品模型数据生成
物品模型生成简单得多,主要因为可以直接操作 ItemModelProvider,而无需 BlockStateProvider 这样的中间类。物品没有对应的方块状态文件,而是直接使用模型。
同样先创建类并继承基础提供器,此处为 ItemModelProvider。因为已经直接位于 ModelProvider 子类中,所有 models() 调用都改为 this,或直接省略。
public class MyItemModelProvider extends ItemModelProvider {
public MyItemModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) {
super(output, "examplemod", existingFileHelper);
}
@Override
protected void registerModels() {
// Block items generally use their corresponding block models as parent.
withExistingParent(MyItemsClass.EXAMPLE_BLOCK_ITEM.getId().toString(), modLoc("block/example_block"));
// Items generally use a simple parent and one texture. The most common parents are item/generated and item/handheld.
// In this example, the item texture would be located at assets/examplemod/textures/item/example_item.png.
// If you want a more complex model, you can use getBuilder() and then work from that, like you would with block models.
withExistingParent(MyItemsClass.EXAMPLE_ITEM.getId().toString(), mcLoc("item/generated")).texture("layer0", "item/example_item");
// The above line is so common that there is a shortcut for it. Note that the item registry name and the
// texture path, relative to textures/item, must match.
basicItem(MyItemsClass.EXAMPLE_ITEM.get());
}
}
与其他数据提供器一样,记得在事件中注册:
@SubscribeEvent // on the mod event bus
public static void gatherData(GatherDataEvent event) {
DataGenerator generator = event.getGenerator();
PackOutput output = generator.getPackOutput();
ExistingFileHelper existingFileHelper = event.getExistingFileHelper();
// other providers here
generator.addProvider(
event.includeClient(),
new MyItemModelProvider(output, existingFileHelper)
);
}
来源: NeoForged/Documentation(1.21.1)。中文翻译依据提交
4a680cdd4d15。