Appearance
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 technique | Rôle exact |
|---|---|
ItemStackHandler | Composant 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. |
Capabilities | Systè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. |
getTicker | Permet à 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. |