Animation System
Animez des blocs, items et entites Minecraft avec des modeles 3D Blockbench et des fichiers .erianim.json.
Lootbox qui s'ouvre, machine qui tourne, entite avec animation idle — le meme pipeline pour tout.
Introduction
Le module Animation de Blocks 3D permet de creer des blocks Minecraft qui sont rendus avec des modeles 3D Blockbench et animes grace a des fichiers .erianim.json.
Contrairement aux blocks Minecraft classiques qui sont de simples cubes avec des textures sur chaque face, un block anime utilise un TileEntity (une entite stockee dans le monde qui contient des donnees) et un TESR (TileEntity Special Renderer — un systeme de rendu qui dessine le modele 3D a la place du cube).
Concepts cles pour les debutants
- TileEntity — Un objet Java rattache a un block dans le monde. Il stocke des donnees (quel modele afficher, quelle animation jouer, etc.) et survit aux redemarrages du serveur.
- TESR (TileEntity Special Renderer) — Le code qui dessine le modele 3D a chaque image. Il lit les donnees du TileEntity et applique les transformations d'animation.
- Blockbench — Un logiciel gratuit (blockbench.net) pour creer des modeles 3D au format Minecraft. Tu y dessines ton modele avec des cubes, tu le textures, puis tu l'exportes en JSON.
- Fichier .erianim.json — Un fichier JSON EriAPI qui decrit les animations : quels groupes de cubes bougent, comment ils bougent, a quelle vitesse, avec quels effets.
Le pipeline de creation
Voici les etapes du processus, de la creation a l'animation en jeu :
- Modelisation — Creer le modele 3D dans Blockbench. Organiser les cubes en groupes (chaque groupe pourra etre anime independamment).
- Export — Exporter le modele au format "Java Block/Item" (.json).
- Animation — Creer le fichier
.erianim.jsonqui decrit les animations (manuellement ou avec l'editeur EriAnim). - Integration — Enregistrer le block dans le code Java avec
EriAnimBlock.create(). - Runtime — Le TESR lit le modele et l'animation, et dessine le block anime a l'ecran.
Quand tu utilises EriAnimBlock.create(), EriAPI enregistre automatiquement le TileEntity,
le TESR, le blockstate, et le rendu de l'item en main. Tu n'as pas besoin de toucher a du code OpenGL ou de creer des classes supplementaires.
Demarrage rapide
Voici comment creer un block anime complet en 5 etapes. A la fin, tu auras un block qui joue une animation quand on fait clic droit dessus.
Etape 1 — Creer le modele dans Blockbench
Ouvre Blockbench et cree un nouveau projet de type "Java Block/Item". Dessine ton modele en organisant les cubes en groupes nommes.
Par exemple, pour une lootbox : un groupe base pour le socle, un groupe lid pour le couvercle,
un groupe lock pour le cadenas. Chaque groupe pourra etre anime separement.
Exporte en File > Export > Export Java Block/Item Model. Le fichier genere est un .json
que tu placeras dans ton mod.
Etape 2 — Creer le fichier .erianim.json
Cree un fichier JSON qui decrit les animations de ton block. Voici un exemple minimal avec une animation "open" qui fait pivoter le couvercle de la lootbox :
{
"format_version": 1,
"model": "monmod:block/ma_lootbox",
"animations": {
"open": {
"duration": 15,
"endBehavior": "freeze",
"tracks": {
"lid": {
"rotation": [
{ "tick": 0, "value": [0, 0, 0], "easing": "linear" },
{ "tick": 15, "value": [-110, 0, 0], "easing": "ease_out_back" }
]
},
"lock": {
"translation": [
{ "tick": 0, "value": [0, 0, 0], "easing": "linear" },
{ "tick": 8, "value": [0, -3, 0], "easing": "ease_out" }
],
"visible": [
{ "tick": 8, "value": false }
]
}
},
"events": [
{ "tick": 0, "type": "sound", "value": "minecraft:block.chest.open", "volume": 1.0, "pitch": 1.2 },
{ "tick": 15, "type": "callback", "value": "open_complete" }
]
}
}
}
Etape 3 — Placer les fichiers dans les bons dossiers
src/main/resources/assets/monmod/
models/block/ma_lootbox.json ← Modele Blockbench (export)
animations/block/ma_lootbox.erianim.json ← Fichier d'animation
textures/blocks/ma_lootbox/ ← Textures PNG du modele
base.png
lid.png
lock.png
blockstates/ma_lootbox.json ← Blockstate (genere auto)
Etape 4 — Enregistrer le block en Java
import fr.eri.eriapi.anim.AnimatedBlockTileEntity;
import fr.eri.eriapi.anim.EriAnimBlock;
import net.minecraft.block.material.Material;
public class MonBlocks {
public static void init() {
EriAnimBlock.create("monmod", "ma_lootbox")
.material(Material.IRON)
.hardness(5.0f)
.resistance(10.0f)
.model("monmod:block/ma_lootbox")
.animation("monmod:block/ma_lootbox")
.renderDistance(64)
.onRightClick(ctx -> {
// Recuperer le TileEntity du block
AnimatedBlockTileEntity te = (AnimatedBlockTileEntity)
ctx.world.getTileEntity(ctx.pos);
if (te != null) {
te.playAnimation("open");
}
})
.register();
}
}
Etape 5 — Tester en jeu
Place le block dans le monde et fais un clic droit. Le couvercle s'ouvre avec un son de coffre, le cadenas tombe et disparait,
et a la fin de l'animation le callback "open_complete" est declenche.
N'oublie pas d'appeler ContentRegistry.register(this) dans ta classe @Mod avant de declarer tes blocks.
C'est necessaire pour que EriAPI enregistre correctement les blocks, TileEntities et TESR aupres de Forge.
Le TESR (AnimatedBlockTESR) est automatiquement enregistre par ContentRegistry pour tous les blocs crees via EriAnimBlock. Aucun appel manuel a ClientRegistry.bindTileEntitySpecialRenderer() n'est necessaire.
EriAnimBlock — Le Builder (recommande)
EriAnimBlock est le moyen le plus simple de creer un block anime. C'est un builder
(une API fluide ou tu chaines des methodes) qui configure automatiquement tout ce qui est necessaire :
le TileEntity, le TESR, le blockstate, et le rendu de l'item en main.
Builder pour blocks animes avec modeles Blockbench 3D. Configure automatiquement TileEntity + TESR + blockstate + item renderer.
-
Statiquestatic EriAnimBlock create(String modId, String registryName)Cree un nouveau builder. Le
modIdest l'identifiant de ton mod (ex:"monmod"). LeregistryNameest le nom interne du block (ex:"lootbox"). -
SetterEriAnimBlock material(Material mat)Definit le materiau du block (ex:
Material.IRON,Material.WOOD,Material.ROCK). Determine le son de pas, si le block est inflammable, etc. -
SetterEriAnimBlock hardness(float hardness)Definit la durete du block (temps pour le casser). Exemples : pierre = 1.5, fer = 5.0, obsidienne = 50.0.
-
SetterEriAnimBlock resistance(float resistance)Definit la resistance aux explosions. Plus c'est haut, plus le block resiste au TNT et aux creepers.
-
SetterEriAnimBlock harvestTool(String tool, int level)Definit l'outil necessaire pour casser le block et le niveau minimum. Outils :
"pickaxe","axe","shovel". Niveaux : 0 = bois, 1 = pierre, 2 = fer, 3 = diamant. -
SetterEriAnimBlock soundType(SoundType sound)Definit le type de son du block (marcher dessus, le casser, le poser). Exemples :
SoundType.METAL,SoundType.WOOD,SoundType.STONE. -
SetterEriAnimBlock lightLevel(float light)Definit le niveau de lumiere emis par le block (0.0 a 1.0). Une torche = 0.9375, une glowstone = 1.0.
-
SetterEriAnimBlock creativeTab(CreativeTabs tab)Definit l'onglet du menu creatif ou le block apparait.
-
SetterEriAnimBlock drops(Item item, int min, int max)Definit ce que le block lache quand il est casse.
minetmaxdefinissent la plage aleatoire de quantite. -
SetterEriAnimBlock silkTouchable(boolean silk)Si
true, le block peut etre recupere avec Silk Touch (toucher de soie). -
SetterEriAnimBlock model(String modelId)Obligatoire. Definit le modele Blockbench a utiliser. Format :
"modid:path/model"(sans l'extension.json). Le fichier doit etre aassets/modid/models/path/model.json.
Exemple :"monmod:block/lootbox"→assets/monmod/models/block/lootbox.json -
SetterEriAnimBlock animation(String animFileId)Obligatoire. Definit le fichier d'animation
.erianim.json. Format :"modid:path/anim"(sans l'extension.erianim.json). Le fichier doit etre aassets/modid/animations/path/anim.erianim.json.
Exemple :"monmod:animations/lootbox"→assets/monmod/animations/animations/lootbox.erianim.json -
SetterEriAnimBlock renderDistance(double distance)Definit la distance maximale (en blocks) a laquelle le modele 3D est rendu. Par defaut :
64blocks. Au-dela, le block est invisible. -
SetterEriAnimBlock defaultAnimation(String animName)Definit une animation qui se joue automatiquement quand le block est charge (avec un EndBehavior LOOP). Ideal pour un idle/respiration. Laisser vide pour pas d'animation automatique.
-
SetterEriAnimBlock tileEntity(Class<?> tileClass)Remplace le TileEntity automatique par une classe personnalisee. La classe doit etendre
AnimatedBlockTileEntityet avoir un constructeur sans argument. Utilise ceci quand tu as besoin de callbacks personnalises (voir section Callbacks). -
CallbackEriAnimBlock onRightClick(Consumer<BlockActionContext> handler)Code execute quand un joueur fait clic droit sur le block. Le
BlockActionContextcontientworld,pos,player,hand,facing. -
CallbackEriAnimBlock onBreak(Consumer<BlockActionContext> handler)Code execute quand le block est casse par un joueur.
-
CallbackEriAnimBlock onPlace(Consumer<BlockActionContext> handler)Code execute quand le block est pose par un joueur.
-
SetterEriAnimBlock javaAnimation(String name, EriAnimJava anim) v1.6.5Enregistre une animation Java directement sur le builder — plus besoin de creer une sous-classe de
AnimatedBlockTileEntityjuste pour appeleraddJavaAnimation(). L'instance est enregistree automatiquement sur le TileEntity (server + client) lors de sa creation. Coexiste avec.animation()pour les fichiers.erianim. Appelable plusieurs fois. -
FinalEriAnimBlock register()Enregistre le block. Configure automatiquement : TileEntity, TESR, type de rendu
ENTITYBLOCK_ANIMATED, item renderer, etopaque/fullCube = false. Si aucun TileEntity custom n'est specifie, utiliseAnimatedBlockTileEntityGeneric.
EriAnimBlock.create("monmod", "machine_raffinage")
.material(Material.IRON)
.hardness(5.0f)
.resistance(10.0f)
.harvestTool("pickaxe", 2)
.soundType(SoundType.METAL)
.lightLevel(0.5f)
.model("monmod:block/machine_raffinage")
// Anims Java declarees directement sur le builder (v1.6.5)
.javaAnimation("idle", new IdleAnim())
.javaAnimation("work", new WorkAnim())
.defaultAnimation("idle") // auto-play au spawn
.renderDistance(48)
.onRightClick(ctx -> {
AnimatedBlockTileEntity te = (AnimatedBlockTileEntity)
ctx.world.getTileEntity(ctx.pos);
if (te != null) te.playAnimation("work");
})
.onBreak(ctx -> {
// Lacher des items quand le block est casse
dropItems(ctx.world, ctx.pos);
})
.register();
.tileEntity(MyTE.class) ? Le builder
.javaAnimation() couvre 99% des cas. Tu n'as besoin de declarer une classe
TileEntity custom que si tu veux override les callbacks
(onAnimationComplete, onAnimationEvent,
onAnimationCallback) ou ajouter des champs / NBT custom au TE.
Java Animation API v1.6.4
Depuis la v1.6.4, EriAPI permet d'ecrire des animations directement en Java, sans passer par un fichier
.erianim. C'est utile pour les animations procedurales (oscillations basees sur sin/cos,
reactions a l'etat de l'entite, mouvements parametriques, etc.) qu'il serait penible voire impossible d'exprimer en JSON.
Une animation Java est une classe qui etend EriAnimJava et qui propose deux points d'extension :
defineKeyframes(AnimContext ctx)— declarations de keyframes statiques (equivalent du JSON.erianim). Appele une seule fois, le resultat est cache.compute(AnimContext ctx, AnimationPose pose)— modifications procedurales appliquees par-dessus la pose interpolee. Appele chaque frame.
Les animations Java cohabitent avec les animations .erianim sur le meme TileEntity. La meme API
te.playAnimation("nom") dispatche automatiquement vers le bon chemin selon le nom enregistre.
EriAnimJava — classe abstraite
Crees une classe qui etend EriAnimJava et implemente au minimum duration() :
import fr.eri.eriapi.anim.*;
import fr.eri.eriapi.gui.anim.Easing;
public class WalkAnim extends EriAnimJava {
@Override
public void defineKeyframes(AnimContext ctx) {
// Equivalent au JSON .erianim, mais en code
ctx.at(0)
.group("leg_left").rotateTo(-30, 0, 0).easing(Easing.EASE_IN_OUT)
.group("leg_right").rotateTo(30, 0, 0).easing(Easing.EASE_IN_OUT);
ctx.at(15)
.group("leg_left").rotateTo(30, 0, 0)
.group("leg_right").rotateTo(-30, 0, 0);
ctx.at(30)
.group("leg_left").rotateTo(-30, 0, 0)
.group("leg_right").rotateTo(30, 0, 0);
}
@Override
public void compute(AnimContext ctx, AnimationPose pose) {
// Procedural: bobbing de la tete sur sin(t)
float t = ctx.animTick + ctx.partial;
pose.group("head").addRotateY((float) Math.sin(t * 0.2f) * 5f);
}
@Override public float duration() { return 30f; }
@Override public EndBehavior endBehavior() { return EndBehavior.LOOP; }
}
Methodes disponibles a override
| Methode | Type retour | Description |
|---|---|---|
defineKeyframes(AnimContext ctx) | void | Declaratif. Optionnel. Cache (rebuilt sur invalidateDef()). |
compute(AnimContext ctx, AnimationPose pose) | void | Procedural. Optionnel. Appele chaque frame. |
duration() | float | Obligatoire. Duree totale en ticks. |
endBehavior() | EndBehavior | Comportement de fin (defaut FREEZE). Voir EndBehaviors. |
invalidateDef() | void | Force le rebuild de defineKeyframes() au prochain frame. |
Enregistrement et lecture
// Dans le constructeur du TileEntity (ou un init partage server+client)
public MyTileEntity() {
addJavaAnimation("walk", new WalkAnim());
addJavaAnimation("idle", new IdleAnim());
}
// Lecture (cote serveur, comme .erianim)
te.playAnimation("walk");
te.pauseAnimation();
te.resumeAnimation();
te.resetAnimationInstant();
AnimContext
Objet contextuel passe a defineKeyframes() et compute(). Champs publics modifiables :
| Champ | Type | Description |
|---|---|---|
animTick | float | Tick d'animation courant (avec partie partielle). Disponible dans compute(). |
partial | float | Partial ticks bruts [0.0, 1.0) pour interpolation fine. |
worldTick | long | Tick du monde au moment du rendu. |
target | Object | Le TileEntity ou l'entite. Cast via les helpers. |
Methodes
| Methode | Retour | Description |
|---|---|---|
at(int tick) | TickBuilder | Demarre une keyframe a ce tick. Utilise dans defineKeyframes(). |
asEntity() | EntityLiving | Cast de target, ou null si non-entite. |
asTileEntity() | TileEntity | Cast de target, ou null si non-TE. |
DSL at(tick).group(name).property(...)
L'API fluide de defineKeyframes() permet de chainer keyframes et groupes a un meme tick :
ctx.at(0)
.group("arm_left").rotateTo(0, 0, 0)
.group("arm_right").rotateTo(0, 0, 0)
.group("body").translateTo(0, 0, 0).easing(Easing.EASE_OUT)
.at(20)
.group("arm_left").rotateTo(0, 45, 0)
.group("arm_right").rotateTo(0, -45, 0).scaleTo(1.1f, 1.1f, 1.1f)
.group("body").translateY(0.2f);
GroupKeyframeBuilder — methodes
| Methode | Description |
|---|---|
rotateTo(x, y, z) / rotateX/Y/Z(v) | Definit la rotation cible (en degres). |
translateTo(x, y, z) / translateX/Y/Z(v) | Definit la translation cible. |
scaleTo(x, y, z) / scale(s) | Definit l'echelle (uniforme ou par axe). |
visible(boolean) | Definit la visibilite a ce tick (instantanee). |
easing(Easing) | Easing utilise pour atteindre cette keyframe (defaut LINEAR). |
group(name) | Bascule sur un autre groupe au meme tick. |
at(tick) | Passe a un nouveau tick. |
GroupPoseBuilder — modifier la pose dans compute()
Dans compute(), utilise pose.group("nom").property(...) pour modifier la pose interpolee.
Deux familles de methodes :
add*(additif) — ajoute a la valeur produite par les keyframes. Utilisation typique pour stacker des effets procedural sur une animation declarative.set*/ sans prefixe (remplacement) — ecrase la valeur produite par les keyframes.
Exemple — oscillation procedural sur la tete
@Override
public void compute(AnimContext ctx, AnimationPose pose) {
float t = ctx.animTick + ctx.partial;
// Tete: balance a gauche/droite
pose.group("head")
.addRotateY((float) Math.sin(t * 0.15f) * 8f)
.addRotateZ((float) Math.cos(t * 0.10f) * 3f);
// Body: petite respiration verticale
pose.group("body").addTranslateY((float) Math.sin(t * 0.05f) * 0.05f);
// Reaction a l'etat de l'entite (si target est une entite)
if (ctx.asEntity() != null) {
float yaw = ctx.asEntity().rotationYaw;
pose.group("head").addRotateY(yaw * 0.1f);
}
}
Methodes du GroupPoseBuilder
| Categorie | Methodes |
|---|---|
| Rotation (set) | rotateX(v), rotateY(v), rotateZ(v), rotateTo(x,y,z) |
| Rotation (additif) | addRotateX(v), addRotateY(v), addRotateZ(v) |
| Translation (set) | translateX(v), translateY(v), translateZ(v), translateTo(x,y,z) |
| Translation (additif) | addTranslateX(v), addTranslateY(v), addTranslateZ(v) |
| Echelle | scaleTo(x,y,z), scale(s) |
| Visibilite | visible(boolean) |
| Chainage | group(String) — bascule sur un autre groupe |
add* par defaut, en complement de defineKeyframes().
Utilise set* uniquement si tu veux contourner totalement le resultat des keyframes (ex : derniers ajustements en
fonction d'un parametre runtime).
Overlay — jouer une animation par-dessus une autre
Le slot overlay permet de jouer une seconde animation Java par-dessus l'animation principale. L'overlay gagne sur tous les groupes qu'il touche ; les groupes qu'il ne touche pas conservent la pose de l'animation principale.
L'overlay est local au client — il n'est pas synchronise par packets. Utilise-le pour des effets visuels reactifs (clignotement, recul d'arme, brillance temporaire) declenches cote client uniquement.
// Lancer l'overlay
te.playOverlay("flash", new FlashOverlayAnim());
// Stopper
te.stopOverlay();
// Verifier
boolean active = te.hasOverlay();
String name = te.getOverlayAnimName();
API overlay
| Methode | Retour | Description |
|---|---|---|
playOverlay(String, EriAnimJava) | void | Joue une anim Java en overlay. Boucle automatiquement. |
stopOverlay() | void | Arrete l'overlay. |
hasOverlay() | boolean | true si un overlay est actif. |
getOverlayAnimName() | String | Nom de l'overlay, ou null. |
addJavaAnimation(name, anim) | void | Enregistre une anim Java dans le registre principal. |
isJavaAnimation(name) | boolean | true si name est dans le registre Java. |
getJavaAnimation(name) | EriAnimJava | Recupere une anim enregistree. |
EndBehavior FREEZE, WAIT_SERVER, LOOP,
LOOP_COUNT, LOOP_PAUSE et le reset instantane sont supportes pour les anims Java.
Les ROLLBACK sont traites comme un reset instantane (pas de rollbackDuration exposee).
Methode alternative : EriBlock + TileEntity custom
Si tu as besoin de logique serveur complexe dans ton block anime (gestion d'inventaire, interactions
conditionnelles, enchainement d'animations...), tu peux creer une sous-classe de AnimatedBlockTileEntity
et la passer au builder via .tileEntity().
import fr.eri.eriapi.anim.AnimatedBlockTileEntity;
import fr.eri.eriapi.anim.AnimationEvent;
public class TileLootbox extends AnimatedBlockTileEntity {
private boolean isOpen = false;
// Constructeur sans argument OBLIGATOIRE
public TileLootbox() {
super("monmod:block/lootbox", "monmod:block/lootbox");
// ^--- modelId ^--- animFileId
}
@Override
protected void onAnimationCallback(String callbackName) {
if ("open_complete".equals(callbackName)) {
isOpen = true;
// Donner les recompenses au joueur, etc.
}
}
@Override
protected void onAnimationComplete(String animName) {
if ("close".equals(animName)) {
isOpen = false;
}
}
@Override
protected void onAnimationEvent(AnimationEvent event) {
if (event.getType() == AnimationEvent.EventType.PARTICLE) {
// Spawn des particules custom
spawnParticles(event.getCount(), event.getSpread());
}
}
public boolean isOpen() { return isOpen; }
}
EriAnimBlock.create("monmod", "lootbox")
.material(Material.WOOD)
.hardness(3.0f)
.model("monmod:block/lootbox")
.animation("monmod:block/lootbox")
.tileEntity(TileLootbox.class) // ← Notre TE custom
.onRightClick(ctx -> {
TileLootbox te = (TileLootbox) ctx.world.getTileEntity(ctx.pos);
if (te != null && !te.isOpen()) {
te.playAnimation("open");
}
})
.register();
Initialiser un bloc avec uniquement des animations Java v1.6.5
Si toutes les animations de ton bloc sont codees en Java (sous-classes de EriAnimJava),
tu n'as plus besoin de fichier .erianim ni de TE custom. Depuis la v1.6.5,
declare les anims directement sur le builder via .javaAnimation(name, anim) —
EriAPI les enregistre automatiquement sur le TileEntity (server + client).
Les deux approches (.animation() avec un fichier .erianim
et .javaAnimation() avec une instance Java) coexistent et peuvent etre
mixees sur le meme bloc. Voici les trois variantes possibles :
// Approche .erianim — necessite un fichier assets/monmod/animations/ma_machine.erianim.json
EriAnimBlock.create("monmod", "ma_machine")
.material(Material.IRON)
.hardness(5.0f)
.model("monmod:block/ma_machine")
.animation("monmod:animations/ma_machine") // ← fichier .erianim
.defaultAnimation("idle")
.onRightClick(ctx -> {
AnimatedBlockTileEntity te = (AnimatedBlockTileEntity)
ctx.world.getTileEntity(ctx.pos);
if (te != null) te.playAnimation("work");
})
.register();
EriAnimBlock.create("monmod", "ma_machine")
.material(Material.IRON)
.hardness(5.0f)
.model("monmod:block/ma_machine")
// Anims Java directement sur le builder — pas besoin de TE custom
.javaAnimation("idle", new IdleAnim())
.javaAnimation("work", new WorkAnim())
.defaultAnimation("idle") // auto-play au spawn
.onRightClick(ctx -> {
AnimatedBlockTileEntity te = (AnimatedBlockTileEntity)
ctx.world.getTileEntity(ctx.pos);
if (te != null) te.playAnimation("work");
})
.register();
// Mix : animations .erianim + animations Java sur le meme bloc
EriAnimBlock.create("monmod", "ma_machine")
.model("monmod:block/ma_machine")
.animation("monmod:animations/ma_machine") // animations .erianim
.javaAnimation("glow", new GlowPulseAnim()) // animation Java en plus
.register();
.tileEntity(MyTE.class) ? Le builder
.javaAnimation() suffit pour declarer/jouer des animations. Tu n'as besoin
d'une classe TE custom que pour override les callbacks d'animation
(onAnimationComplete, onAnimationEvent,
onAnimationCallback) ou ajouter des champs / NBT custom. Dans ce cas,
ajoute simplement addJavaAnimation() dans le constructeur du TE.
.animation() ? L'appel .animation()
reste obligatoire si le bloc utilise des animations .erianim
en plus des animations Java. Il peut etre omis si toutes les animations
sont definies en Java.
AnimatedBlockTileEntity
C'est le coeur du systeme d'animation. Chaque block anime dans le monde possede un AnimatedBlockTileEntity
qui stocke l'etat de l'animation (quelle animation joue, a quel moment, en pause ou non, etc.).
Toutes les methodes de controle (playAnimation, pauseAnimation, etc.) doivent etre appelees
cote serveur. Le TileEntity envoie automatiquement des paquets reseau aux clients pour synchroniser l'animation.
TileEntity pour blocks rendus avec des modeles Blockbench et animes via des fichiers .erianim. Implemente ITickable mais retourne immediatement quand l'etat est IDLE/FROZEN/PAUSED/WAITING_SERVER pour eviter toute surcharge.
Constructeurs
-
ConstructeurAnimatedBlockTileEntity()Constructeur vide. Le modelId et animFileId seront configures par le builder ou manuellement via les setters.
-
ConstructeurAnimatedBlockTileEntity(String modelId)Constructeur avec l'identifiant du modele Blockbench. Ex:
"monmod:block/lootbox". -
ConstructeurAnimatedBlockTileEntity(String modelId, String animFileId)Constructeur complet.
modelId= modele 3D,animFileId= fichier d'animation.
Controle des animations (serveur uniquement)
-
Serveurvoid playAnimation(String animName)Joue l'animation par son nom. Utilise le
EndBehaviorpar defaut defini dans le fichier.erianim.json. Envoie automatiquement un paquet a tous les clients qui voient le block.
Cote : Serveur uniquement. Ne fait rien si appele cote client. -
Serveurvoid playAnimation(String animName, EndBehavior endBehaviorOverride)Joue l'animation avec un
EndBehaviordifferent de celui du fichier.erianim.json. Passernullpour utiliser le comportement par defaut du fichier.
Cote : Serveur uniquement. -
Serveurvoid pauseAnimation()Met l'animation en pause au tick actuel. Le block se fige a la position courante. N'a d'effet que si l'etat est
PLAYING.
Cote : Serveur uniquement. -
Serveurvoid resumeAnimation()Reprend une animation en pause. Le block continue la ou il s'etait arrete. N'a d'effet que si l'etat est
PAUSED.
Cote : Serveur uniquement. -
Serveurvoid resetAnimation()Reinitialise l'animation. Si l'animation actuelle a un
EndBehaviorde typeROLLBACKet unrollbackDuration > 0, le block rejoue l'animation en sens inverse (retour fluide a la position initiale). Sinon, revient instantanement a la position de depart.
Cote : Serveur uniquement. -
Serveurvoid resetAnimation(boolean instant)Reinitialise l'animation. Si
instantesttrue, revient toujours immediatement a la position de depart (ignore le rollback). Sifalse, utilise le rollback si leEndBehaviorestROLLBACK.
Cote : Serveur uniquement. -
Serveurvoid stopAnimation()Arrete immediatement l'animation. Aucun rollback, aucun callback
onAnimationComplete. Le block passe directement a l'etatIDLE. Utilise pour couper net une animation.
Cote : Serveur uniquement.
Etat et progression
-
Getterboolean isAnimating()Retourne
truesi le block est en train de jouer une animation (PLAYINGouROLLING_BACK). Retournefalsepour tous les autres etats (IDLE, FROZEN, PAUSED, WAITING_SERVER). -
GetterAnimState getAnimState()Retourne l'etat actuel de l'animation. Voir la section AnimStates pour la liste des etats possibles.
-
GetterString getCurrentAnimation()Retourne le nom de l'animation en cours, ou une chaine vide
""si aucune animation n'est active. -
Getterfloat getAnimationProgress()Retourne la progression de l'animation entre
0.0(debut) et1.0(fin). Retourne0.0si aucune animation n'est active. Retourne1.0si l'animation est en etatFROZENouWAITING_SERVER.
Cote : Client et serveur. -
GetterString getCurrentAnimName()Retourne le nom interne de l'animation en cours. Identique a
getCurrentAnimation().
Textures dynamiques
-
Serveurvoid setTexture(String target, String value)Remplace une texture du modele par une autre en temps reel.
targetest la cle de texture originale (definie dans le modele Blockbench, ex:"side_core").valueest la cle de la texture de remplacement (ex:"side_core_active"). La texture de remplacement doit exister dans la map de textures du modele. La modification est synchronisee automatiquement aux clients.
Resolution des cles : Le parametretargetest resolu a la fois par la cle JSON et par la valeur du modele Blockbench. Par exemple, si le modele contient"beam": "rarity_beam", alorssetTexture("beam", ...)etsetTexture("rarity_beam", ...)fonctionnent tous les deux.
Cote : Serveur uniquement. -
Serveurvoid clearTexture(String target)Supprime un override de texture specifique. Le modele reprend la texture originale pour cette cle.
Cote : Serveur uniquement. -
Serveurvoid clearAllTextures()Supprime tous les overrides de texture. Le modele reprend toutes ses textures originales.
Cote : Serveur uniquement. -
GetterMap<String, String> getTextureOverrides()Retourne la map des overrides de texture actuels (target → value). Lecture seule.
Configuration
-
Settervoid setAnimFileId(String animFileId)Change le fichier d'animation utilise par ce TileEntity. Utile pour charger des animations differentes sur le meme modele en runtime.
-
GetterString getAnimFileId()Retourne l'identifiant du fichier d'animation actuel.
-
Settervoid setModelId(String modelId)Change le modele 3D utilise par ce TileEntity.
-
GetterString getModelId()Retourne l'identifiant du modele 3D actuel.
-
Settervoid setMaxRenderDistance(double distance)Change la distance maximale de rendu (en blocks).
Callbacks du TileEntity
Si tu crees une sous-classe de AnimatedBlockTileEntity, tu peux surcharger 3 methodes de callback
pour reagir aux evenements de l'animation. Ces callbacks sont appeles cote serveur uniquement.
-
Serveurvoid onAnimationEvent(AnimationEvent event)Appele chaque fois qu'un event du fichier
.erianim.jsonse declenche (son, particule, callback, hide/show group). Le parametreeventcontient le type (event.getType()), la valeur (event.getValue()), et les proprietes specifiques au type (volume, pitch, count, spread). -
Serveurvoid onAnimationComplete(String animName)Appele quand une animation se termine et que son
EndBehaviorest resolu. Par exemple, appele apres un freeze, apres la fin d'un rollback, ou quand unLOOP_COUNTatteint sa derniere iteration. -
Serveurvoid onAnimationCallback(String callbackName)Appele quand un event de type
"callback"se declenche dans le fichier.erianim.json. LecallbackNamecorrespond au champ"value"de l'event. C'est le moyen principal pour synchroniser la logique serveur avec l'animation.
public class TileMachine extends AnimatedBlockTileEntity {
public TileMachine() {
super("monmod:block/machine", "monmod:block/machine");
}
@Override
protected void onAnimationCallback(String callbackName) {
switch (callbackName) {
case "produce_item":
// L'animation a atteint le point ou l'item est cree
produceOutput();
break;
case "consume_fuel":
// L'animation a atteint le point ou le carburant est consomme
consumeFuel();
break;
}
}
@Override
protected void onAnimationComplete(String animName) {
if ("process".equals(animName)) {
// L'animation de traitement est terminee
// Relancer si il y a encore du carburant
if (hasFuel()) {
playAnimation("process");
}
}
}
@Override
protected void onAnimationEvent(AnimationEvent event) {
if (event.getType() == AnimationEvent.EventType.PARTICLE) {
// Spawn des particules de fumee cote serveur
// Les particules seront visibles par tous les joueurs a proximite
if (world instanceof WorldServer) {
((WorldServer) world).spawnParticle(
EnumParticleTypes.SMOKE_NORMAL,
pos.getX() + 0.5, pos.getY() + 1.0, pos.getZ() + 0.5,
event.getCount(), event.getSpread(), 0.1, event.getSpread(),
0.01);
}
}
}
}
Declencher une animation
Cette section regroupe les cas concrets les plus courants pour declencher une animation : sur un clic droit, chaque tick, en reponse a un event Forge, depuis une commande, ou en fonction du comportement d'une entite (idle, walk, attack). Tous les exemples sont cote serveur — EriAPI synchronise automatiquement l'etat d'animation aux clients.
Block — Clic droit (ouvrir une lootbox)
Utilise le callback onRightClick du builder EriAnimBlock.
Verifie toujours isAnimating() pour eviter de relancer une animation
deja en cours. playAnimation() envoie automatiquement les packets
reseau aux clients.
// EriAnimBlock avec callback onRightClick
EriAnimBlock.create("monmod", "lootbox")
.animation("monmod:block/lootbox")
.onRightClick(ctx -> {
AnimatedBlockTileEntity te = (AnimatedBlockTileEntity)
ctx.world.getTileEntity(ctx.pos);
if (te != null && !te.isAnimating()) {
te.playAnimation("open"); // Declenche l'animation "open"
}
})
.register();
Block — Tick continu (machine qui tourne)
Pour une machine qui doit animer en boucle tant qu'elle est active, on sous-classe
AnimatedBlockTileEntity et on surcharge update().
L'animation est relancee chaque fois qu'elle se termine.
resetAnimation() remet le modele en position neutre.
// Sous-classe AnimatedBlockTileEntity avec ITickable
public class TileMachine extends AnimatedBlockTileEntity {
private boolean running = false;
@Override
public void update() {
super.update();
if (!world.isRemote && running && !isAnimating()) {
// Relance l'animation en boucle
playAnimation("spin");
}
}
public void setRunning(boolean running) {
this.running = running;
if (!running) resetAnimation();
}
}
Block — Reagir a un event externe (explosion, joueur qui approche)
On peut declencher une animation depuis n'importe quel event Forge via
EriEvents. Ici, tous les blocks animes dans un rayon de 5 blocs
autour d'une explosion jouent l'animation "shake".
// Via EriEvents - bloquer l'animation quand une explosion explose a cote
EriEvents.on(ExplosionEvent.Detonate.class)
.handle(e -> {
BlockPos center = e.getExplosion().getPosition().toBlockPos();
// Chercher notre block dans un rayon de 5 blocs
for (BlockPos pos : BlockPos.getAllInBoxMutable(
center.add(-5,-5,-5), center.add(5,5,5))) {
TileEntity te = e.world.getTileEntity(pos);
if (te instanceof AnimatedBlockTileEntity) {
((AnimatedBlockTileEntity) te).playAnimation("shake");
}
}
});
Block — Declencher depuis une commande
Utile pour debugger ou offrir une commande admin. Le builder EriCommand
fournit deja l'argument PosArg avec auto-completion.
// Dans un EriCommand
EriCommand.create("machine")
.sub("start")
.arg(PosArg.of("pos"))
.execute(ctx -> {
BlockPos pos = ctx.getPos("pos");
TileEntity te = ctx.getSender().world.getTileEntity(pos);
if (te instanceof AnimatedBlockTileEntity) {
((AnimatedBlockTileEntity) te).playAnimation("run");
ctx.reply("Animation demarree.");
}
})
.register();
Entite — Animation idle (toujours active)
L'animation "idle" tourne en permanence. Les autres animations
(walk, attack) l'interrompent temporairement. Penser a configurer
endBehavior: "loop" dans le .erianim.json.
Ces exemples s'appliquent dans ta classe d'entite Java qui etend
EntityLiving (ou EntityCreature, EntityMob, etc.)
et implemente IAnimatedEntity. EriEntity est un builder
de configuration — il ne fournit pas de callbacks comportementaux dans la version actuelle.
// Dans ta classe entite Java (extends EntityLiving implements IAnimatedEntity)
public class EntityMonstre extends EntityLiving implements IAnimatedEntity {
@Override
public void onUpdate() {
super.onUpdate();
if (!world.isRemote) {
// Lancer idle si aucune animation n'est en cours
if (dataManager.get(ANIM_NAME).isEmpty()) {
playAnimation("idle");
}
}
}
}
Entite — Animation de marche (basee sur le mouvement)
On deduit l'etat d'animation de la vitesse de l'entite. La transition walk ↔ idle est automatique des qu'un seuil de vitesse est franchi.
// Dans ta classe entite Java (extends EntityLiving implements IAnimatedEntity)
public class EntityMonstre extends EntityLiving implements IAnimatedEntity {
@Override
public void onUpdate() {
super.onUpdate();
if (!world.isRemote) {
double speed = Math.sqrt(motionX * motionX + motionZ * motionZ);
String current = dataManager.get(ANIM_NAME);
if (speed > 0.01 && !"walk".equals(current)) {
playAnimation("walk"); // Commence a marcher
} else if (speed <= 0.01 && "walk".equals(current)) {
playAnimation("idle"); // Revient en idle
}
}
}
}
Entite — Animation d'attaque (au moment de frapper)
attackEntityAsMob est appele cote serveur quand l'entite frappe.
On declenche l'animation apres avoir confirme que le coup a atterri.
// Dans ta classe entite Java (extends EntityMob implements IAnimatedEntity)
public class EntityMonstre extends EntityMob implements IAnimatedEntity {
@Override
public boolean attackEntityAsMob(Entity target) {
boolean result = super.attackEntityAsMob(target);
if (result && !world.isRemote) {
playAnimation("attack"); // Lance l'animation d'attaque
}
return result;
}
}
Item anime — Lecture d'animations v1.3.2
AnimatedItemController permet de jouer des animations
.erianim.json en temps reel sur les items animes. L'etat
de lecture est partage par modelId : tous les items
rendus avec le meme modele partagent la meme animation.
// Declarer l'item avec modele anime
EriItem.create("monmod", "epee")
.animatedModel("monmod:item/epee")
.onRightClick(ctx -> {
if (ctx.world.isRemote) {
AnimatedItemController.play("monmod:item/epee", "use");
}
})
.register();
// Depuis n'importe ou cote client
AnimatedItemController.play("monmod:item/epee", "monmod:item/epee_anims", "idle"); // animFileId explicite
AnimatedItemController.play("monmod:item/epee", "swing"); // animFileId = modelId
AnimatedItemController.stop("monmod:item/epee");
boolean playing = AnimatedItemController.isPlaying("monmod:item/epee");
animFileId suit le meme format que les blocs :
"modid:path" resout vers
assets/modid/animations/path.erianim.json.
Format du fichier .erianim.json
Le fichier .erianim.json est un fichier JSON qui decrit toutes les animations d'un block.
Un seul fichier peut contenir plusieurs animations (idle, open, close, process...).
Structure racine
{
"format_version": 1,
"model": "monmod:block/ma_machine",
"animations": {
"idle_breathe": { ... },
"open": { ... },
"close": { ... },
"process": { ... }
}
}
| Champ | Type | Description |
|---|---|---|
format_version |
int | Version du format. Actuellement 1. |
model |
string | Identifiant du modele Blockbench associe (ex: "monmod:block/machine"). Informatif. |
animations |
object | Un objet ou chaque cle est le nom de l'animation et la valeur est sa definition. |
Structure d'une animation
{
"duration": 40,
"endBehavior": "freeze",
"rollbackDuration": 10,
"blendIn": 5,
"blendOut": 0,
"loopCount": 3,
"loopPause": 20,
"tracks": { ... },
"events": [ ... ]
}
| Champ | Type | Defaut | Description |
|---|---|---|---|
duration |
int | 20 | Duree totale en ticks (20 ticks = 1 seconde). |
endBehavior |
string | "freeze" | Que faire quand l'animation se termine. Voir EndBehaviors. |
rollbackDuration |
int | 10 | Duree en ticks de l'animation de retour (pour endBehavior: "rollback"). |
blendIn |
int | 0 | Duree du fondu d'entree (en ticks) depuis la pose precedente. 0 = pas de fondu. |
blendOut |
int | 0 | Duree du fondu de sortie (en ticks). 0 = pas de fondu. |
loopCount |
int | 0 | Nombre de boucles pour endBehavior: "loop_count". |
loopPause |
int | 0 | Pause en ticks entre chaque boucle pour endBehavior: "loop_pause". |
tracks |
object | {} | Les pistes d'animation pour chaque groupe du modele. Cle = nom du groupe. |
elementTracks v1.5.1 |
object | {} | Pistes d'animation ciblant des elements individuels a l'interieur d'un groupe. Cle = "nomGroupe:indexElement" (ex : "body:2"). Meme structure qu'une track de groupe. Voir Element-level animation. |
events |
array | [] | Liste des evenements declenches a des ticks precis pendant l'animation. |
Types de tracks
Chaque groupe de ton modele Blockbench peut avoir une ou plusieurs tracks (pistes d'animation). Chaque track contient une liste de keyframes (images cles) — des points dans le temps ou tu definis une valeur. Le systeme interpole automatiquement entre les keyframes avec la fonction d'easing choisie.
Voici les 7 types de tracks disponibles :
Definit la rotation absolue du groupe en degres (0-359) sur chaque axe. C'est l'orientation fixe du groupe a un instant donne. Les valeurs sont interpolees entre les keyframes.
"rotation": [
{ "tick": 0, "value": [0, 0, 0], "easing": "linear" },
{ "tick": 20, "value": [-90, 0, 0], "easing": "ease_out_back" },
{ "tick": 40, "value": [-90, 45, 0], "easing": "ease_in_out" }
]
Rotation cumulative en degres depuis le debut de l'animation. Contrairement a rotation,
les valeurs ne sont pas limitees a 0-359 : tu peux aller au-dela de 360 pour faire plusieurs tours.
Par exemple, [0, 720, 0] = 2 tours complets sur l'axe Y.
S'ajoute par-dessus la rotation de pose (rotation).
"rotate": [
{ "tick": 0, "value": [0, 0, 0], "easing": "linear" },
{ "tick": 60, "value": [0, 720, 0], "easing": "ease_in_out" }
]
Deplace le groupe en pixels Blockbench (1 pixel Blockbench = 1/16 de block Minecraft).
[0, 16, 0] = deplacer de 1 block vers le haut.
[0, -4, 0] = deplacer de 0.25 blocks vers le bas.
"translation": [
{ "tick": 0, "value": [0, 0, 0], "easing": "linear" },
{ "tick": 10, "value": [0, -3, 0], "easing": "ease_out" },
{ "tick": 20, "value": [0, 0, 0], "easing": "ease_in" }
]
Change la taille du groupe sur chaque axe. [1, 1, 1] = taille normale.
[2, 2, 2] = double taille. [0, 0, 0] = invisible (ecrase a zero).
"scale": [
{ "tick": 0, "value": [1, 1, 1], "easing": "linear" },
{ "tick": 10, "value": [1.5, 1.5, 1.5], "easing": "ease_out_back" },
{ "tick": 20, "value": [1, 1, 1], "easing": "ease_in" }
]
Affiche ou masque un groupe a un tick precis. Pas d'interpolation — c'est un interrupteur on/off.
Le champ value est un booleen : true = visible, false = invisible.
"visible": [
{ "tick": 0, "value": true },
{ "tick": 8, "value": false }
]
Fait tourner le groupe en continu a une vitesse donnee (en degres par tick).
Contrairement a rotation qui definit un angle fixe, spin definit une vitesse de rotation
qui s'accumule frame apres frame. Ideal pour des engrenages, ventilateurs, etc.
Tu peux changer la vitesse de spin au cours de l'animation. L'easing controle la transition entre l'ancienne et la nouvelle vitesse.
"spin": [
{ "tick": 0, "axis": "y", "speed": 0, "easing": "linear" },
{ "tick": 10, "axis": "y", "speed": 18, "easing": "ease_in" },
{ "tick": 30, "axis": "y", "speed": 18, "easing": "linear" },
{ "tick": 40, "axis": "y", "speed": 0, "easing": "ease_out" }
]
| Champ | Type | Description |
|---|---|---|
tick |
int | Tick ou cette vitesse prend effet. |
axis |
string | Axe de rotation : "x", "y", ou "z". Defaut : "y". |
speed |
float | Vitesse de rotation en degres par tick. 18 = 1 tour en 20 ticks (1 seconde). 0 = arret. |
easing |
string | Fonction d'easing pour la transition vers cette vitesse. Defaut : "linear". |
Change la texture d'une face ou d'un element du modele a un tick precis. Pas d'interpolation — le changement est instantane. Les textures de remplacement doivent etre declarees dans la map de textures du modele Blockbench.
"texture": [
{ "tick": 0, "target": "screen", "value": "screen_off" },
{ "tick": 10, "target": "screen", "value": "screen_on" }
]
| Champ | Type | Description |
|---|---|---|
tick |
int | Tick ou le changement de texture s'applique. |
target |
string | Cle de texture a remplacer (nom dans la map textures du modele, ex: "screen"). |
value |
string | Cle de la texture de remplacement (ex: "screen_on"). |
Format des keyframes standard
Les tracks rotation, rotate, translation et scale utilisent
toutes le meme format de keyframe :
| Champ | Type | Description |
|---|---|---|
tick |
int | Position dans le temps (en ticks). 0 = debut de l'animation. |
value |
float[3] | Valeur [x, y, z]. Degres pour rotation/rotate, pixels BB pour translation, facteur pour scale. |
easing |
string | Fonction d'interpolation. Defaut : "linear". Voir Easings. |
Animation au niveau de l'element v1.5.1
Par defaut, une piste d'animation s'applique au groupe Blockbench entier : toute la geometrie
rattachee au groupe tourne / se deplace / s'echelle ensemble autour du pivot du groupe. Depuis la v1.5.1,
tu peux egalement animer des elements individuels a l'interieur d'un groupe. Chaque element
(cube Blockbench) possede son propre rotation.origin (pivot) defini dans le modele et devient
animable independamment.
Quand utiliser elementTracks ?
- Un bouton, une gachette, une piece mecanique a l'interieur d'un groupe qui doit bouger independamment des autres cubes du meme groupe.
- Plusieurs elements du meme groupe qui doivent tourner sur des pivots differents (ex : les pales d'une turbine qui partagent un groupe "turbine").
- Cas ou tu ne veux pas (ou ne peux pas) restructurer le modele Blockbench en sous-groupes.
Si chaque piece mobile merite deja son propre groupe dans Blockbench, utilise simplement tracks —
c'est plus lisible et plus economique a runtime.
Format JSON
Le champ elementTracks se place a cote de tracks dans la definition d'animation. Les cles
suivent le format "nomGroupe:indexElement", ou indexElement est la position
(base 0) de l'element dans le tableau elements de son groupe Blockbench. La valeur a la meme structure
qu'une track de groupe (rotation, rotate, translation, scale, visible, spin — voir Types de tracks).
{
"formatVersion": 1,
"modelId": "mymod:block/turret",
"animations": {
"idle": {
"duration": 40,
"endBehavior": "loop",
"tracks": {
"base": {
"rotation": [
{ "tick": 0, "value": [0, 0, 0] },
{ "tick": 40, "value": [0, 360, 0] }
]
}
},
"elementTracks": {
"body:2": {
"translation": [
{ "tick": 0, "value": [0, 0, 0] },
{ "tick": 20, "value": [0, 2, 0] },
{ "tick": 40, "value": [0, 0, 0] }
]
}
}
}
}
}
elementTracks est absent, le comportement est identique aux versions precedentes : seules les pistes de groupe s'appliquent et aucune matrice OpenGL supplementaire n'est poussee par element.
API Java
Tout l'ecosysteme d'animation (AnimationDef, AnimationPose, AnimationController,
AnimatedBlockTESR, AnimatedEntityRenderer) supporte les poses d'element. Tu peux lire une pose
d'element via AnimationPose.getElement(String groupName, int elementIndex).
// Tester la presence de pistes d'element avant d'iterer
if (animDef.hasElementTracks()) {
for (Map.Entry<String, GroupTrack> entry : animDef.getElementTracks().entrySet()) {
// entry.getKey() = "body:2"
// entry.getValue() = GroupTrack
}
}
// Acces direct via groupe + index
GroupTrack t = animDef.getElementTrack("body", 2);
// Cle canonique (utilitaire statique)
String key = AnimationDef.elementTrackKey("body", 2); // -> "body:2"
AnimationPose pose = controller.getCurrentPose(partialTicks);
// Pose du groupe
AnimationPose.GroupPose bodyPose = pose.getGroup("body");
// Pose de l'element (null si aucune piste n'existe pour cet element)
AnimationPose.GroupPose elemPose = pose.getElement("body", 2);
if (elemPose != null) {
if (!elemPose.visible) return; // element cache
float rotY = elemPose.rotation[1];
// ...
}
Rendu — ordre des transformations
Dans AnimatedBlockTESR et AnimatedEntityRenderer, pour chaque element anime, la matrice OpenGL
est empilee autour du rotationOrigin de l'element avec l'ordre suivant :
translate(rotationOrigin)- Rotation statique JSON de l'element (si
rotationest definie dans le modele Blockbench). - Translation animee (
elemPose.translation / 16, convertie depuis les pixels Blockbench). - Rotations animees (
rotationZ/Y/X puiscumulativeRotationY/X/Z — la part spin). - Echelle animee (
elemPose.scale). translate(-rotationOrigin).- Rendu des faces de l'element.
La rotation statique JSON de l'element est donc preservee — l'animation s'applique par-dessus, dans le repere local du pivot. Si l'element n'a ni pose animee ni rotation statique, aucune matrice supplementaire n'est empilee (optimisation : le chemin cout zero des versions precedentes).
Events dans .erianim.json
Les events sont des actions declenchees a des ticks precis pendant l'animation. Ils sont listes dans le tableau "events"
de chaque animation. Chaque event a un tick et un type.
Les 6 types d'events
| Type | Description | Champs supplementaires |
|---|---|---|
sound |
Joue un son Minecraft a la position du block. Le son est audible par tous les joueurs a proximite. | value (ResourceLocation du son), volume (defaut: 1.0), pitch (defaut: 1.0) |
particle |
Declenche le callback onAnimationEvent() dans le TileEntity. Tu geres le spawn de particules dans ton code Java. |
value (nom de la particule), count (defaut: 1), spread (defaut: 0.5) |
callback |
Appelle onAnimationCallback(value) dans le TileEntity. C'est le moyen principal de synchroniser la logique serveur avec l'animation. |
value (nom du callback) |
hide_group |
Masque un groupe du modele. L'event est dispatche cote serveur pour information. Le rendu utilise le track visible cote client. |
value (nom du groupe) |
show_group |
Affiche un groupe du modele. Meme comportement que hide_group mais en sens inverse. |
value (nom du groupe) |
cut |
Arrete immediatement l'animation et remet le block a l'etat IDLE. Equivalent a appeler resetAnimationInstant(). |
value (ignore) |
"events": [
{
"tick": 0,
"type": "sound",
"value": "minecraft:block.anvil.use",
"volume": 0.8,
"pitch": 1.5
},
{
"tick": 10,
"type": "particle",
"value": "smoke",
"count": 5,
"spread": 0.3
},
{
"tick": 20,
"type": "callback",
"value": "halfway_done"
},
{
"tick": 5,
"type": "hide_group",
"value": "lock"
},
{
"tick": 30,
"type": "show_group",
"value": "reward"
},
{
"tick": 40,
"type": "cut"
}
]
EndBehaviors
Le EndBehavior determine ce qui se passe quand une animation atteint son dernier tick.
Il se definit dans le fichier .erianim.json (champ "endBehavior") ou peut etre
surcharge en Java via te.playAnimation("open", EndBehavior.LOOP).
| Valeur | JSON | Description | Cas d'usage |
|---|---|---|---|
FREEZE |
"freeze" |
Reste fige sur la derniere frame indefiniment. | Porte ouverte, coffre ouvert, levier tire. |
ROLLBACK |
"rollback" |
Rejoue l'animation en sens inverse sur rollbackDuration ticks pour revenir a la frame 0. |
Bouton qui se releve, machine qui s'eteint progressivement. |
ROLLBACK_INSTANT |
"rollback_instant" |
Revient instantanement a la frame 0 sans animation de retour. | Animation de flash, impact visuel rapide. |
WAIT_SERVER |
"wait_server" |
Reste fige sur la derniere frame et attend un appel serveur (te.resetAnimation()) pour revenir. |
Machine qui traite, coffre en attente de confirmation. Le serveur decide quand remettre a zero. |
LOOP |
"loop" |
Boucle infinie. L'animation recommence automatiquement depuis le debut a chaque fin. | Engrenage qui tourne, flamme qui pulse, idle en boucle. |
LOOP_COUNT |
"loop_count" |
Boucle N fois (configure par loopCount) puis se fige sur la derniere frame. |
Animation de charge (3 clignotements), signal lumineux repete. |
LOOP_PAUSE |
"loop_pause" |
Boucle infinie avec une pause de loopPause ticks entre chaque cycle. |
Machine qui traite par lots (cycle, pause, cycle, pause...). |
AnimatedBlockTileEntity te = (AnimatedBlockTileEntity) world.getTileEntity(pos);
// Utiliser le EndBehavior par defaut du fichier .erianim.json
te.playAnimation("process");
// Forcer un EndBehavior different
te.playAnimation("process", EndBehavior.LOOP);
te.playAnimation("open", EndBehavior.WAIT_SERVER);
te.playAnimation("flash", EndBehavior.ROLLBACK_INSTANT);
// Passer null = utiliser le defaut du fichier
te.playAnimation("idle", null);
Easings
Les fonctions d'easing controlent comment une valeur passe d'un keyframe a l'autre.
Au lieu d'un mouvement lineaire (constant), tu peux avoir une acceleration, une deceleration, un rebond, etc.
L'easing est defini dans chaque keyframe par le champ "easing".
Chaque fonction prend un temps normalise t entre 0 et 1 et retourne une valeur interpolee.
Certaines fonctions (ELASTIC, BOUNCE, BACK, SPRING) peuvent retourner des valeurs en dehors de 0-1, ce qui donne des effets
de depassement.
| Nom JSON | Enum Java | Description | Usage typique |
|---|---|---|---|
"linear" |
LINEAR |
Mouvement constant, sans acceleration. f(t) = t |
Rotations mecaniques, deplacements de machines. |
"ease_in" |
EASE_IN |
Demarre lentement, accelere a la fin. f(t) = t^2 |
Objet qui commence a bouger, vehicule qui demarre. |
"ease_out" |
EASE_OUT |
Demarre vite, ralentit a la fin. f(t) = t(2-t) |
Porte qui se ferme, objet qui se pose. |
"ease_in_out" |
EASE_IN_OUT |
Lent au debut et a la fin, rapide au milieu. | Mouvements naturels, couvercles, leviers. |
"ease_in_cubic" |
EASE_IN_CUBIC |
Comme ease_in mais plus accentue. f(t) = t^3 |
Chutes, accelerations fortes. |
"ease_out_cubic" |
EASE_OUT_CUBIC |
Comme ease_out mais plus accentue. Freinage plus rapide. | Objet lourd qui s'arrete, rebond amorti. |
"bounce" |
BOUNCE |
Rebond a l'arrivee, comme une balle qui touche le sol. | Objet qui tombe et rebondit, bouton qui clique. |
"elastic" |
ELASTIC |
Oscillation elastique a l'arrivee, comme un ressort. Depasse la cible puis revient. | Pop-up, element qui apparait avec du punch. |
"ease_in_back" |
EASE_IN_BACK |
Recule un peu avant d'avancer (overshoot a l'entree). | Preparation de frappe, elan avant un saut. |
"ease_out_back" |
EASE_OUT_BACK |
Depasse la cible puis revient (overshoot a la sortie). Effet "pop" tres satisfaisant. | Couvercle de coffre qui s'ouvre avec du punch, item qui grossit. |
"ease_in_expo" |
EASE_IN_EXPO |
Demarrage exponentiel — tres lent puis explosion. f(t) = 2^(10(t-1)) |
Charge d'energie, montee en puissance. |
"ease_out_expo" |
EASE_OUT_EXPO |
Deceleration exponentielle — freine bien plus fort que cubic. f(t) = 1 - 2^(-10t) |
Freinage d'urgence, arret brutal. |
"spring" |
SPRING |
Simulation de ressort physique — oscillations amorties. f(t) = 1 - cos(t*4.5*PI) * e^(-6t) |
Antennes, elements flexibles, feedback de frappe. |
Dans le fichier .erianim.json, l'easing s'ecrit en minuscules avec des underscores ou des tirets.
Les deux notations sont acceptees : "ease_out_back" et "ease-out-back" sont equivalentes.
Les majuscules sont aussi acceptees. Si le nom n'est pas reconnu, LINEAR est utilise par defaut.
AnimStates
L'etat d'animation du TileEntity est accessible via te.getAnimState().
Il te permet de savoir exactement ce que fait le block a un instant donne.
| Etat | Description | Tick serveur ? |
|---|---|---|
IDLE |
Aucune animation en cours. Le block est au repos. Le TESR affiche la pose par defaut du modele. | Non |
PLAYING |
L'animation est en cours de lecture. Les events sont declenches et les tracks sont interpoles. | Oui |
PAUSED |
L'animation est en pause a un tick precis. Le block est fige a la position ou il a ete mis en pause. | Non |
FROZEN |
L'animation est terminee et figee sur la derniere frame (endBehavior = FREEZE ou LOOP_COUNT termine). |
Non |
ROLLING_BACK |
L'animation est en train de jouer en sens inverse, revenant a la frame 0 (endBehavior = ROLLBACK). |
Oui |
WAITING_SERVER |
L'animation est terminee et attend un signal du serveur (te.resetAnimation()) pour revenir a IDLE. |
Non |
AnimatedBlockTileEntity te = (AnimatedBlockTileEntity) world.getTileEntity(pos);
// Verifier si une animation est en cours
if (te.isAnimating()) {
// PLAYING ou ROLLING_BACK
}
// Verifier un etat precis
if (te.getAnimState() == AnimState.WAITING_SERVER) {
// L'animation est terminee, le serveur doit agir
te.resetAnimation();
}
// Verifier la progression
float progress = te.getAnimationProgress(); // 0.0 a 1.0
if (progress > 0.5f) {
// L'animation a depasse la moitie
}
Items animes v1.3.1
Depuis la version 1.3.1, il est possible d'utiliser la methode .animatedModel() sur un
EriItem pour rendre l'item avec un modele Blockbench 3D au lieu de la
texture 2D standard. L'item sera affiche en 3D dans la main du joueur et dans l'inventaire.
Le rendu est delegue a un TileEntityItemStackRenderer interne
(AnimatedBlockItemRenderer) qui utilise le meme pipeline de rendu que
AnimatedBlockTESR — groupes, elements, faces, UV, textures.
Utilisation
Il suffit d'appeler .animatedModel("modid:item/nom") dans la chaine du builder.
Le modele JSON Blockbench doit se trouver dans assets/{modid}/models/item/{nom}.json.
EriItem.create("monmod", "epee_fancy")
.maxStackSize(1)
.rarity(EnumRarity.EPIC)
.animatedModel("monmod:item/epee_fancy") // Modele Blockbench 3D
.register();
Les textures de l'item sont definies directement dans le fichier JSON du modele Blockbench, pas dans le code Java. Le renderer lit automatiquement les textures referees par le modele.
Le modele JSON Blockbench doit etre place dans assets/{modid}/models/item/{nom}.json.
L'identifiant passe a animatedModel() utilise le format "modid:item/nom".
Entites animees v1.3.0
Le systeme d'entites animees permet de creer des entites Minecraft custom rendues avec des
modeles Blockbench 3D et des animations .erianim.json. Il repose sur trois
composants : le builder EriEntity, le renderer AnimatedEntityRenderer, et
l'interface IAnimatedEntity.
EriEntity — Builder fluent
EriEntity est le builder pour definir une entite custom. Il stocke la configuration
(modele, textures, animations, hitbox, sieges) dans un EntityDefinition enregistre dans
le ContentRegistry.
Contrairement a EriItem et EriBlock, EriEntity
ne gere pas l'enregistrement automatique dans Forge. Le mod doit enregistrer
les EntityEntry lui-meme dans RegistryEvent.Register<EntityEntry>
en utilisant ContentRegistry.getEntityDefs() pour recuperer les definitions.
Methodes du builder
| Methode | Description |
|---|---|
create(String modId, String registryName) | Cree un nouveau builder. Methode statique. |
model(String modelId) | Definit le modele Blockbench (ex: "monmod:entity/monstre"). |
texture(String key, String path) | Associe une cle de texture du modele a un chemin de ResourceLocation. |
animation(String name, String animId) | Associe un nom d'animation (ex: "idle", "walk") a un identifiant .erianim. |
javaAnimation(String name, EriAnimJava anim) v1.6.5 | Enregistre une animation Java directement sur le builder. Stockee dans EntityDefinition.javaAnimations, recuperable via ContentRegistry.getEntityDef(modId, name).getJavaAnimation("idle"). Coexiste avec .animation(). |
hitbox(float width, float height) | Definit les dimensions de la hitbox. |
seat(float x, float y, float z) | Ajoute un siege passager simple (coordonnees Blockbench pixels). |
seat(float x, float y, float z, String attachedGroup) | Ajoute un siege attache a un groupe du modele. |
seat(float x, float y, float z, float yawOffset, String attachedGroup, boolean lockCamera) | Siege complet : rotation yaw, groupe attache, verrouillage camera. |
spawnEgg(int primary, int secondary) | Active un spawn egg (couleurs 0xRRGGBB). |
creativeTab(String tabName) | Onglet creatif pour le spawn egg. |
register() | Enregistre la definition dans le ContentRegistry. |
IAnimatedEntity — Interface entite
L'entite Java doit implementer l'interface IAnimatedEntity pour fournir les poses
d'animation au renderer. Deux methodes a implementer :
| Methode | Description |
|---|---|
AnimationPose getCurrentPose(float partialTicks) | Retourne la pose interpolee courante, ou null si aucune animation n'est active (IDLE). |
AnimState getAnimState() | Retourne l'etat d'animation courant de l'entite. |
AnimatedEntityRenderer — Renderer
Le renderer d'entite qui affiche le modele Blockbench avec animations. Le pipeline de rendu
est identique a AnimatedBlockTESR. S'enregistre via
RenderingRegistry.registerEntityRenderingHandler().
| Methode statique | Description |
|---|---|
factory(String modelId) | Cree une IRenderFactory pour le modele donne (ombre par defaut: 0.5). |
factory(String modelId, float shadowSize) | Factory avec taille d'ombre personnalisee. |
EntitySeat — Systeme de sieges
Chaque entite peut avoir un ou plusieurs sieges pour les passagers. Les coordonnees sont en pixels Blockbench (meme espace que le modele).
| Champ | Type | Description |
|---|---|---|
x, y, z | float | Position du siege en pixels Blockbench (local a l'entite). |
yawOffset | float | Rotation yaw du passager en degres (defaut: 0). |
attachedGroup | String | Nom du groupe du modele auquel le siege est attache (peut etre null). Le siege suit les animations du groupe. |
lockCamera | boolean | Si true, la camera du joueur assis est verrouillee (defaut: false). |
Exemple complet
// 1. Definir l'entite via EriEntity builder (dans preInit)
EriEntity.create("monmod", "monstre")
.model("monmod:entity/monstre")
.texture("body", "monmod:textures/entity/monstre/body")
.animation("idle", "monmod:entity/monstre_idle")
.animation("walk", "monmod:entity/monstre_walk")
.hitbox(0.8f, 1.8f)
.seat(0.0f, 24.0f, 0.0f)
.register();
// 2. Classe d'entite — gestion de l'etat d'animation via DataParameters
public class EntityMonstre extends EntityLiving implements IAnimatedEntity {
// DataParameters pour sync serveur -> client
private static final DataParameter<String> ANIM_NAME =
EntityDataManager.createKey(EntityMonstre.class, DataSerializers.STRING);
private static final DataParameter<Integer> ANIM_TICK =
EntityDataManager.createKey(EntityMonstre.class, DataSerializers.VARINT);
private final AnimationController controller = new AnimationController();
public EntityMonstre(World world) {
super(world);
setSize(0.8f, 1.8f);
}
@Override
protected void entityInit() {
super.entityInit();
dataManager.register(ANIM_NAME, "");
dataManager.register(ANIM_TICK, 0);
}
// Appeler cote serveur pour lancer une animation
public void playAnimation(String name) {
dataManager.set(ANIM_NAME, name);
dataManager.set(ANIM_TICK, 0);
}
public void stopAnimation() {
dataManager.set(ANIM_NAME, "");
}
@Override
public void onUpdate() {
super.onUpdate();
if (!world.isRemote) {
String name = dataManager.get(ANIM_NAME);
if (!name.isEmpty()) {
dataManager.set(ANIM_TICK, dataManager.get(ANIM_TICK) + 1);
}
}
}
// --- IAnimatedEntity ---
@Override
public AnimationPose getCurrentPose(float partialTicks) {
String animName = dataManager.get(ANIM_NAME);
if (animName.isEmpty()) return null;
// Charger le fichier d'animation depuis le cache
AnimationFile animFile = EriAnimCache.get("monmod:entity/monstre_" + animName);
if (animFile == null) return null;
AnimationDef animDef = animFile.getAnimations().values().iterator().next();
if (animDef == null) return null;
float tickFloat = dataManager.get(ANIM_TICK) + partialTicks;
return controller.computePose(
animDef,
AnimState.PLAYING,
tickFloat,
animDef.getDuration(),
10, // rollbackDuration (non utilise ici)
null, // blendPose
1.0f // blendProgress
);
}
@Override
public AnimState getAnimState() {
return dataManager.get(ANIM_NAME).isEmpty() ? AnimState.IDLE : AnimState.PLAYING;
}
}
// 3. Enregistrer le renderer (ClientProxy.preInit)
RenderingRegistry.registerEntityRenderingHandler(
EntityMonstre.class,
AnimatedEntityRenderer.factory("monmod:entity/monstre")
);
// 4. Enregistrer dans Forge (RegistryEvent.Register<EntityEntry>)
for (EntityDefinition def : ContentRegistry.getEntityDefs()) {
// Enregistrer EntityEntry avec EntityRegistry.registerModEntity(...)
// Associer AnimatedEntityRenderer via RenderingRegistry (client only)
}
Animations Java pour les entites v1.6.4
Les entites animees supportent aussi le systeme EriAnimJava. Depuis la
v1.6.5, les animations Java peuvent etre declarees directement sur le builder via
.javaAnimation(), exactement comme .animation() pour les
fichiers .erianim. Les deux approches coexistent et peuvent etre mixees
sur la meme entite.
// Approche .erianim — necessite des fichiers .erianim.json dans assets/
EriEntity.create("monmod", "monstre")
.model("monmod:entity/monstre")
.texture("body", "monmod:textures/entity/monstre")
.hitbox(0.8f, 1.8f)
.animation("idle", "monmod:entity/monstre_idle") // ← fichier .erianim
.animation("walk", "monmod:entity/monstre_walk") // ← fichier .erianim
.register();
// Anims Java declarees directement sur le builder (depuis v1.6.5)
EriEntity.create("monmod", "monstre")
.model("monmod:entity/monstre")
.texture("body", "monmod:textures/entity/monstre")
.hitbox(0.8f, 1.8f)
.javaAnimation("idle", new IdleAnim())
.javaAnimation("walk", new WalkAnim())
.register();
// Mix : .erianim pour les anims longues/complexes + Java pour les effets proceduraux
EriEntity.create("monmod", "monstre")
.model("monmod:entity/monstre")
.animation("walk", "monmod:entity/monstre_walk") // .erianim
.javaAnimation("headBob", new HeadBobAnim()) // Java
.register();
import fr.eri.eriapi.anim.AnimationController;
import fr.eri.eriapi.anim.AnimationPose;
import fr.eri.eriapi.anim.AnimState;
import fr.eri.eriapi.anim.EndBehavior;
import fr.eri.eriapi.anim.EriAnimJava;
import fr.eri.eriapi.anim.IAnimatedEntity;
import fr.eri.eriapi.content.ContentRegistry;
import fr.eri.eriapi.content.EntityDefinition;
public class EntityMonstre extends EntityLiving implements IAnimatedEntity {
private final AnimationController controller = new AnimationController();
private EriAnimJava currentAnim;
private float animTick = 0;
public EntityMonstre(World world) {
super(world);
setSize(0.8f, 1.8f);
// Anims recuperees depuis l'EntityDefinition cree par EriEntity
EntityDefinition def = ContentRegistry.getEntityDef("monmod", "monstre");
this.currentAnim = def != null ? def.getJavaAnimation("idle") : null;
}
@Override
public void onUpdate() {
super.onUpdate();
if (currentAnim != null) {
animTick += 1.0f;
float dur = currentAnim.duration();
if (currentAnim.endBehavior() == EndBehavior.LOOP && animTick >= dur) {
animTick -= dur; // boucle
}
}
}
/** Change d'animation par nom (resolution depuis l'EntityDefinition). */
public void setAnimation(String name) {
EntityDefinition def = ContentRegistry.getEntityDef("monmod", "monstre");
EriAnimJava anim = def != null ? def.getJavaAnimation(name) : null;
if (anim != null) {
this.currentAnim = anim;
this.animTick = 0;
}
}
// --- IAnimatedEntity ---
@Override
public AnimationPose getCurrentPose(float partialTicks) {
if (currentAnim == null) return null;
long worldTick = world != null ? world.getTotalWorldTime() : 0;
return controller.computeJavaPose(
currentAnim, // EriAnimJava courante
AnimState.PLAYING,
animTick + partialTicks, // tickFloat interpole
partialTicks,
worldTick,
this, // target accessible via ctx.asEntity()
null, // blendPose
1.0f // blendProgress
);
}
@Override
public AnimState getAnimState() {
return currentAnim != null ? AnimState.PLAYING : AnimState.IDLE;
}
}
.javaAnimation() et non .animation() ?
.animation() mappe un nom vers un fichier .erianim (resource pack,
chargement disque). .javaAnimation() stocke directement une instance
EriAnimJava dans la EntityDefinition, accessible via
ContentRegistry.getEntityDef(modId, name).getJavaAnimation("idle") —
plus besoin d'instancier les anims dans la classe d'entite.
currentAnim et
animTick ne sont pas synchronises serveur→client. Pour une entite visible
par d'autres joueurs, tu dois exposer un identifiant d'animation (par exemple un
int) via un DataParameter et reconstruire currentAnim
cote client en consequence. Voir le pattern DataParameters dans l'exemple
complet ci-dessus.
FREEZE, WAIT_SERVER, LOOP, LOOP_COUNT
et LOOP_PAUSE. ROLLBACK est traite comme un reset instantane.
L'exemple ci-dessus gere la boucle manuellement dans onUpdate() pour pouvoir
utiliser le tick local sans synchronisation reseau.
Contrairement aux blocs animes qui disposent d'un AnimatedBlockTileEntity cote
serveur, les entites gerent leur etat d'animation manuellement via des
DataParameters Forge. Seuls le nom de l'animation et le tick courant sont
synchronises serveur→client. Le calcul de la pose (AnimationController)
est purement client-side, execute chaque frame.
Utilisez ContentRegistry.getEntityDefs() dans votre handler
RegistryEvent.Register<EntityEntry> pour recuperer toutes les definitions
d'entites enregistrees via EriEntity. Chaque EntityDefinition contient
le modId, le registryName, le modelId, les textures, animations, hitbox et sieges.
Events Forge
Le module d'animation publie 4 events Forge sur le MinecraftForge.EVENT_BUS.
Ils sont tous annulables (@Cancelable) et fires cote serveur uniquement.
Tu peux les ecouter avec le systeme d'events EriAPI ou avec @SubscribeEvent.
Fire avant qu'une animation commence. Si annule, l'animation n'est pas jouee.
-
GetterWorld getWorld()Le monde ou se trouve le block.
-
GetterBlockPos getPos()La position du block.
-
GetterAnimatedBlockTileEntity getTileEntity()Le TileEntity du block anime.
-
GetterString getAnimName()Le nom de l'animation qui va etre jouee.
-
GetterEndBehavior getEndBehavior()Le EndBehavior effectif (override ou defaut du fichier).
Fire quand une animation atteint son dernier tick, avant que le EndBehavior ne soit applique. Si annule, l'animation est figee sur la derniere frame (FROZEN).
-
GetterWorld getWorld()Le monde ou se trouve le block.
-
GetterBlockPos getPos()La position du block.
-
GetterAnimatedBlockTileEntity getTileEntity()Le TileEntity du block anime.
-
GetterString getAnimName()Le nom de l'animation qui se termine.
-
GetterEndBehavior getEndBehavior()Le EndBehavior qui va etre applique.
Fire avant qu'une animation soit reinitalisee. Si annule, le reset n'est pas applique.
-
GetterWorld getWorld()Le monde ou se trouve le block.
-
GetterBlockPos getPos()La position du block.
-
GetterAnimatedBlockTileEntity getTileEntity()Le TileEntity du block anime.
-
GetterString getPreviousAnim()Le nom de l'animation qui etait en cours avant le reset.
-
Getterboolean isInstant()
truesi le reset est instantane,falsesi un rollback est utilise.
Fire quand une animation en boucle est sur le point de recommencer (LOOP, LOOP_COUNT, LOOP_PAUSE). Si annule, l'animation arrete de boucler et se fige (FROZEN).
-
GetterWorld getWorld()Le monde ou se trouve le block.
-
GetterBlockPos getPos()La position du block.
-
GetterAnimatedBlockTileEntity getTileEntity()Le TileEntity du block anime.
-
GetterString getAnimName()Le nom de l'animation qui boucle.
-
Getterint getCurrentLoop()Numero de l'iteration actuelle (commence a 0). La premiere fois que l'animation se termine = 0.
-
Getterint getMaxLoops()Nombre maximum de boucles pour
LOOP_COUNT, ou-1pour les boucles infinies (LOOPetLOOP_PAUSE).
Exemples de listeners
import fr.eri.eriapi.anim.event.*;
import fr.eri.eriapi.event.EriEvents;
// Empecher certaines animations de se jouer
EriEvents.on(AnimationStartEvent.class, e -> {
if ("forbidden_anim".equals(e.getAnimName())) {
e.setCanceled(true); // L'animation ne se jouera pas
}
});
// Reagir quand une animation se termine
EriEvents.on(AnimationEndEvent.class, e -> {
if ("charge".equals(e.getAnimName())) {
// Donner un buff au joueur le plus proche
giveBuffToNearestPlayer(e.getWorld(), e.getPos());
}
});
// Arreter une boucle apres une condition
EriEvents.on(AnimationLoopEvent.class, e -> {
if ("production".equals(e.getAnimName())) {
TileMachine te = (TileMachine) e.getTileEntity();
if (!te.hasFuel()) {
e.setCanceled(true); // Arrete la boucle, freeze le block
}
}
});
// Empecher un reset
EriEvents.on(AnimationResetEvent.class, e -> {
if (isBlockLocked(e.getPos())) {
e.setCanceled(true); // Le reset ne s'applique pas
}
});
Textures dynamiques
Tu peux changer les textures d'un block anime en temps reel, de deux facons :
- Via le fichier .erianim.json — avec le track
texture, les textures changent automatiquement pendant l'animation. - Via l'API Java — avec
te.setTexture(), tu changes les textures depuis le code serveur a tout moment.
Priorite des textures
Quand le TESR cherche quelle texture afficher, il regarde dans cet ordre :
- Override d'animation (track texture du .erianim.json) — priorite la plus haute.
- Override du TileEntity (
te.setTexture()) — priorite moyenne. - Texture originale du modele — priorite la plus basse (defaut).
AnimatedBlockTileEntity te = (AnimatedBlockTileEntity) world.getTileEntity(pos);
// Changer une texture specifique
te.setTexture("screen", "screen_active");
// La texture "screen" du modele sera remplacee par "screen_active"
// Le changement est synchronise automatiquement a tous les clients
// Remettre la texture originale
te.clearTexture("screen");
// Remettre TOUTES les textures originales
te.clearAllTextures();
// Verifier les overrides actuels
Map<String, String> overrides = te.getTextureOverrides();
// overrides = {"screen" -> "screen_active", ...}
public class TileReactor extends AnimatedBlockTileEntity {
public TileReactor() {
super("monmod:block/reactor", "monmod:block/reactor");
}
@Override
protected void onAnimationCallback(String callbackName) {
switch (callbackName) {
case "activate_core":
// L'animation atteint le moment ou le coeur doit s'allumer
setTexture("core", "core_glowing");
setTexture("panel", "panel_active");
break;
case "deactivate_core":
clearAllTextures();
break;
}
}
}
Commandes debug
EriAPI enregistre automatiquement la commande /erianim pour tester et deboguer les animations en jeu.
Toutes les sous-commandes requierent le niveau OP 2, sauf reload qui requiert le niveau OP 3.
| Commande | Description |
|---|---|
/erianim play <x> <y> <z> <anim> [endBehavior] |
Joue une animation sur le block anime a la position donnee. Le EndBehavior est optionnel (utilise celui du fichier .erianim par defaut). |
/erianim reset <x> <y> <z> [true|false] |
Reinitialise l'animation. true = instantane, false (defaut) = utilise le rollback si applicable. |
/erianim stop <x> <y> <z> |
Arrete immediatement l'animation (pas de rollback, pas de callback). |
/erianim list <x> <y> <z> |
Liste toutes les animations disponibles dans le fichier .erianim du block, avec leur duree, EndBehavior, nombre de tracks et d'events. |
/erianim state <x> <y> <z> |
Affiche l'etat complet du block : modele, fichier anim, etat, animation en cours, progression, overrides de textures. |
/erianim perf |
Active le tracking de performance et affiche les stats : nombre de blocks rendus, temps de rendu moyen, taille des caches. Lancer une deuxieme fois pour voir les donnees. |
/erianim reload |
(OP 3) Vide les caches de modeles et d'animations. Les fichiers seront recharges au prochain rendu. Ideal pour tester des modifications sans redemarrer le serveur. |
Structure des fichiers
Voici ou placer chaque fichier dans ton mod pour que le systeme d'animation fonctionne correctement.
src/main/resources/assets/monmod/
models/
block/
ma_machine.json ← Modele Blockbench (export "Java Block/Item")
animations/
block/
ma_machine.erianim.json ← Fichier d'animation EriAPI
textures/
blocks/
ma_machine/ ← Dossier de textures du modele
base.png ← Texture principale
panel.png ← Texture du panneau
screen_off.png ← Ecran eteint
screen_on.png ← Ecran allume (pour texture swap)
blockstates/
ma_machine.json ← Blockstate (genere automatiquement par EriAnimBlock)
Le modelId "monmod:block/ma_machine" se resout en
assets/monmod/models/block/ma_machine.json.
Le animFileId "monmod:block/ma_machine" se resout en
assets/monmod/animations/block/ma_machine.erianim.json.
Les textures sont resolues via la map de textures dans le modele Blockbench (section "textures" du JSON).
Performance
Le systeme d'animation est concu pour supporter 500 a 1000 joueurs simultanes. Voici les optimisations integrees et les bonnes pratiques pour garder de bonnes performances.
Optimisations integrees
- Fast exit dans update() — La methode
ITickable.update()retourne immediatement pour les etats IDLE, FROZEN, PAUSED et WAITING_SERVER. Seuls les blocks en etat PLAYING ou ROLLING_BACK consomment du temps CPU serveur. - Cache de rendu IDLE — Quand le block est IDLE et que rien n'a change, le TESR saute le calcul de pose et reutilise le dernier rendu. Un flag
idleRenderDirtycontrole la re-evaluation. - Cache de modeles et d'animations — Les modeles Blockbench et les fichiers .erianim sont parses une seule fois puis mis en cache (
AnimModelCacheetEriAnimCache). Le reload est disponible via/erianim reload. - Events tries — Les events d'animation sont tries par tick, ce qui permet un dispatch en O(n) avec early exit quand le tick depasse le tick courant.
Bonnes pratiques
- Reduire la renderDistance — Pour les blocks decore ou non-critiques, utilise une renderDistance de 32 ou 48 au lieu de 64.
- Eviter les boucles infinies inutiles — Un block en
LOOPconsomme du CPU serveur a chaque tick pour dispatcher les events. Si tu n'as pas d'events, prefere une animation IDLE sans events et avec un track de spin. - Utiliser WAIT_SERVER pour les processus longs — Au lieu de boucler, joue une animation une fois puis attend le signal serveur. Cela evite le ticking continu.
- Peu de groupes = moins de calculs — Chaque groupe anime requiert des calculs de transformation. Fusionne les groupes qui bougent ensemble dans Blockbench.
- Monitoring avec /erianim perf — Utilise la commande en jeu pour verifier le temps de rendu et identifier les blocks problematiques.
Editeur EriAnim
L'editeur EriAnim est un outil de bureau (application Electron) qui permet de creer
et editer visuellement les fichiers .erianim.json sans ecrire le JSON a la main.
Fonctionnalites
- Timeline visuelle — Affiche les tracks et keyframes sur une timeline interactive. Glisse-depose pour deplacer les keyframes.
- Preview 3D — Previsualise l'animation en temps reel avec le modele Blockbench charge dans l'editeur.
- Editeur de keyframes — Formulaire pour editer les valeurs, l'easing, et le tick de chaque keyframe.
- Export direct — Genere le fichier
.erianim.jsonpret a etre integre dans ton mod.
L'editeur EriAnim est un outil interne a EriniumGroup. Si tu travailles sur un mod qui utilise EriAPI,
contacte l'equipe pour obtenir l'acces. En attendant, tu peux toujours creer les fichiers .erianim.json
a la main en suivant la documentation de cette page.