12 juil. 2014, 20:42

youtubeCe tutoriel est également disponible en vidéo.

Sommaire

Introduction

Tous les blocs du même type possèdent la même instance cela cause problème lorsqu’on souhaite avoir des variables différentes sur chaque bloc du monde. Par exemple, si vous déclarez un nombre dans la classe du bloc, et que vous le faites varier en cliquant sur le bloc, vous constaterez qu’en plaçant deux blocs sur la map et en faisant varier le nombre sur le premier bloc puis sur le second bloc, les valeurs vont se succéder. De plus, si vous quittez le monde, le nombre reviendra à 0.
Pour éviter ce problème il faut utiliser les entités de bloc (TileEntity). Les entités de bloc possèdent une instance par bloc posé dans le monde, ainsi leurs variables sont uniques. Par exemple, si vous placez deux coffres, que vous mettez un item dans l’un, il ne sera pas dans l’autre. C’est grâce aux tileentity, car comme dit plus haut, si la variable valant le contenu du coffre était dans la classe du bloc, tous les coffres auront le même contenu.
Du fait que les entités de bloc ont une instance par bloc du monde, pour obtenir de l’instance d’une entité de bloc, il vous faudra 4 valeur pour instancier une entité de bloc :

  • le monde (World)
  • la coordonnée x (int)
  • la coordonnée y (int)
  • la coordonnée z (int)

La fonction à utiliser est : world.getTileEntity(x, y, z)
Les entités de bloc ont aussi une fonction pour écrire leurs données dans le tag nbt du monde, et une fonction pour lire ces données.

Autre chose qui ne sera pas traitée dans ce tutoriel, les entités de bloc permettent aussi de faire un rendu de bloc avancé (comme le coffre et portail de l’end) avec les TileEntitySpecialRenderer.

Ce qu’il faut retenir :
Les entités de bloc permettent d’avoir une instance par bloc posé dans le monde.
Les entités de bloc permettent de sauvegarder des données dans le monde.
Les entités de bloc permettent de faire des rendus de bloc complexes.
Maîtriser les entités de bloc est donc très important pour la suite.

Pré-requis

Code

La classe du bloc :

Pour signaler que votre bloc possède une entité de bloc, il faut ajouter deux méthodes. La première va spécifier la classe du tileentity, et la deuxième signaler que le bloc a une entité de bloc :

    @Override
    public TileEntity createTileEntity(World world, int metadata)
    {
        return new TileEntityTutoriel();
    }

    @Override
    public boolean hasTileEntity(int metadata)
    {
        return true;
    }

Ces deux méthodes ont un argument int qui correspond au metadata, vous pouvez donc attribuer une entité de bloc différente en fonction du metadata :

    @Override
    public TileEntity createTileEntity(World world, int metadata)
    {
        if(metadata == 0)
        {
        return new TileEntityTutoriel();
        }
        else if(metadata == 1)
        {
            return new TileEntityTutoriel2();
        }
        else if(metadata == 2)
        {
            return new TileEntityTutoriel3();
        }
        return null;
    }

    @Override
    public boolean hasTileEntity(int metadata)
    {
        if(metadata >= 0 && metadata <= 2)
            return true;
        return false;
    }

Ici mes blocs de 0 à 2 inclus ont une entité de bloc, les autres non.

La classe du TileEntity :

Créez simplement la classe :

package fr.minecraftforgefrance.tutoriel.common;

import net.minecraft.tileentity.TileEntity;

public class TileEntityTutoriel extends TileEntity
{

}

Et voila, l’entité de bloc est terminé. Dans le cas ou vous avez besoin d’un TileEntity pour faire un rendu TESR, vous n’avez pas besoin d’ajouter des fonctions dans cette classe.

Dans l’autre cas où vous souhaitez enregistrer des variables dans le monde, vous aurez besoin de ces deux fonctions :

    @Override
    public void readFromNBT(NBTTagCompound compound)
    {
        super.readFromNBT(compound);
    }

    @Override
    public void writeToNBT(NBTTagCompound compound)
    {
        super.writeToNBT(compound);
    }

La première va servir à lire les variables depuis un tag nbt, et la deuxième va servir à écrire les variables dans un tag nbt.
Vous pouvez écrire différent types de variable :

  • compound.setByte(String nom, byte valeur)
  • compound.setShort(String nom, short valeur)
  • compound.setInteger(String nom, int valeur)
  • compound.setLong(String nom, long valeur)
  • compound.setFloat(String nom, float valeur)
  • compound.setDouble(String nom, double valeur)
  • compound.setString(String nom, String valeur)
  • compound.setByteArray(String nom, byte[] valeur)
  • compound.setIntArray(String nom, int[] valeur)
  • compound.setBoolean(String nom, boolean valeur)

Et il existe évidemment un équivalent pour les lire :

  • valeur = compound.getByte(String nom)
  • valeur = compound.getShort(String nom)
  • valeur = compound.getInteger(String nom)
  • valeur = compound.getLong(String nom)
  • valeur = compound.getFloat(String nom)
  • valeur = compound.getDouble(String nom)
  • valeur = compound.getString(String nom)
  • valeur = compound.getByteArray(String nom)
  • valeur = compound.getIntArray(String nom)
  • valeur = compound.getBoolean(String nom)

/!\ Important : Les variables que vous allez déclarer dans l’entité de bloc ne doivent pas être statiques ! Sinon vous perdez tout l’intérêt des entités de bloc, qui est d’avoir une instance par bloc posé dans le monde (les variables statiques n’ont qu’une instance, donc déclarer une variable statique dans l’entité de bloc reviendrait au même que déclarer une variable dans le bloc et causerai donc le même problème que celui énoncé dans l’introduction) /!\

Deux autres fonctions qui pourront vous être utile :

    public Packet getDescriptionPacket()
    {
        NBTTagCompound nbttagcompound = new NBTTagCompound();
        this.writeToNBT(nbttagcompound);
        return new S35PacketUpdateTileEntity(this.xCoord, this.yCoord, this.zCoord, 0, nbttagcompound);
    }

    public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity pkt)
    {
        this.readFromNBT(pkt.func_148857_g());
    }

Elles permettent d’envoyer au client toutes les valeurs qui ont été enregistrées dans le tag nbt lorsque l’entité de bloc est chargée.
Attention, elles ne permettent pas d’envoyer les valeurs du client vers le serveur, pour cela il faut utiliser un paquet, ce que nous verrons plus tard dans un autre tutoriel.

La classe principale :

Toute entité de bloc doit être enregistrée, sinon vous allez avoir des erreurs et ses données ne seront pas enregistrés (la fonction writeToNBT en a besoin).
Pour cela, dans la fonction init de votre classe principale, ajoutez :

        GameRegistry.registerTileEntity(VotreTileEntity.class, "modid:nom");

Vous n’êtes pas obligé de mettre le modid dans le nom, mais je vous recommande de le mettre pour éviter tout risque de conflit avec un autre mod (si deux entités de bloc avec le même nom sont enregistrés, le jeu crash).

Exemple d’utilisation :

Vous n’avez pas compris comment utiliser writeToNBT et readFromNBT ? Heureusement cet exemple et là pour vous. Ici je vais enregistrer dans mon entité de bloc un int qui va pouvoir être augmenté en faisant un clic droit sur le haut du bloc, et diminué en faisait un clic droit sur le bas du bloc.
Je vais simplement copier/coller le code et le commenter afin que vous le comprenez.

La classe de l’entité de bloc :

package fr.minecraftforgefrance.tutoriel.common;

import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;

public class TileEntityTutoriel extends TileEntity
{
    private int number; // on déclare le nombre qui va varier

    @Override
    public void readFromNBT(NBTTagCompound compound)
    {
        super.readFromNBT(compound);
        this.number = compound.getInteger("Number"); // pour lire sa valeur depuis  la sauvegarde du monde lorsqu'on charge le chunk qui contient l'entité de bloc
    }

    @Override
    public void writeToNBT(NBTTagCompound compound)
    {
        super.writeToNBT(compound);
        compound.setInteger("Number", this.number); // pour enregistrer sa valeur dans la sauvegarde du monde lorsqu'on décharge le chunk qui contient l'entité de bloc
    }

    public void increase() // une fonction pour augmenter sa valeur
    {
        this.number++;
    }

    public void decrease() // une fonction pour diminuer sa valeur
    {
        this.number--;
    }

    public int getNumber() // et une fonction pour obtenir sa valeur (on appelle ça un getter)
    {
        return number;
    }
}

La classe du bloc :

package fr.minecraftforgefrance.tutoriel.common;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.world.World;

public class BlockTutoriel2 extends Block
{
    protected BlockTutoriel2(Material material)
    {
        super(material);
    }

    @Override
    public boolean hasTileEntity(int metadata)
    {
        return true; // signale que le bloc a une entité
    }

    @Override
    public TileEntity createTileEntity(World world, int metadata)
    {
        return new TileEntityTutoriel(); // indique quelle est l'entité de bloc
    }

    public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int side, float hitX, float hitY, float hitZ)
    {
        /* 
            * petit explication sur la condition world.isRemote
            * world.isRemote = monde client, c'est celui qui va gérer le rendu
            * !world.isRemote = monde serveur, il va gérer le reste, notamment la sauvegarde, les variables d'une entité de bloc sont donc à manipuler côté serveur seulement, d'où la condition juste en dessous
            */
        if(!world.isRemote)
        {
            TileEntity tile = world.getTileEntity(x, y, z); // on obtient l'instance du TileEntity
            if(tile instanceof TileEntityTutoriel) // si le TileEntity est bien le nôtre (cette condition est importante pour éviter tout risque de corruption de monde, car il peut arriver qu'une mauvaise entité de bloc soit sur les coordonnées de votre bloc)
            {
                TileEntityTutoriel tileTuto = (TileEntityTutoriel)tile; // on cast pour avoir accès au méthode qui se trouve dans TileEntityTutoriel
                if(side == 0) // si le côté est 0, donc en dessous, on appelle la fonction decrease pour diminuer la valeur
                {
                    tileTuto.decrease();
                }
                else if(side == 1) // si le côté est 1, donc en dessous, on appelle la fonction increase pour augmenter la valeur
                {
                    tileTuto.increase();
                }
                player.addChatMessage(new ChatComponentTranslation("tile.tutoriel2.number", tileTuto.getNumber())); // et on affiche par un message tchat la valeur. ChatComponentTranslation permet de faire un String.format, dans mon fichier de lang je vais mettre %d qui sera remplacé par la valeur de tileTuto.getNumber(). (voir plus bas)
                return true;
            }
        }
        return false;
    }
}

Dans mon fichier en_US.lang :

tile.tutoriel2.number=Value : %d

Comme dit plus haut, %d sera remplacé par la valeur de tileTuto.getNumber(). L’avantage avec ça, c’est que si dans un langage on aurait mit la valeur avant le texte, on aurait fait : %d texte.
Pour les curieux, tapez sur un moteur de recherche “formatage string java”, vous trouverez plus d’informations à ce sujet.

Et pour finir, l’entité de bloc est enregistrée dans ma classe principale :

        GameRegistry.registerTileEntity(TileEntityTutoriel.class, "modtutoriel:tutoriel");

Résultat

Voir le commit sur github
Le commit sur github montre clairement où ont été placés les fichiers, ainsi que ce qui a été ajouté et retiré dans le fichier.

En vidéo

https://www.youtube.com/watch?v=SAFFui9J364

Crédits

Rédaction :

Correction :

Creative Commons
Ce tutoriel de Minecraft Forge France est mis à disposition selon les termes de la licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International

retourRetour vers le sommaire des tutoriels