Cosmetic
Affichez un modele 3D Blockbench sur n'importe quel slot d'armure d'un joueur. Le modele suit naturellement les bones vanilla (tilt de la tete, sneak, animations de marche, swing des bras) sans une seule ligne d'OpenGL.
Le module Cosmetic resout un probleme classique du modding 1.12.2 : afficher un modele 3D Blockbench sur un joueur sans ecrire un LayerRenderer custom par item, sans toucher au pipeline de rendu vanilla et sans casser l'armure deja equipee.
Le builder cree un ItemArmor classique avec une texture d'armure totalement transparente (la couche vanilla est invisible) puis attache un LayerRenderer partage qui dessine votre modele Blockbench au-dessus, accroche au bon bone du joueur.
Ce que vous pouvez faire
- Un masque, casque, couronne, chapeau sur le slot
HEAD - Une cape, sac a dos, plastron decoratif sur le slot
CHEST - Des jambieres custom sur le slot
LEGS - Des bottes 3D sur le slot
FEET - Un set complet avec multi-bones (chaque partie suit son bone vanilla : bras, torse, jambes)
Installation rapide en 3 etapes
1. Concevez le modele dans Blockbench
Utilisez le preset Blockbench « Item / Modded Block » (ou « Modded Entity » si vous voulez du multi-bones). La taille standard recommandee pour un casque : un cube centre sur (8, 28, 8) en pixels Blockbench. Sauvegardez le modele JSON dans :
assets/votremod/models/item/barry_mask.json
Et les textures associees dans :
assets/votremod/textures/blocks/barry_mask/<nomTexture>.png
2. Creez l'item avec le builder
import fr.eri.eriapi.cosmetic.EriCosmetics;
import net.minecraft.inventory.EntityEquipmentSlot;
Item barryMask = EriCosmetics.armor()
.modId("eriniumfaction").registryName("barry_mask")
.slot(EntityEquipmentSlot.HEAD)
.displayName("Masque de Barry")
.creativeTab(MyCreativeTab.INSTANCE)
.model("eriniumfaction:item/barry_mask")
.build();
3. Enregistrez l'item dans le registry Forge
Le builder ne s'enregistre pas automatiquement dans le registry Forge (meme convention que EriItem). C'est a vous de le faire dans votre handler de registry :
@SubscribeEvent
public static void registerItems(RegistryEvent.Register<Item> event) {
event.getRegistry().register(barryMask);
}
Cote client, ne faites rien de plus — le layer renderer est attache automatiquement par EriAPI sur le RenderPlayer de chaque variante de skin (default + slim) pendant init.
EriCosmetics.preInit() dans votre FMLPreInitDepuis EriAPI 1.8.5, vous pouvez appeler EriCosmetics.preInit() depuis le FMLPreInitializationEvent de votre ClientProxy. Cela enregistre explicitement le ModelBakeEvent listener qui installe le ArmorCosmeticBakedModel. Optionnel — EriAPI le fait deja automatiquement depuis son propre preInit, mais l'appel explicite documente la dependance et reste valide si une future version d'EriAPI retirait l'enregistrement implicite.
@SideOnly(Side.CLIENT)
public class ClientProxy extends CommonProxy {
@Override
public void preInit(FMLPreInitializationEvent e) {
super.preInit(e);
EriCosmetics.preInit();
}
}
Depuis EriAPI 1.8.2, le module Cosmetic dessine le modele Blockbench en 3D partout : inventaire, hotbar, premiere personne, troisieme personne, sol, fixed (item frame), head. Aucun modele item/generated a creer — le ArmorCosmeticBakedModel est genere automatiquement au ModelBakeEvent et delegue la geometrie au ArmorCosmeticTEISR. Le bloc display du JSON Blockbench (cles gui, firstperson_*, thirdperson_*, ground, fixed, head) est respecte automatiquement par contexte — les translations en pixels Blockbench sont divisees par 16 pour matcher la convention OpenGL des transforms vanilla.
Le rendu passe par TextureManager.bindTexture() (via SimpleTexture), hors de l'item atlas. Resultat : n'importe quelle dimension de PNG fonctionne (POT non requis, dimensions non carrees autorisees, ex. 121x152). Le multi-texture est supporte (chaque cle de la map textures du JSON peut pointer vers une PNG differente) et les UV custom de chaque face sont respectes verbatim — pratique pour les modeles Blockbench complexes avec texture_size non-standard.
API du builder
Entry point : EriCosmetics.armor() retourne un ArmorCosmeticBuilder.
Methodes fluent
| Methode | Defaut | Description |
|---|---|---|
modId(String) | — | Namespace du registry. Obligatoire. |
registryName(String) | — | Path du registry. Obligatoire. ID final : modId:registryName. |
slot(EntityEquipmentSlot) | — | HEAD, CHEST, LEGS ou FEET. Obligatoire. |
model(String) | — | ID Blockbench ("mod:item/nom"). Obligatoire. |
displayName(String) | null | Nom affiche en tooltip. Si null, utilise la cle de traduction. |
creativeTab(CreativeTabs) | null | Onglet creatif. Si null, l'item n'apparait dans aucun onglet. |
scale(float) | 1.0f | Scale uniforme autour de l'origine du bone. |
offset(float x, float y, float z) | (0, 0, 0) | Translation supplementaire en blocs GL (= pixels Blockbench / 16), appliquee APRES le bone offset par defaut. |
rotation(float x, float y, float z) | (0, 0, 0) | Rotation Euler en degres autour de l'origine du bone, dans l'ordre X puis Y puis Z. |
followBones(boolean) | true | Si true, les groupes Blockbench dont le nom matche un bone vanilla sont rendus a ce bone (voir section suivante). Sinon, tout va au bone principal du slot. |
build() | — | Cree et retourne l'Item. Vous devez ensuite l'enregistrer dans le registry Forge. |
Validation a build()
build() leve IllegalStateException si :
modIdest null ou videregistryNameest null ou videslotest null ou pas un des 4 slots autorisesmodelest null ou vide
Mapping des bones par slot
Chaque slot d'armure pointe vers un bone principal. Si votre modele Blockbench n'a qu'un seul groupe (ou plusieurs groupes sans nom particulier), tout est rendu a ce bone.
| Slot | Bone principal | Bones additionnels reconnus (multi-bones) |
|---|---|---|
HEAD | bipedHead | (aucun) |
CHEST | bipedBody | bipedRightArm, bipedLeftArm |
LEGS | bipedRightLeg | bipedLeftLeg |
FEET | bipedRightLeg | bipedLeftLeg |
FEET utilise le bone des jambes ?En 1.12.2, les bottes vanilla sont dessinees sur le bone bipedRightLeg / bipedLeftLeg, pas sur un bone « foot » dedie. Le module suit la meme convention — vos bottes 3D bougent avec la jambe.
Modeles multi-bones
Pour un cosmetic qui couvre plusieurs parties du corps (par exemple un plastron + des manches), nommez vos groupes Blockbench avec les conventions suivantes (insensible a la casse, les espaces et tirets bas sont accepted) :
| Nom du groupe Blockbench | Bone vanilla cible |
|---|---|
head | bipedHead |
body | bipedBody |
right_arm, rightarm | bipedRightArm |
left_arm, leftarm | bipedLeftArm |
right_leg, rightleg | bipedRightLeg |
left_leg, leftleg | bipedLeftLeg |
Chaque groupe racine dont le nom matche est rendu a son bone. Les groupes au nom non-matche tombent dans le bone principal du slot.
Exemple : un plastron avec manches qui suivent les bras
"groups": [
{ "name": "body", "children": [...] },
{ "name": "right_arm", "children": [...] },
{ "name": "left_arm", "children": [...] }
]
Resultat : le body est rendu sur le torse, les right_arm / left_arm suivent les bras du joueur (et donc l'animation d'arm-swing pendant la marche, le mouvement lors d'une attaque, etc.).
Desactiver le dispatch multi-bones
Si vous voulez que TOUT votre modele reste sur le bone principal (par exemple un casque conique avec un groupe interne nomme « body » pour le maillage de support), utilisez .followBones(false) :
EriCosmetics.armor()
.modId("mymod").registryName("conical_hat")
.slot(EntityEquipmentSlot.HEAD)
.model("mymod:item/conical_hat")
.followBones(false) // tout va sur bipedHead
.build();
Offset par defaut & reperage Blockbench
Quand le module dessine votre modele, il appelle d'abord bone.postRender(0.0625f) sur le bone cible (place la matrice OpenGL au pivot du bone, avec sa rotation appliquee — meme pipeline que l'armure vanilla), puis applique un offset par defaut qui ramene l'origine Blockbench (0, 0, 0) a l'emplacement attendu dans le repere du bone.
| Bone | Offset par defaut (x, y, z) en blocs GL |
|---|---|
bipedHead | (-0.5, -1.5, -0.5) |
bipedBody | (-0.5, -1.5, -0.5) |
bipedRightArm | (-0.5, -1.5, -0.5) |
bipedLeftArm | (-0.5, -1.5, -0.5) |
bipedRightLeg | (-0.5, -0.75, -0.5) |
bipedLeftLeg | (-0.5, -0.75, -0.5) |
Verifier visuellement
Concevez votre modele dans Blockbench en partant des reperes suivants :
- Casque : centre la tete sur
(8, 28, 8)en pixels — le centre du cube tomb pile sur la tete vanilla. - Torse : meme reperage que le casque (centre sur
(8, 18, 8)) car le bone du body partage le meme pivot Y que la tete. - Jambes : modelez chaque jambe centree sur
(8, 6, 8), hauteur 12px (de y=0 a y=12).
Le .offset() du builder s'ajoute AU dessus de l'offset par defaut — utilisez-le pour ajuster finement (par exemple si votre casque doit etre legerement en avant : .offset(0, 0, -0.05f)).
Exemple de calcul
Pour un masque dont le devant est dessine en Blockbench from [4,23,3.9] to [12,32,3.9] — centre (8, 27.5, 3.9) :
- Conversion en blocs :
(0.5, 1.71875, 0.24375) - Apres l'offset
(-0.5, -1.5, -0.5):(0, 0.21875, -0.25625)dans le repere du bonebipedHead - La tete vanilla s'etend de
y=-0.5(cou) ay=0(sommet) en bone local.y=0.21875est legerement au-dessus du sommet — corrigeable avec.offset(0, -0.22f, 0)si besoin. - La face avant de la tete vanilla est a
z=-0.25. Le masque tombe az=-0.256— pile devant la tete.
BlockbenchRenderer (helper public)
Le module Cosmetic utilise en interne BlockbenchRenderer, un helper qui rend un AnimatedBlockModel deja parse avec ou sans AnimationPose. Ce helper a ete extrait de AnimatedBlockTESR et est public — vous pouvez l'utiliser pour vos propres systemes de rendu custom (TileEntity, items en main, GUI 3D, etc.).
API
// Rendu complet du modele en pose statique, textures auto-bind
BlockbenchRenderer.renderModel(AnimatedBlockModel model);
// Rendu avec une AnimationPose interpolee
BlockbenchRenderer.renderModel(AnimatedBlockModel model, AnimationPose pose);
// Controle total (bind manuel des textures par le caller)
BlockbenchRenderer.renderModel(AnimatedBlockModel model, AnimationPose pose, boolean bindTextures);
// Rendre un seul groupe a la position actuelle
BlockbenchRenderer.renderGroup(ModelGroup group);
BlockbenchRenderer.renderGroup(ModelGroup group, AnimationPose pose);
BlockbenchRenderer.renderGroup(ModelGroup group, AnimationPose pose,
Map<String, ResourceLocation> textures, boolean bindTextures);
// Variante avec texture overrides (utilisee par AnimatedBlockTESR)
BlockbenchRenderer.renderModelWithOverrides(AnimatedBlockModel model, AnimationPose pose,
Map<String, String> textureOverrides);
Le helper part du principe que l'etat GL est deja prepare : matrices en place, blending / culling / lighting setup, lightmap correct. Il ne push / pop pas la matrice racine et ne reset pas la couleur ou le blend mode. Encapsulez l'appel dans pushMatrix() / popMatrix() et configurez votre etat avant.
Troubleshooting
Le modele n'apparait pas du tout
- Verifiez que vous avez bien enregistre l'item dans le registry Forge (le builder ne le fait pas).
- Verifiez le chemin du modele :
.model("mod:item/nom")doit pointer surassets/mod/models/item/nom.json. - Regardez les logs au demarrage :
EriAPI - ArmorCosmeticManager: attached cosmetic layer to N player renderersdoit apparaitre. SiN=0, votreClientProxytourne avant que le render manager soit pret — ouvrez une issue.
Le casque vanilla est encore visible
La texture transparente est embarquee dans EriAPI sous assets/eriapi/textures/models/armor/transparent_layer_1.png. Si vous voyez un casque blanc / rose (texture manquante), verifiez que le jar EriAPI est bien dans votre dossier mods/ (en dev : ajoutez-le en compile files() dans le build.gradle).
Le modele est decalle / dans la mauvaise position
- Verifiez que votre modele Blockbench est concu dans le bon repere — voir Offsets par defaut.
- Ajustez avec
.offset(x, y, z)(en blocs GL = pixels Blockbench / 16). - Pour un modele importe depuis un autre logiciel, essayez
.rotation(0, 180, 0)— certains exporters inversent le sens du Z.
Les manches ne suivent pas les bras
Verifiez que vos groupes Blockbench s'appellent bien right_arm / left_arm (insensible a la casse, le tiret bas est tolere). Le mapping cherche le nom exact — un groupe nomme « ArmRight » ne sera pas reconnu.
Le modele clignote / disparait selon l'angle
Cause classique : votre modele a des faces transparentes mal triees pour le blend OpenGL. Le module active disableCull() et enableBlend() par defaut, mais l'ordre de dessin reste celui des elements du JSON Blockbench. Pour un effet propre, evitez de superposer plusieurs faces transparentes — preferez un seul cube avec une texture alpha.
Performance
Le layer est appele chaque frame pour chaque joueur visible. Le cout est minimal car :
- Le lookup
Item -> ArmorCosmeticDefinitionest unHashMap.get()O(1). - Le modele Blockbench est cache en memoire (premier load uniquement).
- Aucune allocation par frame — les boucles utilisent des
for(int i)sur les listes existantes. - Si le joueur est invisible (
isInvisible()), le layer est court-circuite.