模型提供器

模型生成

默认可以生成模型或方块状态,各自提供生成所需 JSON 的方法:模型使用 ModelBuilder#toJson,方块状态使用 IGeneratedBlockState#toJson。实现后必须将相应提供者添加到 DataGenerator。

BlockStateProvider 可生成方块状态、方块模型及方块物品模型;示例中方块状态引用方块模型,物品模型以方块模型为 parent。

原创说明图,依据 Forge 1.21.x 官方文档 绘制。

// 在模组事件总线上
@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 子目录。非生成模型通常通过它引用。

不检查的模型文件

UncheckedModelFile 是 ModelFile 子类,假定指定模型存在于某处。

注意:不应有使用 UncheckedModelFile 引用模型的情况。如果有,说明相关资源没有被 ExistingFileHelper 正确跟踪。

模型构建器

ModelBuilder 表示待生成的 ModelFile,包含模型的父模型、面、纹理、变换、光照和加载器等数据。

提示:虽然可以生成复杂模型,但建议事先用建模软件构建,再由数据提供者通过父模型中定义的引用生成应用了特定纹理的子模型。

通过 ModelBuilder#parent 设置的父模型可以是任何生成的或已有的 ModelFile。创建构建器时,生成文件立即加入 ModelProvider。可直接传入构建器作为父模型,也可传入 ResourceLocation。

警告:传入 ResourceLocation 时,如果父模型没有在子模型之前生成,会抛出异常。

模型中的每个元素(ModelBuilder#element)定义为立方体,使用两个三维点(ElementBuilder#from 和 #to),每轴限制在闭区间 [-16,32]。立方体各面(ElementBuilder#face)可指定剔除方向(FaceBuilder#cullface)、着色索引(#tintindex)、textures 键中的纹理引用(#texture)、纹理 UV 坐标(#uvs),以及以 90 度为间隔的旋转(#rotation)。

注意:如果方块模型元素在任意轴超出 [0,16],建议拆成多个方块,例如多方块结构,以避免光照和剔除问题。

每个立方体还可通过 ElementBuilder#rotation 绕指定点(RotationBuilder#origin)和轴(#axis)以 22.5 度为间隔旋转(#angle),也可相对整个模型缩放各面(#rescale),并通过 ElementBuilder#shade 决定是否渲染阴影。

每个模型通过 ModelBuilder#texture 定义纹理键列表,指向位置或引用。元素中通过 # 前缀引用键,例如 #example。位置指定 assets/<namespace>/textures/<path>.png 中的纹理;引用则供以当前模型为父模型的模型稍后定义纹理。

模型还可通过 ModelBuilder#transforms 针对任意视角(第一人称左手、GUI、地面等)变换。对每个视角(TransformsBuilder#transform)可设置旋转、平移和缩放(TransformVecBuilder#rotation、#translation、#scale)。最后,ModelBuilder#ao 决定是否在维度中使用环境光遮蔽,ModelBuilder#guiLight 决定模型的光照和阴影来源。

BlockModelBuilder

BlockModelBuilder 表示待生成的方块模型。除 ModelBuilder 功能外,还可通过 #rootTransform 生成整个模型的变换。根变换可围绕指定原点(RootTransformBuilder#origin)分别或一次性平移、旋转和缩放(#transform、#rotation、#postRotation、#scale)。

ItemModelBuilder

ItemModelBuilder 表示待生成的物品模型。除 ModelBuilder 功能外,还可生成覆盖项(OverrideBuilder#override)。每个覆盖项可设定条件:给定属性必须高于指定值(#predicate);满足时渲染指定模型(#model),而不是当前模型。

模型提供者

ModelProvider 子类负责生成已构造的 ModelBuilder。提供者接收生成器、模组 ID、models 下生成用的子目录、ModelBuilder 工厂和已有文件帮助器。每个子类都必须实现 #registerModels。

方法 说明
getBuilder 为给定模组 ID 在提供者子目录创建新的 ModelBuilder。
withExistingParent 为给定父模型创建构建器;父模型不是由该构建器生成时使用。
mcLoc 为 minecraft 命名空间中的路径创建 ResourceLocation。
modLoc 为给定模组 ID 的命名空间中的路径创建 ResourceLocation。

此外还有多个使用原版模板生成常见模型的辅助方法,大多数用于方块模型,少数通用。

注意:模型位于特定子目录并不意味着其他子目录中的模型不能引用它;子目录通常只表明模型用于哪类对象。

BlockModelProvider

BlockModelProvider 通过 BlockModelBuilder 在 block 文件夹生成方块模型。为了供物品模型使用,方块模型通常应以 minecraft:block/block 或其子模型为父模型。

注意:方块模型及对应物品模型通常通过 BlockStateProvider 生成,而不是直接继承 BlockModelProvider 和 ItemModelProvider。

ItemModelProvider

ItemModelProvider 通过 ItemModelBuilder 在 item 文件夹生成模型。大多数物品模型以 item/generated 为父模型,并通过 layer0 指定纹理,可使用 #singleTexture 完成。

注意:item/generated 支持五个叠加纹理层:layer0 至 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 中生成这些方块的物品模型。它接收数据生成器、模组 ID 和已有文件帮助器;每个子类必须实现 #registerStatesAndModels。

提供者有生成方块状态 JSON 和方块模型的基本方法。物品模型必须单独生成,因为一个方块状态 JSON 可以在不同上下文中定义多个模型。复杂任务中常用方法包括:

方法 说明
models 获取用于生成方块模型的 BlockModelProvider。
itemModels 获取用于生成方块物品模型的 ItemModelProvider。
modLoc / 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 只能有一个变体为真。

通常使用 partialState 创建待定义的部分状态,或使用 forAllStates 定义将给定 BlockState 转为 ConfiguredModel 数组的函数;forAllStatesExcept 类似,但还指定不影响渲染模型的属性。对 PartialBlockstate 可通过 #with 指定属性,通过 #setModels 设置模型、#addModels 追加模型,或者使用 #modelForState 构建,完成时调用 ConfiguredModel$Builder#addModel 而不是 #build。

// 在 BlockStateProvider#registerStatesAndModels 中
// EXAMPLE_BLOCK_1 具有 BlockStateProperties#AXIS 属性
this.getVariantBuilder(EXAMPLE_BLOCK_1) // 获取变体构建器
  .partialState() // 创建部分状态
  .with(AXIS, Axis.Y) // 当 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) // 当 AXIS = Z 时
    .modelForState() // 设置 AXIS = Z 时的模型
    .modelFile(hModelFile) // 可显示 hModelFile
    .addModel() // 完成 AXIS = Z 时的模型
  .with(AXIS, Axis.X) // 当 AXIS = X 时
    .modelForState() // 设置 AXIS = X 时的模型
    .modelFile(hModelFile) // 可显示 hModelFile
    .rotationY(90) // 将 hModelFile 绕 Y 轴旋转 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); // 忽略含水属性

MultiPartBlockStateBuilder

使用 BlockStateProvider#getMultipartBuilder 生成多部分模型。每个部分(#part)指定一组属性条件;匹配维度中 BlockState 时显示模型列表中的模型。所有匹配状态的条件组所选模型会相互叠加显示。

对于通过 ConfiguredModel$Builder#addModel 获取的部分,可通过 #condition 指定属性为某些值时的条件。默认所有条件必须成功;设置 #useOr 后只需至少一个成功。可通过 #nestedGroup 分组条件,前提是当前组只含其他组,不含单个条件。使用 #endNestedGroup 离开条件组,使用 #end 完成部分。

// 在 BlockStateProvider#registerStatesAndModels 中:红石线
this.getMultipartBuilder(REDSTONE) // 获取多部分构建器
  .part() // 创建部件
    .modelFile(redstoneDot) // 可显示 redstoneDot
    .addModel() // redstoneDot 在以下条件满足时显示
    .useOr() // 以下条件至少一个为真
    .nestedGroup() // 分组中全部条件为真时成立
      .condition(WEST_REDSTONE, NONE) // 西侧为 NONE
      .condition(EAST_REDSTONE, NONE) // 东侧为 NONE
      .condition(SOUTH_REDSTONE, NONE) // 南侧为 NONE
      .condition(NORTH_REDSTONE, NONE) // 北侧为 NONE
    .endNestedGroup() // 结束分组
    .nestedGroup() // 分组中全部条件为真时成立
      .condition(EAST_REDSTONE, SIDE, UP) // 东侧为 SIDE 或 UP
      .condition(NORTH_REDSTONE, SIDE, UP) // 北侧为 SIDE 或 UP
    .endNestedGroup() // 结束分组
    .nestedGroup() // 分组中全部条件为真时成立
      .condition(EAST_REDSTONE, SIDE, UP) // 东侧为 SIDE 或 UP
      .condition(SOUTH_REDSTONE, SIDE, UP) // 南侧为 SIDE 或 UP
    .endNestedGroup() // 结束分组
    .nestedGroup() // 分组中全部条件为真时成立
      .condition(WEST_REDSTONE, SIDE, UP) // 西侧为 SIDE 或 UP
      .condition(SOUTH_REDSTONE, SIDE, UP) // 南侧为 SIDE 或 UP
    .endNestedGroup() // 结束分组
    .nestedGroup() // 分组中全部条件为真时成立
      .condition(WEST_REDSTONE, SIDE, UP) // 西侧为 SIDE 或 UP
      .condition(NORTH_REDSTONE, SIDE, UP) // 北侧为 SIDE 或 UP
    .endNestedGroup() // 结束分组
    .end() // 完成部件
  .part() // 创建部件
    .modelFile(redstoneSide0) // 可显示 redstoneSide0
    .addModel() // redstoneSide0 在以下条件满足时显示
    .condition(NORTH_REDSTONE, SIDE, UP) // 北侧为 SIDE 或 UP
    .end() // 完成部件
  .part() // 创建部件
    .modelFile(redstoneSideAlt0) // 可显示 redstoneSideAlt0
    .addModel() // redstoneSideAlt0 在以下条件满足时显示
    .condition(SOUTH_REDSTONE, SIDE, UP) // 南侧为 SIDE 或 UP
    .end() // 完成部件
  .part() // 创建部件
    .modelFile(redstoneSideAlt1) // 可显示 redstoneSideAlt1
    .rotationY(270) // 绕 Y 轴旋转 270 度
    .addModel() // redstoneSideAlt1 在以下条件满足时显示
    .condition(EAST_REDSTONE, SIDE, UP) // 东侧为 SIDE 或 UP
    .end() // 完成部件
  .part() // 创建部件
    .modelFile(redstoneSide1) // 可显示 redstoneSide1
    .rotationY(270) // 绕 Y 轴旋转 270 度
    .addModel() // redstoneSide1 在以下条件满足时显示
    .condition(WEST_REDSTONE, SIDE, UP) // 西侧为 SIDE 或 UP
    .end() // 完成部件
  .part() // 创建部件
    .modelFile(redstoneUp) // 可显示 redstoneUp
    .addModel() // redstoneUp 在以下条件满足时显示
    .condition(NORTH_REDSTONE, UP) // 北侧为 UP
    .end() // 完成部件
  .part() // 创建部件
    .modelFile(redstoneUp) // 可显示 redstoneUp
    .rotationY(90) // 绕 Y 轴旋转 90 度
    .addModel() // redstoneUp 在以下条件满足时显示
    .condition(EAST_REDSTONE, UP) // 东侧为 UP
    .end() // 完成部件
  .part() // 创建部件
    .modelFile(redstoneUp) // 可显示 redstoneUp
    .rotationY(180) // 绕 Y 轴旋转 180 度
    .addModel() // redstoneUp 在以下条件满足时显示
    .condition(SOUTH_REDSTONE, UP) // 南侧为 UP
    .end() // 完成部件
  .part() // 创建部件
    .modelFile(redstoneUp) // 可显示 redstoneUp
    .rotationY(270) // 绕 Y 轴旋转 270 度
    .addModel() // redstoneUp 在以下条件满足时显示
    .condition(WEST_REDSTONE, UP) // 西侧为 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
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(ResourceLocation.fromNamespaceAndPath(MOD_ID, "example_loader"), parent, existingFileHelper);
  }
}

随后将加载器的配置添加为链式方法:

// 在 ExampleLoaderBuilder 中
public ExampleLoaderBuilder<T> exampleInt(int example) { /* 设置整数 */ return this; }
public ExampleLoaderBuilder<T> exampleString(String example) { /* 设置字符串 */ return this; }

如果有额外配置,应重写 #toJson 写入额外属性。

// 在 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);
  }
}

自定义模型消费者

可通过手动生成模型,创建类似 BlockStateProvider 的自定义模型消费者。应指定并提供用于生成模型的 ModelProvider 子类。

public class ExampleModelConsumerProvider implements IDataProvider {
  public ExampleModelConsumerProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) {
    this.example = new ExampleModelProvider(output, modid, existingFileHelper);
  }
}

数据提供者运行后,可通过 ModelProvider#generateAll 生成其子类中的模型。

// 在 ExampleModelConsumerProvider 中
@Override
public CompletableFuture<?> run(CachedOutput cache) {
  // 填充模型提供者
  CompletableFuture<?> exampleFutures = this.example.generateAll(cache); // 生成模型
  // 执行逻辑并创建用于写入文件的 CompletableFuture;假设得到 providerFuture
  return CompletableFuture.allOf(exampleFutures, providerFuture);
}

原文:Model Generation。