4 août 2013, 23:29

Vous êtes actuellement capable de créer des blocs multi-textures, mais vous vous êtes surement rendu compte que ces derniers était toujours dans le même sens et ne se pose pas en rapport de la direction du joueur.

Nous allons voir dans ce tutoriel comment faire ça.

Sur un bloc basique

Pour un bloc basique, nous allons utiliser le même principe que la citrouille. En fonction du sens dans lequel vous allez placer le bloc, le metadata va être différent. Nous allons donc gérer la direction avec les metadata, il est donc impossible d’utiliser cette méthode sur des blocs à metadata !
Rendez-vous dans la classe du bloc. Pour commencer, je vais ajouter un nouveau icône pour devant :

    private Icon icontop, iconbottom;

devient :

    private Icon icontop, iconbottom, iconfront;

Ensuite l’enregistreur d’icône :

    public void registerIcons(IconRegister iconRegister)
    {
        blockIcon = iconRegister.registerIcon("modtutoriel:BlockTutorial");
        iconfront = iconRegister.registerIcon("modtutoriel:BlockTutorial_Front");
        icontop = iconRegister.registerIcon("modtutoriel:BlockTutorial_Top");
        iconbottom = iconRegister.registerIcon("modtutoriel:BlockTutorial_Bottom");
    }

Ajoutez maintenant cette fonction :

    public void onBlockPlacedBy(World world, int x, int y, int z, EntityLivingBase living, ItemStack stack)
    {
        int direction = MathHelper.floor_double((double)(living.rotationYaw * 4.0F / 360.0F) + 2.5D) & 3;
        world.setBlockMetadataWithNotify(x, y, z, direction, 2);
    }

C’est le même code que pour la citrouille, l’int direction va soit prendre la valeur 0, 1, 2 ou 3 en fonction de la direction de l’entité qui à posé le bloc.
Ensuite, world.setBlockMetadataWithNotify va poser le ce bloc avec un metadata de la valeur de la direction. (Le dernier 2 est un flag qui gère une action, le 2 sert à ne rien déclencher, car le fait d’ajouter un nouveau bloc cause déjà la mise à jour de l’affichage du client. Les setBlock et leurs flags seront expliqués plus en détail dans un autre tutoriel).

Maintenant il nous reste plus qu’à changer le getIcon en fonction du metadata du bloc :

    @SideOnly(Side.CLIENT)
    public Icon getIcon(int side, int metadata)
    {
        return side == 1 ? this.icontop : (side == 0 ? this.iconbottom : (metadata == 2 && side == 2 ? this.iconfront : (metadata == 3 && side == 5 ? this.iconfront : (metadata == 0 && side == 3 ? this.iconfront : (metadata == 1 && side == 4 ? this.iconfront : this.blockIcon)))));
    }

C’est juste une longue condition ternaire, rien de méchant 😛 Si vous avez du mal avec, n’hésitez pas à la passer en condition if else, c’est plus simple pour customiser les textures 🙂

Voir sur github

Sur un bloc avec metadata

1. La classe du bloc

Si vous avez essayé le code juste au dessus sur un bloc avec metadata, vous vous êtes surement rendu compte que vous placez un bloc différent en fonction de votre direction. Et c’est normal, car la première fonction utilise les metadata pour la direction. Comme nous utilisons déjà les metadata pour les différents blocs, il faut utiliser autre chose qui peut stocker des données. Nous allons donc utiliser les TileEntity ! Dans mon cas, je vais uniquement le faire mon pour bloc de metadata 2, mais vous pouvez aussi le faire pour les autres.
Avant tout, comme j’ajoute un TileEntity en plus, (je ne l’avais que fait pour le metadata 0) :

    @Override
    public TileEntity createTileEntity(World world, int metadata)
    {
        if(metadata == 0)
            return new TileEntityTutorial();
        else if(metadata == 2)
            return new TileEntityTutorial2();
        else
            return null;
    }

Et :

    public boolean hasTileEntity(int metadata)
    {
        if(metadata == 0 || metadata == 2)
            return true;
        else
          return false;
    }

Maintenant la fonction pour le placement du bloc :

    public void onBlockPlacedBy(World world, int x, int y, int z, EntityLivingBase living, ItemStack stack)
    {
        int direction = MathHelper.floor_double((double)(living.rotationYaw * 4.0F / 360.0F) + 2.5D) & 3;
        TileEntity te = world.getBlockTileEntity(x, y, z);
        if(te != null && stack.getItemDamage() == 2 && te instanceof TileEntityTutorial2)
        {
            ((TileEntityTutorial2)te).setDirection((byte)direction);
            world.markBlockForUpdate(x, y, z);
        }
    }

Donc comme avant, le code pour la direction de l’entity, puis j’instancie le TileEntity, ensuite je vérifie si : le TileEntity n’est pas null (donc qu’il est bien existant), que le metadata de l’ItemStack que le joueur a en main est 2 (comme je ne le fais que pour le metadata 2) et que le TileEntity est d’instance TileEntityTutorial2 qui correspond à la classe de mon TileEntity
Si c’est le cas, je cast TileEntityTutorial2 au tileEntity et je vais la fonction setDirection qui est une fonction que je vais créer dans mon TileEntity. Ensuite je mets à jour l’affichage du client.
D’ailleurs, j’ai aussi casté (je sais pas si ça ce dit, mais la traduction de “cast” est pas mieux) un byte à mon int direction, pour la simple et bonne raison qu’un byte est largement suffisant pour stocké soit 0, 1, 2 ou 3, utiliser un int serait juste surcharger mon TileEntity.

2. La classe du TileEntity

Le principe est exactement que pour le système de visiteur, sauf que la on stock juste un byte :

package tutoriel.common;

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

public class TileEntityTutorial2 extends TileEntity
{
    public byte direction;

    public void readFromNBT(NBTTagCompound nbtTag)
    {
        super.readFromNBT(nbtTag);
        direction = nbtTag.getByte("direction");
    }

    public void writeToNBT(NBTTagCompound nbtTag)
    {
        super.writeToNBT(nbtTag);
        nbtTag.setByte("direction", direction);
    }

    public void setDirection(byte direct)
    {
        direction = direct;
    }

    public byte getDirection()
    {
        return direction;
    }

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

    public void onDataPacket(INetworkManager net, Packet132TileEntityData pkt)
    {
      this.readFromNBT(pkt.data);
    }
}

Les deux dernières fonctions gèrent les packets, si vous ne les mettez pas vous allez avoir des problèmes de direction après déco/réco

3. La classe principale

N’oubliez pas d’enregistrer votre tileEntity dans la partie Init :

    GameRegistry.registerTileEntity(TileEntityTutorial2.class, "TileEntityTutorial2");

4. Finition

La direction de votre bloc est bien sauvegardez dans le nbttag, mais elle n’est pas prise en compte. Petit problème, getIcon ne prend pas en compte le tileEntity. Nous allons donc utiliser cette méthode :

    @SideOnly(Side.CLIENT)
    public Icon getBlockTexture(IBlockAccess blockaccess, int x, int y, int z, int side)
    {
        if(blockaccess.getBlockMetadata(x, y, z) == 2)
        {
            TileEntity te = blockaccess.getBlockTileEntity(x, y, z);
            byte direction = ((TileEntityTutorial2)te).getDirection();
            return side == 1 ? Icon3[0] : (side == 0 ? Icon3[1] : (direction == 2 && side == 2 ? Icon3[2] : (direction == 3 && side == 5 ? Icon3[2] : (direction == 0 && side == 3 ? Icon3[2] : (direction == 1 && side == 4 ? Icon3[2] : Icon3[3])))));
        }
        else
        {
            return this.getIcon(side, blockaccess.getBlockMetadata(x, y, z));
        }
    }

Donc si le metadata est 2, j’instancie le TileEntity, je le cast pour récupérer la direction, et ensuite se sont à nouveau des conditions comme juste au dessus. Si le metadata n’est pas 2 (donc mes autres blocs sans direction) il return sur le getIcon(side, metadata)
/!\ Ne touchez pas à getIcon(side, metadata), laissez-le comme il est, si j’enlève le bloc de metadata 2, l’item en main n’aura plus de texture /!\

Voir sur github