Skip to main content
Version: 1.20.x

自定义配方

每个配方定义都由三个组件组成:Recipe实现,它保存数据并用所提供的输入处理执行逻辑,RecipeType表示配方将用于的类别或上下文,以及RecipeSerializer,它处理配方数据的解码和网络通信。如何选择使用配方取决于实施者。

配方

Recipe接口描述配方数据和执行逻辑。这包括匹配输入并提供相关联的结果。由于配方子系统默认执行物品转换,因此输入是通过Container子类型提供的。

!!! 重要 传递到配方中的Container应被视为其内容是不可变的。任何可变操作都应该通过ItemStack#copy对输入的一份副本执行。

为了能够从管理器获得配方实例,#matches必须返回true。此方法根据提供的容器进行检查,以查看相关联的输入是否有效。Ingredient可以通过调用Ingredient#test进行验证。

如果已经选择了配方,则使用#assemble构建配方,该#assemble可以使用来自输入的数据来创建结果。

!!! 提示 #assemble应始终生成唯一的”ItemStack。如果不确定#assemble是否执行此操作,请在返回之前对结果调用ItemStack#copy

大多数其他方法纯粹是为了与配方相结合。

public record ExampleRecipe(Ingredient input, int data, ItemStack output) implements Recipe<Container> {
// 在此处实现方法
}

!!! 注意 虽然在上面的示例中使用了一个记录,但在你自己的实现中不需要这样做。

RecipeType

RecipeType负责定义配方将在其中使用的类别或上下文。例如,如果一个配方要在熔炉中熔炼,它的类型将是RecipeType#BLASTING。在高炉中进行熔炼的类型为RecipeType#BLASTING

如果现有类型中没有一个与配方将在其中使用的上下文匹配,则必须注册一个新的RecipeType

RecipeType实例必须由新配方子类型中的Recipe#getType返回。

// 对于某个RegistryObject<RecipeType> EXAMPLE_TYPE
// 在ExampleRecipe中
@Override
public RecipeType<?> getType() {
return EXAMPLE_TYPE.get();
}

RecipeSerializer

RecipeSerializer负责解码JSON,并通过网络为关联的Recipe子类型进行通信。序列化器解码的每个配方都保存为RecipeManager中的唯一实例。RecipeSerializer必须已被注册

RecipeSerializer只需要实现三个方法:

方法描述
fromJson将JSON解码为Recipe子类型。
toNetworkRecipe编码到缓冲区以发送到客户端。配方标识符无需编码。
fromNetwork从服务端发送的缓冲区中解码Recipe。配方标识符不需要解码。

然后,新配方子类型中的Recipe#getSerializer必须返回该RecipeSerializer实例。

// 对于某个RegistryObject<RecipeSerializer> EXAMPLE_SERIALIZER
// 在ExampleRecipe中
@Override
public RecipeSerializer<?> getSerializer() {
return EXAMPLE_SERIALIZER.get();
}

!!! 提示 有一些有用的方法可以让配方的读写数据变得更容易。Ingredient可以使用#fromJson#toNetwork#fromNetwork,而ItemStack可以使用CraftingHelper#getItemStackFriendlyByteBuf#writeItemFriendlyByteBuf#readItem

构建JSON

自定义配方JSON与其他配方存储在同一个位置。指定的type应表示配方序列化器的注册表名称。任何附加数据都是由序列化器在解码期间指定的。

{
// 自定义序列化器的注册表名称
"type": "examplemod:example_serializer",
"input": {
// 某些原料输入
},
"data": 0, // 配方所需的一些数据
"output": {
// 某些物品栈输出
}
}

非物品逻辑

如果物品未用作配方输入或结果的一部分,则RecipeManager中提供的常规方法将无效。相反,应将用于测试配方有效性和/或提供结果的附加方法添加到自定义Recipe实例中。从那里,特定RecipeType的所有配方都可以通过RecipeManager#getAllRecipesFor获得,然后使用新实现的方法进行检查和/或提供结果。

// 在某个Recipe子实现ExampleRecipe中

// 检查该位置的方块,看它是否与存储的数据匹配
boolean matches(Level level, BlockPos pos);

// 创建要将指定位置的方块设置为的方块状态
BlockState assemble(RegistryAccess access);

// 在某个管理器类中
public Optional<ExampleRecipe> getRecipeFor(Level level, BlockPos pos) {
return level.getRecipeManager()
.getAllRecipesFor(exampleRecipeType) // 获取所有配方
.stream() // 在所有配方中查阅类型
.filter(recipe -> recipe.matches(level, pos)) // 检查该配方输入是否合法
.findFirst(); // 查找与输入匹配的第一个配方
}

数据生成

所有自定义配方,无论输入或输出数据如何,都可以使用RecipeProvider创建到用于数据生成FinishedRecipe中。