Skip to content

3. 面向对象的数据生成

SolidBlock edited this page Aug 19, 2024 · 3 revisions

你可以让你模组中的物品或者方块对象自身就带有生成运行时资源的方法。实现 ItemResourceGeneratorBlockResourceGenerator 即可。这是 BRRP 提供的功能。这样的方法的好处就是充分利用了面向对象编程的特点。你可以通过覆盖(override)的方式来决定你自己的对象的某个资源应该如何生成。最后,在实际生成资源的过程中,你只需要迭代你模组中的所有物品或方块,并调用 writeAssetswriteDatawriteAllwriteResources 来将其写入你的运行时资源包(具体取决于你的实际需求)。

实现接口

你可以为你的物品 ItemResourceGenerator 接口,或为你的方块实现 BlockResourceGenerator 接口。注意 BlockResourceGenerator 继承了 ItemResourceGenerator,因此你的方块在生成资源时会自动包括其对应的物品的所有资源,不需要专门为物品生成资源。

实现接口后,没有任何需要实现的方法,因为所有的方法都有默认方法,包括:

  • 获取物品 ID、物品模型 ID、战利品表 ID、配方 ID 等的方法都已经为你写好了,如有需要可以覆盖。
  • 物品模型默认为基于 item/generated 并使用对应纹理 ID 的材质,方块的模型默认为直接继承物品模型,如有需要可以覆盖。覆盖的方法可以返回 null 以表示不生成这些数据。
  • 物品的配方默认为 null,即表示不会生成。通常你需要直接覆盖此方法并生成对应的配方。
  • 方块纹理 ID 的获取方法也已经为你写好了,其中会使用到 TextureRegistry,如有需要可以覆盖。
  • 方块模型和方块状态默认为 null,即表示不会生成。通常你需要覆盖这些方法并决定对应的方块模型和方块状态的生成方式,当然也可以通过其他方式生成。
  • 方块的战利品表默认为最简单的战利品表,即掉落方块本身。如有需要可以覆盖。注意:如果你希望你的方块不掉落,应该返回空的战利品表而非 null

覆盖方法

你可以覆盖接口中提供的方法,这样方块对象的数据生成可以更加灵活。例如,假如你需要创建自己的台阶方块,你需要覆盖 getBlockStates()getBlockModel()getLootTable()getCraftingRecipe() 等方法,因为台阶方块具有自己的方块状态、方块模型、战利品表和配方。此外你还需要覆盖 writeBlockModel(),因为台阶方块不只有一个方块模型,你需要为每个台阶方块生成多个方块模型。例如(参见 BRRPSlabBlock):

  @Environment(EnvType.CLIENT)
  @Override
  public BlockStateSupplier getBlockStates() {
    // 台阶的方块状态。台阶根据其方块状态,可能使用其下半砖、上半砖或整块的模块,因为需要指定这三种模型的 ID。
    // 这里,使用原版数据生成器自带的方法 `BlockStateModelGenerator.createSlabBlockState` 生成。
    final Identifier id = getBlockModelId();
    final Identifier baseBlockModelId = baseBlock != null ? BRRPUtils.getBlockModelId(baseBlock) : id.brrp_suffixed("_double");
    final Identifier topSlabModelId = id.brrp_suffixed("_top");
    return BlockStateModelGenerator.createSlabBlockState(this, id, topSlabModelId, baseBlockModelId);
  }

  @Environment(EnvType.CLIENT)
  @Override
  public ModelJsonBuilder getBlockModel() {
    // 这是台阶的下半砖的模型。上半砖和整块的模型,没有单独的方法指定,但是会在 `writeBlockModel` 方法中直接使用。
    return ModelUtils.createModelWithVariants(this, Models.SLAB);
  }

  @Environment(EnvType.CLIENT)
  @Override
  public void writeBlockModel(RuntimeResourcePack pack) {
    final ModelJsonBuilder model = getBlockModel();
    final Identifier id = getBlockModelId();
    // 这个方法会一并写入台阶的下半砖和上半砖的模型。
    ModelUtils.writeModelsWithVariants(pack, id, model, Models.SLAB, Models.SLAB_TOP);
    if (baseBlock == null) {
      pack.addModel(id.brrp_suffixed("_double"), model.withParent(Models.CUBE_BOTTOM_TOP));
    }
  }

  @Override
  public LootTable.Builder getLootTable(BlockLootTableGenerator blockLootTableGenerator) {
    // 台阶的战利品表。这里直接使用了原版的 `blockLootTableGenerator.slabDrops`。
    return blockLootTableGenerator.slabDrops(this);
  }

  @Override
  public CraftingRecipeJsonBuilder getCraftingRecipe() {
    return baseBlock == null ? null : RecipeProvider.createSlabRecipe(getRecipeCategory(), this, Ingredient.ofItems(baseBlock)).criterion(RecipeProvider.hasItem(baseBlock), RecipeProvider.conditionsFromItem(baseBlock));
  }

如果你需要让你的方块能够生成切石配方,除了覆盖 getStonecuttingRecipe() 之外,还需要覆盖 shouldWriteStonecuttingRecipe 方法,因为通常即使是同一类的方块也不一定都有切石配方,例如,橡木台阶和安山岩台阶都是台阶,但只有安山岩台阶有切石配方。

对于变种类方块,也就是基于另一个方块进行变形的方块,你可能还需要覆盖 getBaseBlock()。这样,在使用 getTextureId 获取方块纹理 ID 时,除了考虑 TextureRegistry 之外,还会尝试获取其基础方块的纹理,而非根据方块自身的 ID 来决定纹理 ID。

生成数据

实现接口之后,你还需要将方块的数据生成至你的运行时资源包,这是因为你只是实现了接口并覆盖了方法,这些方法还没有被调用。在数据生成时,你可以调用 writeAssetswriteDatawriteResourceswriteAll 来生成运行时数据,具体调用哪个,取决于你的实际需求。

其中,writeResources 有两种方法:

  • void writeResources(RuntimeResourcePack pack, @Nullable ResourceType resourceType)(当 resourceType 为 null 时,会把客户端和服务器的资源两个都生成)
  • void writeResources(RuntimeResourcePack pack, boolean clientIncluded, boolean serverIncluded)(在特定情况下有用)

接口的预置实现

本模组预置了几个类,实现了上述接口并覆盖了一些方法。你可以直接在模组中创建这些类的对象(如有需要也可以进一步创建子类并覆盖方法),或访问这些类的源代码以了解面向对象的数据生成的做法。

例如,BRRPStairsBlock 继承了 StairsBlock 并实现了 BlockResourceGenerator。你可以直接实例化该类,这样在数据生成时将直接使用楼梯类方块的数据生成方法,你也可以根据你自己的需要进一步继承和覆盖。