Appearance
CRÉATION DE RECETTES PERSONNALISÉES
Guide de développement complet pour Minecraft Forge 1.20.1
1. Introduction au système de recettes de la 1.20.1
Depuis les versions récentes de Minecraft, le système de recettes est entièrement piloté par les données (Data-driven) au format JSON. Dans Forge 1.20.1, ajouter une recette existante (comme une recette de fabrication sur table ou de cuisson au four) se fait uniquement via des fichiers JSON placés dans vos dossiers de ressources (src/main/resources/data/[modid]/recipes/).
Cependant, lorsque vous créez une machine personnalisée, un bloc spécifique ou un mécanisme inédit (par exemple, un broyeur ou une table d'infusion), vous devez définir un nouveau Recipe Type (Type de recette) et un Recipe Serializer (Sérialiseur de recette) en Java pour permettre à Minecraft de lire, comprendre et appliquer vos fichiers JSON personnalisés.
2. Utiliser les types de recettes Vanilla / Forge existants
Avant d'implémenter un système complexe en Java, sachez que vous pouvez utiliser les types de recettes natifs si votre objet se fabrique simplement. Il suffit de placer un fichier JSON dans votre dossier recipes.
Exemple : Recette en forme (Shaped Recipe) - JSON
json
{
"type": "minecraft:crafting_shaped",
"pattern": [
"X",
"XIX",
"X"
],
"key": {
"X": { "item": "minecraft:diamond" },
"I": { "item": "mymod:my_custom_item" }
},
"result": {
"item": "mymod:my_advanced_block",
"count": 1
}
}3. Création d'un système de recette personnalisé (Mod Recipes)
Pour créer une recette propre à votre mod (par exemple, une recette pour un "Broyeur" qui prend un ingrédient en entrée et produit un résultat après un certain temps), trois composants Java sont obligatoires:
L'Interface / Classe de Recette (
Recipe<Container>) : Définit les données logiques de la recette en mémoire (ingrédients, résultat, ID).Le Sérialiseur (
RecipeSerializer) : Traduit le fichier JSON en objet Java (lecture) et gère la synchronisation réseau entre le serveur et le client.Le Type de Recette (
RecipeType) : Un simple identifiant unique qui catégorise votre recette.
Étape A : Définir la classe de Recette
Voici l'implémentation standard d'une recette personnalisée prenant un ingrédient en entrée et fournissant un objet en sortie.
java
package com.example.mymod.recipe;
import com.example.mymod.MyMod;
import com.google.gson.JsonObject;
import net.minecraft.core.NonNullList;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.*;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
public class CrushingRecipe implements Recipe<Container> {
private final ResourceLocation id;
private final Ingredient input;
private final ItemStack output;
public CrushingRecipe(ResourceLocation id, Ingredient input, ItemStack output) {
this.id = id;
this.input = input;
this.output = output;
}
@Override
public boolean matches(Container container, Level level) {
if (level.isClientSide()) return false;
return input.test(container.getItem(0));
}
@Override
public ItemStack assemble(Container container, RegistryAccess registryAccess) {
return output.copy();
}
@Override
public boolean canCraftInDimensions(int width, int height) {
return true;
}
@Override
public ItemStack getResultItem(RegistryAccess registryAccess) {
return output;
}
@Override
public ResourceLocation getId() {
return id;
}
@Override
public RecipeSerializer<?> getSerializer() {
return ModRecipes.CRUSHING_SERIALIZER.get();
}
@Override
public RecipeType<?> getType() {
return Type.INSTANCE;
}
@Override
public NonNullList<Ingredient> getIngredients() {
NonNullList<Ingredient> ingredients = NonNullList.create();
ingredients.add(this.input);
return ingredients;
}
public static class Type implements RecipeType<CrushingRecipe> {
public static final Type INSTANCE = new Type();
public static final String ID = "crushing";
}
}Note sur la 1.20.1 : La méthode
getResultItem()etassemble()prennent désormais un paramètreRegistryAccessrequis pour gérer correctement les données dynamiques des registres (comme les enchantements ou les potions complexes).
Étape B : Créer le Sérialiseur de Recette (Serializer)
Le sérialiseur doit implémenter RecipeSerializer<T>. Il contient trois méthodes cruciales : fromJson (lecture du JSON data), fromNetwork (lecture des paquets réseau sur le client) et toNetwork (écriture des paquets depuis le serveur vers le client).
java
public static class Serializer implements RecipeSerializer<CrushingRecipe> {
public static final Serializer INSTANCE = new Serializer();
public static final ResourceLocation ID = new ResourceLocation(MyMod.MODID, "crushing");
@Override
public CrushingRecipe fromJson(ResourceLocation recipeId, JsonObject json) {
Ingredient input = Ingredient.fromJson(GsonHelper.getAsJsonObject(json, "ingredient"));
ItemStack output = ShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result"));
return new CrushingRecipe(recipeId, input, output);
}
@Override
public @Nullable CrushingRecipe fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) {
Ingredient input = Ingredient.fromNetwork(buffer);
ItemStack output = buffer.readItem();
return new CrushingRecipe(recipeId, input, output);
}
@Override
public void toNetwork(FriendlyByteBuf buffer, CrushingRecipe recipe) {
recipe.input.toNetwork(buffer);
buffer.writeItem(recipe.output);
}
}4. Enregistrement (Registration) auprès de Forge
Pour que Forge reconnaisse votre nouveau type de recette et son sérialiseur, vous devez utiliser le mécanisme de DeferredRegister. Créez une classe dédiée nommée ModRecipes:
java
package com.example.mymod.recipe;
import com.example.mymod.MyMod;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class ModRecipes {
public static final DeferredRegister<RecipeSerializer<?>> SERIALIZERS =
DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, MyMod.MODID);
public static final DeferredRegister<RecipeType<?>> RECIPE_TYPES =
DeferredRegister.create(ForgeRegistries.RECIPE_TYPES, MyMod.MODID);
public static final RegistryObject<RecipeSerializer<CrushingRecipe>> CRUSHING_SERIALIZER =
SERIALIZERS.register("crushing", () -> CrushingRecipe.Serializer.INSTANCE);
public static final RegistryObject<RecipeType<CrushingRecipe>> CRUSHING_TYPE =
RECIPE_TYPES.register("crushing", () -> CrushingRecipe.Type.INSTANCE);
public static void register(IEventBus eventBus) {
SERIALIZERS.register(eventBus);
RECIPE_TYPES.register(eventBus);
}
}N'oubliez pas d'appeler ModRecipes.register(modEventBus); dans le constructeur principal de votre classe de Mod (@Mod(MyMod.MODID)).
5. Structure du fichier JSON personnalisé
Maintenant que le code Java est prêt, vous pouvez écrire vos recettes personnalisées en JSON. Créez un fichier à l'emplacement suivant:
src/main/resources/data/mymod/recipes/iron_dust_from_crushing.json
json
{
"type": "mymod:crushing",
"ingredient": {
"item": "minecraft:iron_ingot"
},
"result": {
"item": "mymod:iron_dust",
"count": 2
}
}6. Utilisation de la recette dans une machine (BlockEntity)
Pour utiliser cette recette dans votre bloc/machine, vous devez chercher la recette correspondante dans le monde à l'aide du RecipeManager disponible via l'instance de la classe Level.
java
// Exemple de méthode de validation dans votre BlockEntity (méthode tick)
private static void craftItem(Level level, BlockPos pos, MyBlockEntity entity) {
// Création d'un conteneur temporaire pour tester la recette
SimpleContainer container = new SimpleContainer(entity.getContainerSize());
container.setItem(0, entity.getItem(0)); // Slot d'entrée
// Recherche de la recette correspondante
Optional<CrushingRecipe> recipe = level.getRecipeManager()
.getRecipeFor(CrushingRecipe.Type.INSTANCE, container, level);
if (recipe.isPresent()) {
ItemStack result = recipe.get().getResultItem(level.registryAccess());
// Procéder au traitement (retirer l'ingrédient, insérer le résultat)
entity.removeItem(0, 1);
entity.setItem(1, new ItemStack(result.getItem(), entity.getItem(1).getCount() + result.getCount()));
}
}7. Génération Automatique des Recettes (Data Generation)
Pour éviter de créer manuellement des dizaines de fichiers JSON, Forge inclut un système de Data Generation. Vous pouvez étendre la classe RecipeProvider pour générer vos recettes personnalisées proprement.
java
package com.example.mymod.datagen;
import com.example.mymod.recipe.CrushingRecipe;
import net.minecraft.data.PackOutput;
import net.minecraft.data.recipes.FinishedRecipe;
import net.minecraft.data.recipes.RecipeProvider;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import java.util.function.Consumer;
public class ModRecipeProvider extends RecipeProvider {
public ModRecipeProvider(PackOutput output) {
super(output);
}
@Override
protected void buildRecipes(Consumer<FinishedRecipe> writer) {
// Exemple d'implémentation de génération de recette personnalisée
// Vous devez généralement créer un builder dédié pour vos recettes spécifiques
}
}8. Résumé des points de vigilance en 1.20.1
| Élément | Vigilance particulière en 1.20.1 |
|---|---|
| Registry Access | Toujours passer le level.registryAccess() lors de la récupération du résultat (getResultItem). |
| FriendlyByteBuf | L'ordre d'écriture dans toNetwork doit être strictement identique à l'ordre de lecture dans fromNetwork. |
| Side Safety | Assurez-vous de n'effectuer les validations de recettes que du côté Serveur (!level.isClientSide()). |