Faire tourner les mobs : rendre des entités Minecraft vivantes en turntables
Chaque mob du wiki tourne en vue 3/4. Pas un fichier de modèle, pas un screenshot : l'entité vivante, rendue par le jeu en 12 frames. Comment je la fais tourner, et comment je l'empêche de déborder de sa case — démos interactives à l'appui.
Sur modpacks.my-monkey.fr, quand tu survoles un mob, il tourne sur lui-même en vue trois-quarts. C’est le détail dont je suis le plus content, et c’est aussi le plus mal compris : ce n’est ni un modèle 3D chargé dans le navigateur, ni un screenshot. C’est l’entité vivante, rendue par le moteur du jeu en 12 frames posées dans une seule image. Voici comment elle tourne, et comment je l’empêche de sortir de sa case — avec deux démos que tu peux manipuler.
Tiens, commence par jouer avec. Glisse sur le mob pour le faire pivoter, change d’entité :
↔ glisser 60° Pourquoi rendre l’entité vivante, pas son modèle
L’approche évidente serait de lire le fichier de modèle du mob et de le réafficher. Sauf qu’il n’existe pas de format universel. Les entités vanilla ont leur modèle en dur dans le code ; GeckoLib en couvre une quinzaine de mods ; les autres sont éclatés entre formats maison. Vouloir parser « le » modèle, c’est signer pour un parser par mod, à vie.
La bascule, c’est la même que pour le rendu des icônes d’items : ne lis pas le modèle, demande au jeu de dessiner l’entité. Le moteur sait déjà afficher n’importe quelle entité vivante — c’est exactement ce qu’il fait dans l’aperçu d’inventaire. Je spawn l’entité, je l’oriente, je capture. Vanilla, GeckoLib, format exotique : si le jeu sait l’afficher, je sais la capturer. Zéro code spécifique à un mod.
Le turntable : 12 frames et une rotation sur Y
Un turntable, c’est conceptuellement simple : je fais tourner l’entité de 360° par pas de 30°, et je rends une frame à chaque pas. Douze frames, douze angles. Le viewer les rejoue en boucle, et tu obtiens une rotation fluide à partir de douze images plates — ce que la démo ci-dessus montre quand elle « spin ».
Le cœur, c’est l’orientation. En MC 26.1, ça passe par trois quaternions composés :
float bb = Math.max(entity.getBbHeight(), entity.getBbWidth());float scale = (F * fill) / Math.max(bb, 0.5f);EntityRenderState state = mc.getEntityRenderDispatcher().extractEntity(entity, 1.0f);
// les entités GUI rendent à l'envers → flip Z ; bascule caméra ; spin YQuaternionf pose = new Quaternionf().rotateZ((float) Math.PI);Quaternionf cameraOrbit = new Quaternionf().rotateX(TILT); // 0.32 rad, vue 3/4pose.mul(cameraOrbit);pose.rotateY(angle); // angle = frame × 30°
return OffscreenGui.render(F, F, gfx -> gfx.entity(state, scale, new Vector3f(0, 0, 0), pose, cameraOrbit, 0, 0, F, F));flowchart LR E["entité<br/>vivante"] --> X["extractEntity()"] X --> S["EntityRenderState"] Q1["rotateZ(π)<br/>flip vertical"] --> R["gfx.entity(state, scale,<br/>pose, cameraOrbit)"] Q2["rotateX(0.32)<br/>bascule 3/4"] --> R Q3["rotateY(θ)<br/>θ = frame × 30°"] --> R S --> R R --> O["OffscreenGui<br/>→ frame F×F"]
Le rotateZ(π) n’est pas cosmétique : les entités rendues en contexte GUI sortent à l’envers, il faut les retourner. Le rotateX(0.32) donne la légère plongée qui fait la vue trois-quarts. Et le rotateY(angle) est la rotation du turntable proprement dite.
Le vrai combat : empêcher le mob de déborder de sa case
Voilà la partie qui a pris le plus de temps. Le rendu utilise une échelle de caméra fixe, mais les mobs n’ont pas tous la même taille — ni, surtout, la même silhouette par rapport à leur hitbox. Les piques d’un guardian, les tentacules d’un ghast, la masse d’un ravager débordent largement de la boîte de collision sur laquelle l’échelle est calée. Résultat brut : le mob touche les bords de la frame, et se fait couper.
Pousse le curseur ci-dessous : tu montes le remplissage du rendu, et dès que le contenu atteint le bord, les côtés concernés passent au rouge — exactement la condition que le mod surveille.
Le contenu tient dans la frame.
La solution est un auto-fit en deux passes :
flowchart TD
A["rendre les 12 frames<br/>à fill = 0.42"] --> B{"un bord<br/>touché ?"}
B -->|oui| C["fill ×= 0.6"]
C --> A
B -->|non| D["recadrer l'union des bbox<br/>à FIT_FILL = 0.86"]
D --> F["composer la sheet<br/>12 × 128 px"] Passe 1 : je rends les douze frames à un remplissage prudent (0.42 de la frame), et je mesure l’union des boîtes englobantes opaques sur toute la rotation. Si quoi que ce soit touche un bord, je multiplie le remplissage par 0,6 et je recommence — jusqu’à six fois, ce qui couvre un débordement d’environ ×26 (le guardian rentre vers la 3ᵉ tentative). Passe 2 : je recadre l’union sur un carré et je redescends à OUT = 128 px, en visant 86 % de la case. Tout mob finit donc visible en entier et à une taille visuelle homogène, quelle que soit la taille de son modèle.
Le même geste, trois moteurs de rendu
Comme le dumper vit en une branche par version de Minecraft, le turntable a trois implémentations — même idée, API à chaque fois différente :
| Version | Appel de rendu | Retournement | Centrage |
|---|---|---|---|
| Forge 1.18 | PoseStack direct + EntityRenderDispatcher, yBodyRot par frame | aucun (surtout pas de flipY) | feetY = −(bbH·scale)/2 |
| NeoForge 1.21 | InventoryScreen.renderEntityInInventory + GuiGraphics | flipY() à la relecture | feetY = F/2 + (bbH·scale)/2 |
| NeoForge 26.1 | gfx.entity(quaternions) via OffscreenGui (pipeline différé) | flip intégré au pose | recadrage par bbox |
C’est la 26.1 qui a tout rebattu, parce que le jeu est passé au rendu GPU différé : je ne dessine plus dans une frame, j’extrais un gfx.entity(...) et je laisse le GuiRenderer du jeu le flusher dans mon target offscreen. La logique d’auto-fit, elle, se reporte telle quelle d’une version à l’autre — c’est de la géométrie, pas de l’API.
Une sheet pour plusieurs variantes
Dernier piège, côté données cette fois. Les tables de butin sont souvent plus granulaires que les types d’entité. Aether II déclare des loots kirrid/arctic/black quand il n’existe qu’un type d’entité arctic_kirrid ; vanilla fait pareil avec sheep/black. Si je ne fais rien, j’obtiens une nuée d’œufs de spawn fantômes pour des variantes qui n’ont pas de sheet propre.
Donc je collapse : un id de loot en chemin (kirrid/arctic/black) est rabattu sur la sheet d’entité dont l’ensemble de tokens est le plus grand sous-ensemble des tokens du chemin — ce qui gère même les réordonnancements (arctic + kirrid → arctic_kirrid) — et je fusionne les drops. Sur ATM 11, ça fait passer la liste de 258 à 172 mobs, et la moitié du bruit d’œufs de spawn disparaît.
Les limites, honnêtement
- Quelques dizaines d’entités ne rendent rien : entités invisibles, marqueurs, projectiles « vivants » sans modèle affichable. Pour celles-là je retombe sur l’œuf de spawn correspondant, puis sur un glyphe. Personne ne cherche l’icône d’une entité-marqueur.
- De rares mobs à débordement extrême restent rognés au-delà des six tentatives de rétrécissement (un effet de boss dont le visuel déborde de presque dix fois sa hitbox). Marginal, mais réel.
- L’échelle de caméra reste fixe au dump : c’est pour ça que le viewer remesure chaque sheet côté client (le même scan de pixels que la démo) pour réharmoniser les tailles à l’affichage. Le mod cadre, le navigateur peaufine.
Conclusion
Faire tourner un mob, au fond, c’est trois décisions : rendre l’entité vivante plutôt que son fichier de modèle (universel), la capturer en douze frames d’une rotation sur Y (un turntable, pas de la vraie 3D dans le navigateur), et lui garantir qu’elle ne sortira jamais de sa case par un auto-fit qui mesure et rétrécit jusqu’à ce que ça rentre. Le reste — quaternions, flipY, échelles — n’est que la traduction de ces trois idées dans l’API du moment, qui change à chaque version du jeu.
Et puisque tout part du moteur du jeu lui-même, ça marche pour le prochain mob d’un prochain mod sans que j’aie une ligne à écrire pour lui. C’est exactement ce qu’on voulait d’un wiki qui se veut fidèle : pas une approximation dessinée à la main, la vraie créature, qui tourne.
Live : modpacks.my-monkey.fr · survole n’importe quel mob pour le voir tourner.
Chargement…