模型生成
默认情况下,可以为模型或方块状态生成模型。每种都提供了一种生成必要JSON的方法(ModelBuilder#toJson
用于模型,IGeneratedBlockState#toJson
用于方块状态)。实现后,必须将关联的提供者 添加到DataGenerator
中。
// 在模组事件总线上
@SubscribeEvent
public void gatherData(GatherDataEvent event) {
DataGenerator gen = event.getGenerator();
ExistingFileHelper efh = event.getExistingFileHelper();
gen.addProvider(
// 告诉生成器仅在生成客户端资源时运行
event.includeClient(),
output -> new MyItemModelProvider(output, MOD_ID, efh)
);
gen.addProvider(
event.includeClient(),
output -> new MyBlockStateProvider(output, MOD_ID, efh)
);
}
模型文件
ModelFile
充当提供者引用或生成的所有模型的基础。每个模型文件存储相对于models
子目录的位置,并可以断言该文件是否存在。
现存的模型文件
ExistingModelFile
是ModelFile
的子类,它通过ExistingFileHelper#exists
检查模型是否已存在于models
子目录中。所有未生成的模型通常通过ExistingModelFile
引用。
未检查的模型文件
UncheckedModelFile
是ModelFile
的一个子类,它假定指定的模型存在于某个位置。
!!! 注意
不应存在使用UncheckedModelFile
引用模型的情况。如果存在,则ExistingFileHelper
无法正确跟踪关联的资源。
模型生成器
ModelBuilder
表示要生成的ModelFile
。它包含了关于模型的所有数据:它的父级、面、纹理、变换、照明和加载器。
!!! 提示 虽然可以生成复杂的模型,但建议事先使用建模软件构建这些模型。然后,数据提供者可以生成具有通过父复杂模型中定义的引用应用的特定纹理的子模型。
生成器的父级(通过ModelBuilder#parent
)可以是任何ModelFile
:生成的或现有的。一旦创建了生成器,生成的文件就会添加到ModelProvider
中。生成器本身可以作为父级传入,也可以提供ResourceLocation
。
!!! 警告
如果在传递ResourceLocation
时父模型不是在子模型之前生成的,则将引发异常。
模型中的每个元素(通过ModelBuilder#element
)都被定义为使用两个三维点(分别为ElementBuilder#from
和#to
)的立方体,其中每个轴都被限制为值[-16,32]
(包括-16和32)。多维数据集的每个面(ElementBuilder#face
)都可以指定面何时被剔除(FaceBuilder#cullface
)、色调索引(FaceBuilder#tintindex
)、来自textures
键的纹理引用(FaceBuilder#texture
)、纹理上的UV坐标(FaceBuilder#uvs
)以及以90度间隔旋转(FaceBuilder#rotation
)。
!!! 注意
建议在任何轴上元素超过[0,16]
界限的方块模型分离为多个方块,例如多方块结构,以避免照明和剔除问题。
每个立方体还可以围绕指定点(RotationBuilder#origin
)以22.5度的间隔(RotationBuilder#angle
)为给定轴(RotationBuilder#axis
)旋转(ElementBuilder#rotation
)。立方体也可以相对于整个模型缩放所有面(RotationBuilder#rescale
)。多维数据集还可以确定是否应渲染其阴影(ElementBuilder#shade
)。
每个模型都定义了一个纹理键列表(ModelBuilder#texture
),该列表指向一个位置或引用。然后,通过使用#
前缀,可以在任何元素中引用每个键(example
的纹理键可以在使用#example
元素中引用)。位置指定纹理在assets/<namespace>/textures/<path>.png
中的位置。引用由作为当前模型的子级的任何模型使用,作为以后定义纹理的键。
对于任何定义的透视图(在第一人称的左手、在图形用户界面、在地面等),还可以对模型进行转换(ModelBuilder#transforms
)。对于任何透视图(TransformsBuilder#transform
),都可以设置旋转(TransformVecBuilder#rotation
)、平移(TransformVecBuilder#translation
)和缩放(TransformVecBuilder#scale
)。
最后,模型可以设置是否在某个存档(ModelBuilder#ao
)中使用环境遮挡,以及从哪个位置从ModelBuilder#guiLight
对模型进行明暗处理。
BlockModelBuilder
BlockModelBuilder
表示要生成的方块模型。除了ModelBuilder
之外,还可以生成对整个模型的转换(BlockModelBuilder#rootTransform
)。根可以围绕某个原点(RootTransformBuilder#origin
)单独或全部在一个变换(RootTransformBuilder#transform
)中进行平移(RootTransformBuilder#transform
)、旋转(RootTransformBuilder#rotation
、RootTransformBuilder#postRotation
)和缩放(RootTransformBuilder#origin
)。
ItemModelBuilder
ItemModelBuilder
表示要生成的物品模型。除了ModelBuilder
之外,还可以生成overrides(OverrideBuilder#override
)。应用于模型的每个重写都可以应用表示给定属性的条件,该属性必须高于指定值(OverrideBuilder#predicate
)。如果满足条件,则将呈现指定的模型(OverrideBuilder#model
),而不是此模型。
模型提供者
ModelProvider
子类负责生成构造的ModelBuilder
。提供者接收生成器、mod id、要在其中生成的models
文件夹中的子目录、ModelBuilder
工厂和现有文件助手。每个提供器子类都必须实现#registerModels
。
提供器包含创建ModelBuilder
或为获取纹理或模型引用提供便利的基本方法:
方法 | 描述 |
---|---|
getBuilder | Creates a new ModelBuilder within the provider's subdirectory for the given mod id. |
withExistingParent | Creates a new ModelBuilder for the given parent. Should be used when the parent is not generated by the builder. |
mcLoc | Creates a ResourceLocation for the path in the minecraft namespace. |
modLoc | Creates a ResourceLocation for the path in the given mod id's namespace. |
此外,还有几个助手可以使用普通模板轻松生成通用模型。大多数是方块模型,只有少数是通用的。
!!! 注意 尽管模型在一个特定的子目录中,但并不意味着该模型不能被另一个子目录中的模型引用。通常,它表示该模型用于该类型的对象。
BlockModelProvider
BlockModelProvider
用于通过block
文件夹中的BlockModelBuilder
生成方块模型。方块模型通常应为minecraft:block/block
或其子模型之一的父模型,以便与物品模型一起使用。
!!! 注意
方块模型及其物品模型对应物通常不是通过BlockModelProvider
和ItemModelProvider
的直接子类生成的,而是通过BlockStateProvider
生成的。
ItemModelProvider
ItemModelProvider
用于通过item
文件夹中的ItemModelBuilder
生成块模型。大多数物品模型的父级为item/generated
,并使用layer0
来指定其纹理,这可以使用#singleTexture
来完成。
!!! 注意
item/generated
可以支持堆叠在一起的五个纹理层:layer0
、layer1
、layer2
、layer3
和layer4
。
// 在某个ItemModelProvider#registerModels中
// 将会生成'assets/<modid>/models/item/example_item.json'
// 父级将是'minecraft:item/generated'
// 对于纹理键'layer0'
// 其将会在'assets/<modid>/textures/item/example_item.png'
this.basicItem(EXAMPLE_ITEM.get());
!!! 注意 方块的物品模型通常应作为现有方块模型的父级,而不是为物品生成单独的模型。
方块状态提供者
BlockStateProvider
负责为所述方块生成blockstates
中的方块状态JSON、models/block
中的方块模型以及models/item
中的物品模型。提供器接收数据生成器、mod id和现有的文件助手。每个BlockStateProvider
子类都必须实现#registerStatesAndModels
。
提供者包含用于生成方块状态JSON和方块模型的基本方法。物品模型必须单独生成,因为方块状态JSON可以定义多个模型以在不同的上下文中使用。然而,在处理更复杂的任务时,模组开发者应该注意一些常见的方法:
方法 | 描述 |
---|---|
models | 获取用于生成物品方块模型的BlockModelProvider 。 |
itemModels | 获取用于生成物品方块模型的ItemModelProvider 。 |
modLoc | 为给定mod id的命名空间中的路径创建ResourceLocation 。 |
mcLoc | 为minecraft 命名空间中的路径创建ResourceLocation 。 |
blockTexture | 引用textures/block 中与方块同名的纹理。 |
simpleBlockItem | 为给定关联模型文件的方块创建物品模型。 |
simpleBlockWithItem | 使用方块模型作为其父级,为方块模型和物品模型创建单个方块状态。 |
方块状态JSON由变量或条件组成。每个变量或条件都引用一个ConfiguredModelList
:ConfiguredModel
的列表。每个配置的模型都包含模型文件(通过ConfiguredModel$Builder#modelFile
)、90度间隔的X和Y旋转(分别通过#rotationX
和rotationY
)、当模型通过方块状态JSON旋转时纹理是否可以旋转(通过#uvLock
),以及与列表中其他模型相比出现的模型的权重(通过#weight
)。
生成器(ConfiguredModel#builder
)还可以通过使用#nextModel
创建下一个模型并重复设置直到调用#build
来创建ConfiguredModel
的数组。
VariantBlockStateBuilder
可以使用BlockStateProvider#getVariantBuilder
生成变量。每个变体都指定了一个属性列表(PartialBlockstate
),当该列表与存档中的BlockState
匹配时,将显示从相应模型列表中选择的模型。如果存在未被定义的任何变体覆盖的BlockState
,则抛出异常。对于任何BlockState
,只有一种变体可以为true。
PartialBlockstate
通常使用以下三种方法之一进行定义:
方法 | 描述 |
---|---|
partialState | 创建要定义的PartialBlockstate 。 |
forAllStates | 定义一个函数,其中给定的BlockState 可以由ConfiguredModel 的数组表示。 |
forAllStatesExcept | 定义一个类似于#forAllStates 的函数;但是,它还指定了哪些属性不会影响渲染的模型。 |
对于PartialBlockstate
,可以指定定义的属性(#with
)。 配置的模型可以设置(#setModels
),附加到现有模型(#addModels
),或构建(#modelForState
,然后是ConfiguredModel$Builder#addModel
,而不是#ConfiguredModel$Builder#build
)。
// 在某个BlockStateProvider#registerStatesAndModels中
// EXAMPLE_BLOCK_1:拥有属性BlockStateProperties#AXIS
this.getVariantBuilder(EXAMPLE_BLOCK_1) // 获取变量生成器
.partialState() // 构建部分状态
.with(AXIS, Axis.Y) // 当 BlockState AXIS = Y 时
.modelForState() // 当 AXIS = Y 时设置模型
.modelFile(yModelFile1) // 可以显示'yModelFile1'
.nextModel() // 当 AXIS = Y 时添加另一个模型
.modelFile(yModelFile2) // 可以显示'yModelFile2'
.weight(2) // 此时将显示'yModelFile2' 2/3
.addModel() // 完成当 AXIS = Y 时的模型
.with(AXIS, Axis.Z) // 当 BlockState AXIS = Z 时
.modelForState() // 当 AXIS = Z 时设置模型
.modelFile(hModelFile) // 可以显示'hModelFile'
.addModel() // 完成当 AXIS = Z 时的模型
.with(AXIS, Axis.X) // 当 BlockState AXIS = X 时
.modelForState() // 当 AXIS = X 时设置模型
.modelFile(hModelFile) // 可以显示'hModelFile'
.rotationY(90) // 绕Y轴将'hModelFile'旋转90度
.addModel(); // 完成当 AXIS = X 时的模型
// EXAMPLE_BLOCK_2:拥有属性BlockStateProperties#HORIZONTAL_FACING
this.getVariantBuilder(EXAMPLE_BLOCK_2) // 获取变量生成器
.forAllStates(state -> // 对于全部可能的状态
ConfiguredModel.builder() // 创建配置模型生成器
.modelFile(modelFile) // 可以显示'modelFile'
.rotationY((int) state.getValue(HORIZONTAL_FACING).toYRot()) // 根据变量需求将'modelFile'绕Y轴旋转
.build() // 创建配置模型的数组
);
// EXAMPLE_BLOCK_3:拥有属性BlockStateProperties#HORIZONTAL_FACING, BlockStateProperties#WATERLOGGED
this.getVariantBuilder(EXAMPLE_BLOCK_3) // 获取变量生成器
.forAllStatesExcept(state -> // 对于全部HORIZONTAL_FACING状态
ConfiguredModel.builder() // 创建配置模型生成器
.modelFile(modelFile) // 可以显示'modelFile'
.rotationY((int) state.getValue(HORIZONTAL_FACING).toYRot()) // 根据变量需求将'modelFile'绕Y轴旋转
.build(), // 创建配置模型的数组
WATERLOGGED); // 忽略WATERLOGGED属性
MultiPartBlockStateBuilder
可以使用BlockStateProvider#getMultipartBuilder
生成多部分。每个部分(MultiPartBlockStateBuilder#part
)指定一组属性条件,当与存档中的BlockState
匹配时,将显示模型列表中的模型。所有与BlockState
匹配的条件组将显示它们所选的模型。
对于任何部分(通过ConfiguredModel$Builder#addModel
获得),当属性是指定值之一时,可以添加条件(通过#condition
)。条件必须全部成功,或者当设置了#useOr
时,必须至少有一个成功。只要当前分组只包含其他组而不包含单个条件,就可以对条件进行分组(通过#nestedGroup
)。条件组可以使用#endNestedGroup
留下,给定的部分可以通过#end
完成。
// 在某个BlockStateProvider#registerStatesAndModels中
// 红石线
this.getMultipartBuilder(REDSTONE) // 获取多部分生成器
.part() // 创建一个部分
.modelFile(redstoneDot) // 可以显示'redstoneDot'
.addModel() // 'redstoneDot'被显示,当...
.useOr() // 这些条件中至少一个为true
.nestedGroup() // 当所有组合条件均为true时,true
.condition(WEST_REDSTONE, NONE) // 当WEST_REDSTONE为NONE时,true
.condition(EAST_REDSTONE, NONE) // 当EAST_REDSTONE为NONE时,true
.condition(SOUTH_REDSTONE, NONE) // 当SOUTH_REDSTONE为NONE时,true
.condition(NORTH_REDSTONE, NONE) // 当NORTH_REDSTONE为NONE时,true
.endNestedGroup() // 结束组合
.nestedGroup() // 当所有组合条件均为true时,true
.condition(EAST_REDSTONE, SIDE, UP) // 当EAST_REDSTONE为SIDE或UP时,true
.condition(NORTH_REDSTONE, SIDE, UP) // 当NORTH_REDSTONE为SIDE或UP时,true
.endNestedGroup() // 结束组合
.nestedGroup() // 当所有组合条件均为true时,true
.condition(EAST_REDSTONE, SIDE, UP) // 当EAST_REDSTONE为SIDE或UP时,true
.condition(SOUTH_REDSTONE, SIDE, UP) // 当SOUTH_REDSTONE为SIDE或UP时,true
.endNestedGroup() // 结束组合
.nestedGroup() // 当所有组合条件均为true时,true
.condition(WEST_REDSTONE, SIDE, UP) // 当WEST_REDSTONE为SIDE或UP时,true
.condition(SOUTH_REDSTONE, SIDE, UP) // 当SOUTH_REDSTONE为SIDE或UP时,true
.endNestedGroup() // 结束组合
.nestedGroup() // 当所有组合条件均为true时,true
.condition(WEST_REDSTONE, SIDE, UP) // 当WEST_REDSTONE为SIDE或UP时,true
.condition(NORTH_REDSTONE, SIDE, UP) // 当NORTH_REDSTONE为SIDE或UP时,true
.endNestedGroup() // 结束组合
.end() // 结束该部分
.part() // 创建一个部分
.modelFile(redstoneSide0) // 可以显示'redstoneSide0'
.addModel() // 'redstoneSide0'被显示,当...
.condition(NORTH_REDSTONE, SIDE, UP) // NORTH_REDSTONE为SIDE或UP
.end() // 结束该部分
.part() // 创建一个部分
.modelFile(redstoneSideAlt0) // 可以显示'redstoneSideAlt0'
.addModel() // 'redstoneSideAlt0'被显示,当...
.condition(SOUTH_REDSTONE, SIDE, UP) // SOUTH_REDSTONE为SIDE或UP
.end() // 结束该部分
.part() // 创建一个部分
.modelFile(redstoneSideAlt1) // 可以显示'redstoneSideAlt1'
.rotationY(270) // 将'redstoneSideAlt1'绕Y轴旋转270度
.addModel() // 'redstoneSideAlt1'被显示,当...
.condition(EAST_REDSTONE, SIDE, UP) // EAST_REDSTONE为SIDE或UP
.end() // 结束该部分
.part() // 创建一个部分
.modelFile(redstoneSide1) // 可以显示'redstoneSide1'
.rotationY(270) // 将'redstoneSide1'绕Y轴旋转270度
.addModel() // 'redstoneSide1'被显示,当...
.condition(WEST_REDSTONE, SIDE, UP) // WEST_REDSTONE为SIDE或UP
.end() // 结束该部分
.part() // 创建一个部分
.modelFile(redstoneUp) // 可以显示'redstoneUp'
.addModel() // 'redstoneUp'被显示,当...
.condition(NORTH_REDSTONE, UP) // NORTH_REDSTONE为UP
.end() // 结束该部分
.part() // 创建一个部分
.modelFile(redstoneUp) // 可以显示'redstoneUp'
.rotationY(90) // 将'redstoneUp'绕Y轴旋转90度
.addModel() // 'redstoneUp'被显示,当...
.condition(EAST_REDSTONE, UP) // EAST_REDSTONE为UP
.end() // 结束该部分
.part() // 创建一个部分
.modelFile(redstoneUp) // 可以显示'redstoneUp'
.rotationY(180) // 将'redstoneUp'绕Y轴旋转180度
.addModel() // 'redstoneUp'被显示,当...
.condition(SOUTH_REDSTONE, UP) // SOUTH_REDSTONE为UP
.end() // 结束该部分
.part() // 创建一个部分
.modelFile(redstoneUp) // 可以显示'redstoneUp'
.rotationY(270) // 将'redstoneUp'绕Y轴旋转270度
.addModel() // 'redstoneUp'被显示,当...
.condition(WEST_REDSTONE, UP) // WEST_REDSTONE为UP
.end(); // 结束该部分
模型加载器生成器
还可以为给定的ModelBuilder
生成自定义模型加载器。自定义模型加载器子类CustomLoaderBuilder
,可以通过#customLoader
应用于ModelBuilder
。传入的工厂方法创建了一个新的加载器生成器,可以对其进行配置。完成所有更改后,自定义加载器可以通过CustomLoaderBuilder#end
返回到ModelBuilder
。
模型生成器 | 工厂方法 | 描述 |
---|---|---|
DynamicFluidContainerModelBuilder | #begin | 为特定的流体生成一个桶模型。 |
CompositeModelBuilder | #begin | 生成一个由模型组成的模型。 |
ItemLayersModelBuilder | #begin | 生成一个item/generated 模型的Forge实现。 |
SeparateTransformsModelBuilder | #begin | 生成一个模型,其修改基于特定的变换。 |
ObjModelBuilder | #begin | 生成一个OBJ模型。 |
// 对于某个BlockModelBuilder生成器
builder.customLoader(ObjModelBuilder::begin) // 自定义加载器'forge:obj'
.modelLocation(modLoc("models/block/model.obj")) // 设置OBJ模型位置
.flipV(true) // 在提供的.mtl纹理中翻转V坐标
.end() // 完成自定义加载器配置
.texture("particle", mcLoc("block/dirt")) // 将粒子纹理设置为泥土
.texture("texture0", mcLoc("block/dirt")); // 将'texture0'纹理设置为泥土
自定义模型加载器生成器
可以通过扩展CustomLoaderBuilder
来创建自定义加载器生成器。构造函数仍然可以具有protected
的可见性,其中ResourceLocation
硬编码为通过ModelEvent$RegisterGeometryLoaders#register
注册的加载器id。然后,可以通过静态工厂方法或构造函数(如果设置为public
)初始化生成器。
public class ExampleLoaderBuilder<T extends ModelBuilder<T>> extends CustomLoaderBuilder<T> {
public static <T extends ModelBuilder<T>> ExampleLoaderBuilder<T> begin(T parent, ExistingFileHelper existingFileHelper) {
return new ExampleLoaderBuilder<>(parent, existingFileHelper);
}
protected ExampleLoaderBuilder(T parent, ExistingFileHelper existingFileHelper) {
super(new ResourceLocation(MOD_ID, "example_loader"), parent, existingFileHelper);
}
}
Afterwards, any configurations specified by the loader should be added as chainable methods.
// 在ExampleLoaderBuilder中
public ExampleLoaderBuilder<T> exampleInt(int example) {
// 设置int
return this;
}
public ExampleLoaderBuilder<T> exampleString(String example) {
// 设置string
return this;
}
If any additional configuration is specified, #toJson
should be overridden to write the additional properties.
// 在ExampleLoaderBuilder中
@Override
public JsonObject toJson(JsonObject json) {
json = super.toJson(json); // 处理基础加载器属性
// 编码自定义加载器属性
return json;
}
自定义模型提供者
自定义模型提供者需要ModelBuilder
子类和ModelProvider
子类,前者定义要生成的模型的基础,后者生成模型。
ModelBuilder
子类包含任何特殊属性,这些属性可以专门应用于这些类型的模型(物品模型可以具有重写)。如果添加了任何附加属性,则需要重写#toJson
以写入附加信息。
public class ExampleModelBuilder extends ModelBuilder<ExampleModelBuilder> {
// ...
}
ModelProvider
子类不需要特殊的逻辑。构造函数应硬编码models
文件夹和ModelBuilder
中的子目录,以表示要生成的模型。
public class ExampleModelProvider extends ModelProvider<ExampleModelBuilder> {
public ExampleModelProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) {
// 如果'#getBuilder'中未指定'modid',则模型将生成到'assets/<modid>/models/example'
super(output, modid, "example", ExampleModelBuilder::new, existingFileHelper);
}
}
自定义模型Consumer
自定义模型Consumer,如BlockStateProvider
,可以通过手动生成模型来创建。应指定用于生成模型的ModelProvider
子类并使其可用。
public class ExampleModelConsumerProvider implements IDataProvider {
public ExampleModelConsumerProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) {
this.example = new ExampleModelProvider(output, modid, existingFileHelper);
}
}
一旦数据提供者运行,就可以使用ModelProvider#generateAll
生成ModelProvider
子类中的模型。
// 在ExampleModelConsumerProvider中
@Override
public CompletableFuture<?> run(CachedOutput cache) {
// 填入模型提供者
CompletableFuture<?> exampleFutures = this.example.generateAll(cache); // 生成模型
// 运行逻辑并创建CompletableFuture以写入文件
// ...
// 假设我们有一个新的CompletableFuture providerFuture
return CompletableFuture.allOf(exampleFutures, providerFuture);
}