自定义配方
每个配方定义由三个部分组成:保存数据并根据输入处理执行逻辑的 Recipe 实现;表示配方使用类别或上下文的 RecipeType;负责解码和网络通信的 RecipeSerializer。如何使用配方由实现者决定。
原创说明图,依据 Forge 1.21.x 官方文档 绘制。
Recipe
Recipe 接口描述配方数据及执行逻辑,包括匹配输入并提供相应结果。配方子系统默认执行物品转换,因此通过 Container 子类型提供输入。
重要:传入配方的
Container内容应视为不可变。任何修改操作都应通过ItemStack#copy对输入副本执行。
要从管理器获取配方实例,#matches 必须返回 true。该方法检查容器中的输入是否有效,可以调用 Ingredient#test 使用配料验证。选中配方后,使用 #assemble 构建结果,可以利用输入数据创建结果。
提示:
#assemble应始终产生独立的ItemStack。不确定时,返回前对结果调用ItemStack#copy。
其他方法大多用于配方书集成。
public record ExampleRecipe(Ingredient input, int data, ItemStack output) implements Recipe<Container> {
// 在此实现方法
}
上例使用了 record,但自己的实现不一定要使用。
RecipeType
RecipeType 定义配方使用的类别或上下文。例如熔炉烧炼使用 RecipeType#SMELTING,高炉烧炼使用 RecipeType#BLASTING。如果现有类型均不匹配,应注册新类型,并在新配方子类型的 Recipe#getType 中返回实例。
// 对于某个 RegistryObject<RecipeType> EXAMPLE_TYPE,在 ExampleRecipe 中:
@Override
public RecipeType<?> getType() {
return EXAMPLE_TYPE.get();
}
RecipeSerializer
RecipeSerializer 负责为对应 Recipe 子类型解码 JSON 和进行网络通信。每个解码后的配方都作为独立实例存储在 RecipeManager 中。序列化器必须注册。
只需实现三个方法:
| 方法 | 说明 |
|---|---|
fromJson |
将 JSON 解码为 Recipe 子类型。 |
toNetwork |
将配方编码到缓冲区以发送给客户端,无需编码配方标识符。 |
fromNetwork |
从服务端发送的缓冲区解码配方,无需解码配方标识符。 |
新配方子类型的 Recipe#getSerializer 必须返回该实例:
// 对于某个 RegistryObject<RecipeSerializer> EXAMPLE_SERIALIZER,在 ExampleRecipe 中:
@Override
public RecipeSerializer<?> getSerializer() {
return EXAMPLE_SERIALIZER.get();
}
提示:有些方法可以方便地读写配方数据。
Ingredient可用#fromJson、#toNetwork、#fromNetwork;ItemStack可用CraftingHelper#getItemStack、FriendlyByteBuf#writeItem、FriendlyByteBuf#readItem。
构建 JSON
自定义配方 JSON 与其他配方存放在同一位置。type 应为配方序列化器的注册表名称;额外数据由序列化器在解码时指定。
带注释和省略号的示例用于说明结构;实际 JSON 文件需移除注释并补全内容。
{
"type": "examplemod:example_serializer", // 自定义序列化器注册表名称
"input": { /* 某个配料输入 */ },
"data": 0, // 配方所需的某些数据
"output": { /* 某个输出堆 */ }
}
非物品逻辑
如果配方输入或结果不使用物品,RecipeManager 提供的常规方法便无用。应在自定义 Recipe 实例中添加检查配方有效性和/或提供结果的方法。随后通过 RecipeManager#getAllRecipesFor 获取特定 RecipeType 的所有配方,用新方法检查并提供结果。
// 在某个 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,用于数据生成。
原文:Custom Recipes。