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.

fr.eri.eriapi.anim Forge 1.12.2 EriAPI 1.2.0+

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 :

  1. Modelisation — Creer le modele 3D dans Blockbench. Organiser les cubes en groupes (chaque groupe pourra etre anime independamment).
  2. Export — Exporter le modele au format "Java Block/Item" (.json).
  3. Animation — Creer le fichier .erianim.json qui decrit les animations (manuellement ou avec l'editeur EriAnim).
  4. Integration — Enregistrer le block dans le code Java avec EriAnimBlock.create().
  5. Runtime — Le TESR lit le modele et l'animation, et dessine le block anime a l'ecran.
Tout est automatique

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 :

JSON — ma_lootbox.erianim.json
{
  "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

Structure des fichiers
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

Java — Enregistrer le block anime
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.

Rappel : ContentRegistry

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.

EriAnimBlock
fr.eri.eriapi.anim.EriAnimBlock
Builder

Builder pour blocks animes avec modeles Blockbench 3D. Configure automatiquement TileEntity + TESR + blockstate + item renderer.

  • static EriAnimBlock create(String modId, String registryName)
    Cree un nouveau builder. Le modId est l'identifiant de ton mod (ex: "monmod"). Le registryName est le nom interne du block (ex: "lootbox").
    Statique
  • EriAnimBlock 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.
    Setter
  • EriAnimBlock hardness(float hardness)
    Definit la durete du block (temps pour le casser). Exemples : pierre = 1.5, fer = 5.0, obsidienne = 50.0.
    Setter
  • EriAnimBlock resistance(float resistance)
    Definit la resistance aux explosions. Plus c'est haut, plus le block resiste au TNT et aux creepers.
    Setter
  • EriAnimBlock 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.
    Setter
  • EriAnimBlock soundType(SoundType sound)
    Definit le type de son du block (marcher dessus, le casser, le poser). Exemples : SoundType.METAL, SoundType.WOOD, SoundType.STONE.
    Setter
  • EriAnimBlock 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.
    Setter
  • EriAnimBlock creativeTab(CreativeTabs tab)
    Definit l'onglet du menu creatif ou le block apparait.
    Setter
  • EriAnimBlock drops(Item item, int min, int max)
    Definit ce que le block lache quand il est casse. min et max definissent la plage aleatoire de quantite.
    Setter
  • EriAnimBlock silkTouchable(boolean silk)
    Si true, le block peut etre recupere avec Silk Touch (toucher de soie).
    Setter
  • EriAnimBlock model(String modelId)
    Obligatoire. Definit le modele Blockbench a utiliser. Format : "modid:path/model" (sans l'extension .json). Le fichier doit etre a assets/modid/models/path/model.json.
    Exemple : "monmod:block/lootbox"assets/monmod/models/block/lootbox.json
    Setter
  • EriAnimBlock animation(String animFileId)
    Obligatoire. Definit le fichier d'animation .erianim.json. Format : "modid:path/anim" (sans l'extension .erianim.json). Le fichier doit etre a assets/modid/animations/path/anim.erianim.json.
    Exemple : "monmod:animations/lootbox"assets/monmod/animations/animations/lootbox.erianim.json
    Setter
  • EriAnimBlock renderDistance(double distance)
    Definit la distance maximale (en blocks) a laquelle le modele 3D est rendu. Par defaut : 64 blocks. Au-dela, le block est invisible.
    Setter
  • EriAnimBlock 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.
    Setter
  • EriAnimBlock tileEntity(Class<?> tileClass)
    Remplace le TileEntity automatique par une classe personnalisee. La classe doit etendre AnimatedBlockTileEntity et avoir un constructeur sans argument. Utilise ceci quand tu as besoin de callbacks personnalises (voir section Callbacks).
    Setter
  • EriAnimBlock onRightClick(Consumer<BlockActionContext> handler)
    Code execute quand un joueur fait clic droit sur le block. Le BlockActionContext contient world, pos, player, hand, facing.
    Callback
  • EriAnimBlock onBreak(Consumer<BlockActionContext> handler)
    Code execute quand le block est casse par un joueur.
    Callback
  • EriAnimBlock onPlace(Consumer<BlockActionContext> handler)
    Code execute quand le block est pose par un joueur.
    Callback
  • EriAnimBlock javaAnimation(String name, EriAnimJava anim) v1.6.5
    Enregistre une animation Java directement sur le builder — plus besoin de creer une sous-classe de AnimatedBlockTileEntity juste pour appeler addJavaAnimation(). L'instance est enregistree automatiquement sur le TileEntity (server + client) lors de sa creation. Coexiste avec .animation() pour les fichiers .erianim. Appelable plusieurs fois.
    Setter
  • EriAnimBlock register()
    Enregistre le block. Configure automatiquement : TileEntity, TESR, type de rendu ENTITYBLOCK_ANIMATED, item renderer, et opaque/fullCube = false. Si aucun TileEntity custom n'est specifie, utilise AnimatedBlockTileEntityGeneric.
    Final
Java — Exemple complet avec toutes les options
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();
Quand garder .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() :

Java — animation hybride (keyframes + procedural)
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

MethodeType retourDescription
defineKeyframes(AnimContext ctx)voidDeclaratif. Optionnel. Cache (rebuilt sur invalidateDef()).
compute(AnimContext ctx, AnimationPose pose)voidProcedural. Optionnel. Appele chaque frame.
duration()floatObligatoire. Duree totale en ticks.
endBehavior()EndBehaviorComportement de fin (defaut FREEZE). Voir EndBehaviors.
invalidateDef()voidForce le rebuild de defineKeyframes() au prochain frame.

Enregistrement et lecture

Java — enregistrer puis jouer
// 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();
Important : le registre Java doit etre alimente identiquement sur serveur et client. L'idiome standard est de l'enregistrer dans le constructeur du TileEntity (qui s'execute des deux cotes).

AnimContext

Objet contextuel passe a defineKeyframes() et compute(). Champs publics modifiables :

ChampTypeDescription
animTickfloatTick d'animation courant (avec partie partielle). Disponible dans compute().
partialfloatPartial ticks bruts [0.0, 1.0) pour interpolation fine.
worldTicklongTick du monde au moment du rendu.
targetObjectLe TileEntity ou l'entite. Cast via les helpers.

Methodes

MethodeRetourDescription
at(int tick)TickBuilderDemarre une keyframe a ce tick. Utilise dans defineKeyframes().
asEntity()EntityLivingCast de target, ou null si non-entite.
asTileEntity()TileEntityCast 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 :

Java — chainage de keyframes
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

MethodeDescription
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

Java — sin/cos pour bobbing et balancement
@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

CategorieMethodes
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)
EchellescaleTo(x,y,z), scale(s)
Visibilitevisible(boolean)
Chainagegroup(String) — bascule sur un autre groupe
Quand utiliser quoi ? Utilise 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.

Java — declencher un overlay
// Lancer l'overlay
te.playOverlay("flash", new FlashOverlayAnim());

// Stopper
te.stopOverlay();

// Verifier
boolean active = te.hasOverlay();
String name = te.getOverlayAnimName();

API overlay

MethodeRetourDescription
playOverlay(String, EriAnimJava)voidJoue une anim Java en overlay. Boucle automatiquement.
stopOverlay()voidArrete l'overlay.
hasOverlay()booleantrue si un overlay est actif.
getOverlayAnimName()StringNom de l'overlay, ou null.
addJavaAnimation(name, anim)voidEnregistre une anim Java dans le registre principal.
isJavaAnimation(name)booleantrue si name est dans le registre Java.
getJavaAnimation(name)EriAnimJavaRecupere une anim enregistree.
Note : seuls les 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().

Java — TileEntity custom avec callbacks
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; }
}
Java — Enregistrer avec le TE custom
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 :

Java — Avec un fichier .erianim (approche originale)
// 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();
Java — Avec des animations Java (pas de fichier .erianim)
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();
Java — Les deux combines (.erianim + Java sur le meme bloc)
// 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();
Quand garder .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.
Quand garder .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.

AnimatedBlockTileEntity
fr.eri.eriapi.anim.AnimatedBlockTileEntity
TileEntity

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

  • AnimatedBlockTileEntity()
    Constructeur vide. Le modelId et animFileId seront configures par le builder ou manuellement via les setters.
    Constructeur
  • AnimatedBlockTileEntity(String modelId)
    Constructeur avec l'identifiant du modele Blockbench. Ex: "monmod:block/lootbox".
    Constructeur
  • AnimatedBlockTileEntity(String modelId, String animFileId)
    Constructeur complet. modelId = modele 3D, animFileId = fichier d'animation.
    Constructeur

Controle des animations (serveur uniquement)

  • void playAnimation(String animName)
    Joue l'animation par son nom. Utilise le EndBehavior par 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.
    Serveur
  • void playAnimation(String animName, EndBehavior endBehaviorOverride)
    Joue l'animation avec un EndBehavior different de celui du fichier .erianim.json. Passer null pour utiliser le comportement par defaut du fichier.
    Cote : Serveur uniquement.
    Serveur
  • void 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.
    Serveur
  • void 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.
    Serveur
  • void resetAnimation()
    Reinitialise l'animation. Si l'animation actuelle a un EndBehavior de type ROLLBACK et un rollbackDuration > 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.
    Serveur
  • void resetAnimation(boolean instant)
    Reinitialise l'animation. Si instant est true, revient toujours immediatement a la position de depart (ignore le rollback). Si false, utilise le rollback si le EndBehavior est ROLLBACK.
    Cote : Serveur uniquement.
    Serveur
  • void stopAnimation()
    Arrete immediatement l'animation. Aucun rollback, aucun callback onAnimationComplete. Le block passe directement a l'etat IDLE. Utilise pour couper net une animation.
    Cote : Serveur uniquement.
    Serveur

Etat et progression

  • boolean isAnimating()
    Retourne true si le block est en train de jouer une animation (PLAYING ou ROLLING_BACK). Retourne false pour tous les autres etats (IDLE, FROZEN, PAUSED, WAITING_SERVER).
    Getter
  • AnimState getAnimState()
    Retourne l'etat actuel de l'animation. Voir la section AnimStates pour la liste des etats possibles.
    Getter
  • String getCurrentAnimation()
    Retourne le nom de l'animation en cours, ou une chaine vide "" si aucune animation n'est active.
    Getter
  • float getAnimationProgress()
    Retourne la progression de l'animation entre 0.0 (debut) et 1.0 (fin). Retourne 0.0 si aucune animation n'est active. Retourne 1.0 si l'animation est en etat FROZEN ou WAITING_SERVER.
    Cote : Client et serveur.
    Getter
  • String getCurrentAnimName()
    Retourne le nom interne de l'animation en cours. Identique a getCurrentAnimation().
    Getter

Textures dynamiques

  • void setTexture(String target, String value)
    Remplace une texture du modele par une autre en temps reel. target est la cle de texture originale (definie dans le modele Blockbench, ex: "side_core"). value est 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 parametre target est resolu a la fois par la cle JSON et par la valeur du modele Blockbench. Par exemple, si le modele contient "beam": "rarity_beam", alors setTexture("beam", ...) et setTexture("rarity_beam", ...) fonctionnent tous les deux.
    Cote : Serveur uniquement.
    Serveur
  • void clearTexture(String target)
    Supprime un override de texture specifique. Le modele reprend la texture originale pour cette cle.
    Cote : Serveur uniquement.
    Serveur
  • void clearAllTextures()
    Supprime tous les overrides de texture. Le modele reprend toutes ses textures originales.
    Cote : Serveur uniquement.
    Serveur
  • Map<String, String> getTextureOverrides()
    Retourne la map des overrides de texture actuels (target → value). Lecture seule.
    Getter

Configuration

  • void setAnimFileId(String animFileId)
    Change le fichier d'animation utilise par ce TileEntity. Utile pour charger des animations differentes sur le meme modele en runtime.
    Setter
  • String getAnimFileId()
    Retourne l'identifiant du fichier d'animation actuel.
    Getter
  • void setModelId(String modelId)
    Change le modele 3D utilise par ce TileEntity.
    Setter
  • String getModelId()
    Retourne l'identifiant du modele 3D actuel.
    Getter
  • void setMaxRenderDistance(double distance)
    Change la distance maximale de rendu (en blocks).
    Setter

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.

Callbacks a surcharger
Dans ta sous-classe de AnimatedBlockTileEntity
Protected
  • void onAnimationEvent(AnimationEvent event)
    Appele chaque fois qu'un event du fichier .erianim.json se declenche (son, particule, callback, hide/show group). Le parametre event contient le type (event.getType()), la valeur (event.getValue()), et les proprietes specifiques au type (volume, pitch, count, spread).
    Serveur
  • void onAnimationComplete(String animName)
    Appele quand une animation se termine et que son EndBehavior est resolu. Par exemple, appele apres un freeze, apres la fin d'un rollback, ou quand un LOOP_COUNT atteint sa derniere iteration.
    Serveur
  • void onAnimationCallback(String callbackName)
    Appele quand un event de type "callback" se declenche dans le fichier .erianim.json. Le callbackName correspond au champ "value" de l'event. C'est le moyen principal pour synchroniser la logique serveur avec l'animation.
    Serveur
Java — Exemple de callbacks
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.

Java — EriAnimBlock avec callback onRightClick
// 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.

Java — Sous-classe avec update()
// 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".

Java — Declenchement via EriEvents
// 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.

Java — Commande custom
// 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.

Ou placer ce code

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.

Java — Entite avec animation idle
// 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.

Java — Entite avec animation de marche
// 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.

Java — Entite avec animation d'attaque
// 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

Lecture d'animations supportee

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.

Java — Jouer une animation sur un item
// 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");
Format de l'animFileId

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

JSON — 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

JSON — Definition 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 :

rotation
Pose — orientation absolue
Keyframe [x, y, z]

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.

JSON — Track rotation
"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" }
]
rotate
Rotation cumulative — multi-tours
Keyframe [x, y, z]

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).

JSON — Track rotate (2 tours complets en Y)
"rotate": [
  { "tick": 0,  "value": [0, 0, 0],     "easing": "linear" },
  { "tick": 60, "value": [0, 720, 0],   "easing": "ease_in_out" }
]
translation
Position — deplacement
Keyframe [x, y, z]

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.

JSON — Track translation (le groupe descend puis remonte)
"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" }
]
scale
Echelle — taille
Keyframe [x, y, z]

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).

JSON — Track scale (le groupe grandit puis revient)
"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" }
]
visible
Visibilite — afficher/masquer
Instantane

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.

JSON — Track visible (le cadenas disparait au tick 8)
"visible": [
  { "tick": 0, "value": true },
  { "tick": 8, "value": false }
]
spin
Rotation continue — degres/tick
SpinKeyframe

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.

JSON — Track spin (accelere puis ralentit)
"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".
texture
Changement de texture — swap instantane
Instantane

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.

JSON — Track texture (l'ecran s'allume au tick 10)
"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).

JSON — Animation d'element avec rotation infinie
{
  "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] }
          ]
        }
      }
    }
  }
}
Retrocompatibilite totale. Si 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).

Java — AnimationDef
// 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"
Java — AnimationPose
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 :

  1. translate(rotationOrigin)
  2. Rotation statique JSON de l'element (si rotation est definie dans le modele Blockbench).
  3. Translation animee (elemPose.translation / 16, convertie depuis les pixels Blockbench).
  4. Rotations animees (rotation Z/Y/X puis cumulativeRotation Y/X/Z — la part spin).
  5. Echelle animee (elemPose.scale).
  6. translate(-rotationOrigin).
  7. 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)
JSON — Exemples d'events
"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).

EndBehavior
fr.eri.eriapi.anim.EndBehavior
Enum
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...).
Java — Surcharger le EndBehavior en code
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.

Easing
fr.eri.eriapi.gui.anim.Easing
Enum — 13 fonctions
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.
Notation dans le JSON

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.

AnimState
fr.eri.eriapi.anim.AnimState
Enum
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
Java — Verifier l'etat d'animation
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.

Java — Item avec modele 3D Blockbench
EriItem.create("monmod", "epee_fancy")
    .maxStackSize(1)
    .rarity(EnumRarity.EPIC)
    .animatedModel("monmod:item/epee_fancy")  // Modele Blockbench 3D
    .register();
Textures dans le JSON Blockbench

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.

Emplacement du fichier 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.

Enregistrement manuel

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

MethodeDescription
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.5Enregistre 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 :

MethodeDescription
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 statiqueDescription
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).

ChampTypeDescription
x, y, zfloatPosition du siege en pixels Blockbench (local a l'entite).
yawOffsetfloatRotation yaw du passager en degres (defaut: 0).
attachedGroupStringNom du groupe du modele auquel le siege est attache (peut etre null). Le siege suit les animations du groupe.
lockCamerabooleanSi true, la camera du joueur assis est verrouillee (defaut: false).

Exemple complet

Java — Entite animee complete
// 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.

Java — Avec un fichier .erianim (approche originale)
// 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();
Java — Builder EriEntity avec .javaAnimation() v1.6.5
// 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();
Java — Mix .erianim + Java sur la meme entite
// 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();
Java — Classe d'entite : recuperer les anims via ContentRegistry
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;
    }
}
Pourquoi .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.
Synchronisation multijoueur : dans cet exemple, 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.
EndBehavior pris en charge : les animations Java supportent 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.
Gestion manuelle de l'etat d'animation

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.

ContentRegistry.getEntityDefs()

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.

AnimationStartEvent
fr.eri.eriapi.anim.event.AnimationStartEvent
@Cancelable

Fire avant qu'une animation commence. Si annule, l'animation n'est pas jouee.

  • World getWorld()
    Le monde ou se trouve le block.
    Getter
  • BlockPos getPos()
    La position du block.
    Getter
  • AnimatedBlockTileEntity getTileEntity()
    Le TileEntity du block anime.
    Getter
  • String getAnimName()
    Le nom de l'animation qui va etre jouee.
    Getter
  • EndBehavior getEndBehavior()
    Le EndBehavior effectif (override ou defaut du fichier).
    Getter
AnimationEndEvent
fr.eri.eriapi.anim.event.AnimationEndEvent
@Cancelable

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).

  • World getWorld()
    Le monde ou se trouve le block.
    Getter
  • BlockPos getPos()
    La position du block.
    Getter
  • AnimatedBlockTileEntity getTileEntity()
    Le TileEntity du block anime.
    Getter
  • String getAnimName()
    Le nom de l'animation qui se termine.
    Getter
  • EndBehavior getEndBehavior()
    Le EndBehavior qui va etre applique.
    Getter
AnimationResetEvent
fr.eri.eriapi.anim.event.AnimationResetEvent
@Cancelable

Fire avant qu'une animation soit reinitalisee. Si annule, le reset n'est pas applique.

  • World getWorld()
    Le monde ou se trouve le block.
    Getter
  • BlockPos getPos()
    La position du block.
    Getter
  • AnimatedBlockTileEntity getTileEntity()
    Le TileEntity du block anime.
    Getter
  • String getPreviousAnim()
    Le nom de l'animation qui etait en cours avant le reset.
    Getter
  • boolean isInstant()
    true si le reset est instantane, false si un rollback est utilise.
    Getter
AnimationLoopEvent
fr.eri.eriapi.anim.event.AnimationLoopEvent
@Cancelable

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).

  • World getWorld()
    Le monde ou se trouve le block.
    Getter
  • BlockPos getPos()
    La position du block.
    Getter
  • AnimatedBlockTileEntity getTileEntity()
    Le TileEntity du block anime.
    Getter
  • String getAnimName()
    Le nom de l'animation qui boucle.
    Getter
  • int getCurrentLoop()
    Numero de l'iteration actuelle (commence a 0). La premiere fois que l'animation se termine = 0.
    Getter
  • int getMaxLoops()
    Nombre maximum de boucles pour LOOP_COUNT, ou -1 pour les boucles infinies (LOOP et LOOP_PAUSE).
    Getter

Exemples de listeners

Java — Ecouter les events avec EriEvents
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 :

  1. Override d'animation (track texture du .erianim.json) — priorite la plus haute.
  2. Override du TileEntity (te.setTexture()) — priorite moyenne.
  3. Texture originale du modele — priorite la plus basse (defaut).
Java — Changer les textures via l'API
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", ...}
Java — Utiliser les callbacks pour la logique serveur + textures
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.

CommandEriAnim
fr.eri.eriapi.anim.command.CommandEriAnim
OP 2+
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.

Structure des fichiers dans le mod
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)
Resolution des chemins

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 idleRenderDirty controle 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 (AnimModelCache et EriAnimCache). 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 LOOP consomme 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.json pret a etre integre dans ton mod.
Ou trouver l'editeur

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.