Appearance
La création d'armure
Objectif : Comprendre l'architecture des armures, lier des textures 3D et programmer des effets de panoplie complets en totale autonomie.
1. Introduction et Architecture du Système d'Armures
Dans Minecraft 1.20.1, le système d'armure repose sur une séparation nette entre les données techniques (durabilité, réduction de dégâts, enchantabilité), représentées par l'interface ArmorMaterial, et l'objet en jeu manipulé par le moteur, représenté par la classe ArmorItem.
Pour créer une armure fonctionnelle, vous devez suivre un flux de développement rigoureux:
Créer un matériau personnalisé (
ArmorMaterial).Enregistrer les quatre pièces de l'armure (Casque, Plastron, Jambières, Bottes).
Assigner les textures 3D lues par le moteur de rendu de l'entité joueur.
Intercepter les ticks de l'entité pour appliquer des effets passifs dynamiques.
2. Étape 1: Création de l'ArmorMaterial Personnalisé
L'interface ArmorMaterial centralise toutes les statistiques de votre ensemble d'armures. Bien que vous puissiez créer une classe qui implémente cette interface, la convention moderne sous Forge consiste à utiliser une structure d'énumération (enum) propre et hautement configurable.
Fichier : src/main/java/com/nomauteur/modid/item/ModArmorMaterials.java
java
package com.nomauteur.modid.item;
import com.nomauteur.modid.ModId;
import com.nomauteur.modid.registry.ModItems;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.util.LazyLoadedValue;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.ArmorMaterial;
import net.minecraft.world.item.crafting.Ingredient;
import java.util.function.Supplier;
public enum ModArmorMaterials implements ArmorMaterial {
AMETHYSTE("amethyste", 28, new int[]{3, 6, 8, 3}, 15,
SoundEvents.ARMOR_EQUIP_DIAMOND, 2.0F, 0.0F, () ->
Ingredient.of(ModItems.AMETHYSTE_GEM.get()));
// Coefficients natifs de Minecraft pour multiplier la durabilité de base
private static final int[] BASE_DURABILITY = new int[] {13, 15, 16, 11};
private final String name;
private final int durabilityMultiplier;
private final int[] protectionAmounts;
private final int enchantmentValue;
private final SoundEvent equipSound;
private final float toughness;
private final float knockbackResistance;
private final LazyLoadedValue<Ingredient> repairIngredient;
ModArmorMaterials(String name, int durabilityMultiplier, int[] protectionAmounts, int enchantmentValue,
SoundEvent equipSound, float toughness, float knockbackResistance,
Supplier<Ingredient> repairIngredient) {
this.name = name;
this.durabilityMultiplier = durabilityMultiplier;
this.protectionAmounts = protectionAmounts;
this.enchantmentValue = enchantmentValue;
this.equipSound = equipSound;
this.toughness = toughness;
this.knockbackResistance = knockbackResistance;
this.repairIngredient = new LazyLoadedValue<>(repairIngredient);
}
@Override
public int getDurabilityForType(ArmorItem.Type type) {
// Associe le multiplicateur à l'emplacement spécifique de la pièce
return BASE_DURABILITY[type.getSlot().getIndex()] * this.durabilityMultiplier;
}
@Override
public int getDefenseForType(ArmorItem.Type type) {
return this.protectionAmounts[type.getSlot().getIndex()];
}
@Override
public int getEnchantmentValue() {
return this.enchantmentValue;
}
@Override
public SoundEvent getEquipSound() {
return this.equipSound;
}
@Override
public Ingredient getRepairIngredient() {
return this.repairIngredient.get();
}
@Override
public String getName() {
return ModId.MOD_ID + ":" + this.name;
}
@Override
public float getToughness() {
return this.toughness;
}
@Override
public float getKnockbackResistance() {
return this.knockbackResistance;
}
}Analyse technique des paramètres :
protectionAmounts: Tableau d'entiers correspondant à(Bottes, Jambières, Plastron, Casque). Un point de protection affiché à l'écran équivaut à 1 demi-tronc d'armure.toughness(Dureté) : Propriété cruciale contre les attaques lourdes. Plus elle est élevée, moins l'armure perd de son efficacité face à de gros dégâts. Le Diamant est à2.0F, la Netherite à3.0F.LazyLoadedValue: Garantit que l'ingrédient de réparation n'est évalué qu'au moment de son utilisation, évitant les crashs d'initialisation si l'item de réparation s'enregistre après l'armure.
3. Étape 2: Enregistrement des Objets d'Armure
Une fois votre matériau défini, vous devez instancier vos objets en passant le type requis (ArmorItem.Type). Depuis les versions récentes, Minecraft utilise cette énumération stricte pour lier le slot d'inventaire et les comportements natifs de l'équipement.
Fichier : src/main/java/com/nomauteur/modid/registry/ModItems.java
java
package com.nomauteur.modid.registry;
import com.nomauteur.modid.ModId;
import com.nomauteur.modid.item.ModArmorMaterials;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ArmorItem.Type;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class ModItems {
public static final DeferredRegister<Item> ITEMS =
DeferredRegister.create(ForgeRegistries.ITEMS, ModId.MOD_ID);
// Enregistrement unitaire de chaque composant de l'armure
public static final RegistryObject<Item> AMETHYSTE_HELMET = ITEMS.register("amethyste_helmet",
() -> new ArmorItem(ModArmorMaterials.AMETHYSTE, Type.HELMET, new Item.Properties()));
public static final RegistryObject<Item> AMETHYSTE_CHESTPLATE = ITEMS.register("amethyste_chestplate",
() -> new ArmorItem(ModArmorMaterials.AMETHYSTE, Type.CHESTPLATE, new Item.Properties()));
public static final RegistryObject<Item> AMETHYSTE_LEGGINGS = ITEMS.register("amethyste_leggings",
() -> new ArmorItem(ModArmorMaterials.AMETHYSTE, Type.LEGGINGS, new Item.Properties()));
public static final RegistryObject<Item> AMETHYSTE_BOOTS = ITEMS.register("amethyste_boots",
() -> new ArmorItem(ModArmorMaterials.AMETHYSTE, Type.BOOTS, new Item.Properties()));
public static void register(IEventBus eventBus) {
ITEMS.register(eventBus);
}
}4. Étape 3: Gestion des Textures 3D sur le Joueur
C'est l'étape où la majorité des développeurs débutants rencontrent des problèmes de "Missing Texture" (damier noir et violet). Minecraft attend des fichiers spécifiques situés dans les assets du mod pour habiller le modèle 3D de l'entité humaine.
Règles de nommage et arborescence
Les fichiers de texture doivent impérativement être placés dans le dossier suivant :
src/main/resources/assets/[modid]/textures/models/armor/
Le moteur graphique compile l'armure en deux couches distinctes:
Couche 1 (
layer_1.png) : Contient les textures appliquées au casque, au plastron, aux épaulettes et aux bottes.Couche 2 (
layer_2.png) : Contient la texture appliquée exclusivement aux jambières (leggings).
⚠️ ATTENTION AU FORMAT DU NOM DE FICHIER ! Le moteur de rendu cherche la chaîne retournée par la méthode
getName()de votre matériau. Dans notre exemple, elle retournemodid:amethyste. Le jeu va donc automatiquement formater la recherche ainsi :
textures/models/armor/amethyste_layer_1.png*textures/models/armor/amethyste_layer_2.pngSi vos fichiers se nomment différemment, la texture ne se chargera pas sur le joueur.
5. Étape 4: Implémentation d'un Effet de Potion de Panoplie Complète
Pour rendre votre armure unique, vous devez surcharger la méthode onArmorTick héritée de ArmorItem. Cette méthode s'exécute à chaque tick de jeu (20 fois par seconde) pour chaque pièce d'armure actuellement équipée par une entité. Afin de créer un code propre, réutilisable et facilement extensible, nous allons concevoir une classe spécialisée nommée ModArmorItem.
Fichier : src/main/java/com/nomauteur/modid/item/ModArmorItem.java
java
package com.nomauteur.modid.item;
import com.google.common.collect.ImmutableMap;
import com.nomauteur.modid.registry.ModItems;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.ArmorMaterial;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import java.util.Map;
public class ModArmorItem extends ArmorItem {
// Map associant un matériau d'armure à son effet de potion associé
private static final Map<ArmorMaterial, MobEffectInstance> MATERIAL_TO_EFFECT_MAP =
new ImmutableMap.Builder<ArmorMaterial, MobEffectInstance>()
.put(ModArmorMaterials.AMETHYSTE, new MobEffectInstance(MobEffects.FIRE_RESISTANCE, 200, 0, false, false, true))
.build();
public ModArmorItem(ArmorMaterial material, Type type, Properties properties) {
super(material, type, properties);
}
@Override
public void onArmorTick(ItemStack stack, Level level, Player player) {
// On exécute la vérification uniquement côté serveur pour éviter les désynchronisations de paquets
if (!level.isClientSide()) {
if (hasFullSuitOfArmor(player)) {
evaluateArmorEffects(player);
}
}
}
private void evaluateArmorEffects(Player player) {
for (Map.Entry<ArmorMaterial, MobEffectInstance> entry : MATERIAL_TO_EFFECT_MAP.entrySet()) {
ArmorMaterial material = entry.getKey();
MobEffectInstance effect = entry.getValue();
if (hasCorrectArmorOn(material, player)) {
addStatusEffectForPlayer(player, effect);
}
}
}
private void addStatusEffectForPlayer(Player player, MobEffectInstance effect) {
// Vérifie si le joueur possède déjà l'effet pour éviter de reset l'animation visuelle en boucle
boolean hasEffect = player.hasEffect(effect.getEffect());
if (!hasEffect) {
// Recrée une instance propre pour rafraîchir la durée correctement
player.addEffect(new MobEffectInstance(effect.getEffect(), effect.getDuration(),
effect.getAmplifier(), effect.isAmbient(), effect.isVisible(), effect.showIcon()));
}
}
private boolean hasFullSuitOfArmor(Player player) {
ItemStack boots = player.getInventory().getArmor(0);
ItemStack leggings = player.getInventory().getArmor(1);
ItemStack chestplate = player.getInventory().getArmor(2);
ItemStack helmet = player.getInventory().getArmor(3);
return !helmet.isEmpty() && !chestplate.isEmpty() && !leggings.isEmpty() && !boots.isEmpty();
}
private boolean hasCorrectArmorOn(ArmorMaterial material, Player player) {
for (ItemStack armorStack : player.getInventory().armor) {
if (!(armorStack.getItem() instanceof ArmorItem)) {
return false;
}
ArmorItem armorItem = (ArmorItem) armorStack.getItem();
if (armorItem.getMaterial() != material) {
return false;
}
}
return true;
}
}Explication des arguments de MobEffectInstance :
new MobEffectInstance(MobEffects.FIRE_RESISTANCE, 200, 0, false, false, true)
200: Durée de l'effet en ticks (200 ticks = 10 secondes). Puisque le tick d'armure rafraîchit l'effet en permanence, une durée de 10 secondes garantit une transition fluide sans clignotement de l'interface.0: Amplificateur de l'effet (0 correspond au niveau I, 1 correspond au niveau II, etc.).false(isAmbient) : Réduit l'opacité des particules à l'écran si vrai (similaire aux effets de balise).false(isVisible) : Définit si les particules magiques s'échappent du corps du joueur. Mis àfalse, votre armure offre un effet propre sans polluer la vue du joueur.true(showIcon) : Affiche l'icône de l'effet en haut à droite de l'écran et dans l'inventaire.
Ajustement requis dans ModItems : Pour que cette classe logique prenne effet, retournez dans votre classe
ModItemset remplacez les instanciationsnew ArmorItem(...)parnew ModArmorItem(...)pour votre ensemble personnalisé.
6. Fichiers JSON requis (Assets et Modélisation standard)
Pour que vos items d'armures aient un visuel 2D propre lorsqu'ils sont tenus en main ou vus dans l'inventaire créatif, vous devez leur générer un fichier de modèle d'item en JSON.
Fichier : src/main/resources/assets/[modid]/models/item/amethyste_helmet.json
json
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "modid:item/amethyste_helmet"
}
}Note : Répétez ce fichier pour les trois autres pièces (
amethyste_chestplate.json,amethyste_leggings.json,amethyste_boots.json) en modifiant l'identifiant de la texture. Le fichier image de la texture de l'item devra se trouver dansassets/[modid]/textures/item/.
7. Guide d'Autonomie : Débugger et Étendre le système
Comment appliquer des attributs uniques (ex: vitesse accrue, vie supplémentaire) ?
Si vous souhaitez que votre armure donne un bonus passif permanent d'attribut (sans passer par le système d'effet de potion), vous pouvez utiliser les événements ou surcharger la méthode getDefaultAttributeModifiers dans votre classe ModArmorItem. Cela permet de lier des modificateurs d'attributs directement aux slots équipés.
Problème fréquent : L'effet ne s'arrête pas quand j'enlève l'armure
Dans l'implémentation ci-dessus, si le joueur retire une pièce d'armure, la méthode onArmorTick s'arrête instantanément. L'effet de potion va donc expirer naturellement après la durée fixée (ex: 10 secondes) car il n'est plus rafraîchi.
Si vous voulez une suppression instantanée, vous devez implémenter un écouteur d'événement global (LivingEquipmentChangeEvent) pour purger manuellement l'effet du joueur dès qu'une pièce est extraite de son inventaire d'équipement.
Vérification finale de votre check-list de compilation :
Le fichier de configuration
ModArmorMaterialsutilise un identifiant en minuscule unique.Les fichiers PNG de couches 3D sont bien enregistrés au format exact
nommatériau_layer_1.pngetnommatériau_layer_2.png.La classe
ModItemsest correctement appelée au lancement du jeu dans votre classe principale via le bus d'événement Forge.