Skip to content

CURSUS DE DÉVELOPPEMENT DE MODS MINECRAFT — NIVEAU AVANCÉ

Module 10: Les Blocs à Entités (Block Entities)

Donner de l'intelligence et de la mémoire aux blocs sous Forge 1.20.1


1. Introduction: Pourquoi une Block Entity ?

Dans Minecraft, les blocs sont des objets "légers". Pour économiser de la mémoire, un seul objet "Pierre" est partagé par tous les blocs de pierre du monde. On ne peut donc pas stocker de données spécifiques (comme le contenu d'un coffre ou l'énergie d'une machine) directement dans la classe Block.

La Block Entity (BE) (anciennement appelée Tile Entity) est une classe associée à une coordonnée précise du monde. Elle permet de stocker des variables persistantes (via le format NBT) et d'exécuter du code à chaque tick de jeu (20 fois par seconde).


2. Étape 1: Le Registre (Registry)

Avant de programmer la logique de l'entité, il faut enregistrer son type (BlockEntityType). C'est ce qui permet à Forge de lier votre bloc à son conteneur de données.

Fichier : src/main/java/com/nomauteur/modid/registry/ModBlockEntities.java

java
package com.nomauteur.modid.registry;

import com.nomauteur.modid.ModId;
import com.nomauteur.modid.block.entity.MaMachineBE;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;

public class ModBlockEntities {
    public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITIES =
        DeferredRegister.create(ForgeRegistries.BLOCK_ENTITIES, ModId.MOD_ID);

    // Enregistrement du type d'entité en lui associant le bloc valide
    public static final RegistryObject<BlockEntityType<MaMachineBE>> MA_MACHINE_BE =
        BLOCK_ENTITIES.register("ma_machine_be", () ->
            BlockEntityType.Builder.of(MaMachineBE::new, ModBlocks.MA_MACHINE.get()).build(null)
        );

    public static void register(IEventBus eventBus) {
        BLOCK_ENTITIES.register(eventBus);
    }
}

(Note : La structure a été complétée pour former un fichier de registre Forge standard et fonctionnel ).


3. Étape 2: La Classe de l'Entité (La Logique)

C'est le cerveau de votre bloc. Elle gère l'inventaire interne et s'assure de sauvegarder son contenu sur le disque dur lorsque le monde se ferme.

Fichier : src/main/java/com/nomauteur/modid/block/entity/MaMachineBE.java

java
package com.nomauteur.modid.block.entity;

import com.nomauteur.modid.registry.ModBlockEntities;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.blockentity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemStackHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class MaMachineBE extends BlockEntity {

    // 1. Définition de l'inventaire (Forge ItemStackHandler à 3 slots)
    private final ItemStackHandler itemHandler = new ItemStackHandler(3) {
        @Override
        protected void onContentsChanged(int slot) {
            setChanged(); [cite_start]// Marque le bloc comme "modifié" pour forcer la sauvegarde sur le disque [cite: 10]
        }
    };

    private LazyOptional<IItemHandler> lazyItemHandler = LazyOptional.empty();

    public MaMachineBE(BlockPos pos, BlockState state) {
        super(ModBlockEntities.MA_MACHINE_BE.get(), pos, state);
    }

    // 2. Écriture des données dans le fichier de sauvegarde NBT du monde
    @Override
    protected void saveAdditional(CompoundTag nbt) {
        nbt.put("inventaire", itemHandler.serializeNBT());
        super.saveAdditional(nbt);
    }

    // 3. Lecture des données depuis le fichier NBT lors du chargement du chunk
    @Override
    public void load(CompoundTag nbt) {
        super.load(nbt);
        itemHandler.deserializeNBT(nbt.getCompound("inventaire"));
    }

    // 4. Boucle d'exécution active (Le Ticker)
    public void tick(Level level, BlockPos pos, BlockState state) {
        // Logique de traitement de la machine (Ex: consommer du carburant, transformer un item)
    }

    [cite_start]// 5. Système de Capabilities (Interopérabilité avec les tuyaux et entonnoirs d'autres mods) [cite: 10]
    @Override
    public @NotNull <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
        if (cap == ForgeCapabilities.ITEM_HANDLER) {
            return lazyItemHandler.cast(); [cite_start]// Permet aux tuyaux tiers d'accéder à l'inventaire [cite: 10]
        }
        return super.getCapability(cap, side);
    }

    @Override
    public void onLoad() {
        super.onLoad();
        lazyItemHandler = LazyOptional.of(() -> itemHandler);
    }

    @Override
    public void invalidateCaps() {
        super.invalidateCaps();
        lazyItemHandler.invalidate();
    }

    [cite_start]// 6. Synchronisation Réseau Serveur -> Client (Utile pour les affichages dynamiques et hologrammes) [cite: 10]
    @Override
    public CompoundTag getUpdateTag() {
        return saveWithoutMetadata(); [cite_start]// Envoie l'intégralité du NBT lors du chargement initial du bloc par le client [cite: 10]
    }

    @Nullable
    @Override
    public Packet<ClientGamePacketListener> getUpdatePacket() {
        return ClientboundBlockEntityDataPacket.create(this); [cite_start]// Envoie les paquets de mise à jour réseau [cite: 10]
    }
}

4. Étape 3: Liaison avec la Classe Block

Pour que votre entité soit instanciée dans le monde, son bloc associé doit implémenter l'interface EntityBlock de Minecraft. C'est également ici que l'on déclare la méthode d'enregistrement du processus de Tick (getTicker).

Fichier : src/main/java/com/nomauteur/modid/block/MaMachineBlock.java

java
package com.nomauteur.modid.block;

import com.nomauteur.modid.block.entity.MaMachineBE;
import com.nomauteur.modid.registry.ModBlockEntities;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;

public class MaMachineBlock extends BaseEntityBlock {

    public MaMachineBlock(Properties props) {
        super(props);
    }

    // Définit la méthode de rendu (indispensable car BaseEntityBlock la passe à INVISIBLE par défaut)
    @Override
    public RenderShape getRenderShape(BlockState state) {
        return RenderShape.MODEL;
    }

    [cite_start]// Crée une nouvelle instance de l'entité de bloc à ces coordonnées [cite: 10]
    @Nullable
    @Override
    public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
        [cite_start]return new MaMachineBE(pos, state); [cite: 10]
    }

    [cite_start]// Enregistre le Ticker pour exécuter le code de l'entité à chaque tick de jeu [cite: 10]
    @Nullable
    @Override
    public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) {
        if (level.isClientSide()) return null; [cite_start]// Le traitement logique principal ne tourne que sur le serveur [cite: 10]
        
        return createTickerHelper(type, ModBlockEntities.MA_MACHINE_BE.get(),
            (lvl, pos, st, be) [cite_start]-> be.tick(lvl, pos, st) [cite: 10]
        );
    }
}

5. Résumé des concepts clés du système de Block Entity

Élément techniqueRôle exact
ItemStackHandlerComposant de Forge remplaçant l'ancien IInventory vanilla. Il gère le stockage sûr des objets.
setChanged()Notifie le moteur de Minecraft qu'une variable interne a changé. Sans cet appel, l'inventaire se viderait à chaque redémarrage du serveur.
CapabilitiesSystème d'interopérabilité de Forge. Il permet à des tuyaux de transport d'items ou d'énergie provenant d'autres mods de se connecter à votre bloc de manière transparente.
getTickerPermet à votre bloc d'avoir un comportement actif (calculer des recettes, consommer de l'énergie) de façon optimisée, sans surcharger le processeur du serveur.