Monday, June 2, 2014

Rendering - Part 1 - Custom Block Model

Prerequisites:
  In this tutorial, you will learn how to create a block with a custom model. For this tutorial, I will make a block that looks like an arrow . Please note that I only have about a week of experience on this topic, so not all of the things I say will be 100% accurate.
Block
First, begin by creating a block that extends BlockContainer.

public class BlockArrow extends BlockContainer
{
    public BlockArrow()
    {
        super(Material.iron);
        setBlockName("arrow");
        setCreativeTab(RSQTutorials.tabRSQ);
        BlocksRSQ.registerBlock(this); // this method calls GameRegistry.registerBlock()
        setBlockBounds(0, 0, 0, 1, 0.0625F, 1); // this makes my block 1 pixel tall
    }
}

Now, add these three methods. They tell minecraft that you're making an "irregular" block.

    @Override
    public int getRenderType()
    {
        return -1;
    }

    @Override
    public boolean isOpaqueCube()
    {
        return false;
    }

    @Override
    public boolean renderAsNormalBlock()
    {
        return false;
    }

To make the block face you when you place it, we're going to add the onBlockPlacedBy() method.

    @Override
    public void onBlockPlacedBy(World world, int i, int j, int k, EntityLivingBase e, ItemStack is)
    {
        int l = MathHelper.floor_double(e.rotationYaw * 4F / 360F + 0.5D) & 3;
        TileEntityArrow te = (TileEntityArrow) world.getTileEntity(i, j, k);
        if(te != null)
        {
                te.direction = l;
        }
    }

  The l variable  is the direction that the block is going to face. We also check if there is a tile entity variable at the x, y, z that the block was placed, and set the direction variable in the tile entity to the direction of the block.

To finish up our block class, add the method below.

    @Override
    public TileEntity createNewTileEntity(World var1, int var2)
    {
        return new TileEntityArrow();
    }

The method creates a new tile entity when our block is placed.
Tile Entity
  Create a new tile entity class that extends TileEntity. Mine will be called TileEntityArrow. Add a public int called direction. It will be stored in the tile entity so NBT can read and write to it easily.

public class TileEntityArrow extends TileEntity
{
    public int direction;
}

Next, add these two methods.

    @Override
    public void writeToNBT(NBTTagCompound par1)
    {
        super.writeToNBT(par1);
        par1.setInteger("direction", direction);
    }

    @Override
    public void readFromNBT(NBTTagCompound par1)
    {
        super.readFromNBT(par1);
        direction = par1.getInteger("direction");

    }

  The writeToNBT method saves the direction variable to the NBT of the tile entity. An example of when it's is called is when you open the Escape menu. The readFromNBT does just the opposite of the writeToNBT method. It reads from the NBT of the tile entity and sets the direction variable to whatever it read.

To finish up your tile entity class, add these two methods that are necessary for NBT work.

    @Override
    public Packet getDescriptionPacket()
    {
        NBTTagCompound var1 = new NBTTagCompound();
        writeToNBT(var1);
        return new S35PacketUpdateTileEntity(xCoord, yCoord, zCoord, 1, var1);
    }

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


Tile Entity Renderer

  Next, create a class that extends TileEntitySpecialRenderer. This is the class where we are going to render the actual model. Add a private final variable called model. The Model**** class is the .java that Techne exported. Also, override the renderTileEntityAt() method.

public class TileEntityArrowRenderer extends TileEntitySpecialRenderer
{
    private final ModelArrow model;

    public TileEntityArrowRenderer()
    {
     model = new ModelArrow();
    }

    @Override
    public void renderTileEntityAt(TileEntity te, double i, double j, double k, float f)
    {
    }
}

  The renderTileEntityAt method is where the model will be draw. First, I create a couple of variables to help later.


    @Override
    public void renderTileEntityAt(TileEntity te, double i, double j, double k, float f)
    {
        TileEntityArrow tea = (TileEntityArrow) te;
        float x = (float) i;
        float y = (float) j;
        float z = (float) k;
    }

  We will be using GL11 to render our model. If you do not know how to use GL11 yet, check out my other tutorial on the basic functions of GL11. To begin, add the method GL11.glPushMatrix() to your renderTileEntityAt() method. glPushMatrix() tells GL11 that you are about to start rendering something. Next, glTranslate to the x, y, and z variables you defined. The method should now look like this.

    @Override
    public void renderTileEntityAt(TileEntity te, double i, double j, double k, float f)
    {
        TileEntityArrow tea = (TileEntityArrow) te;
        float x = (float) i;
        float y = (float) j;
        float z = (float) k;
        GL11.glPushMatrix();
        GL11.glTranslatef(x, y, z);
    }

  We translate our model to x, y, and z because by default, the model follows the player's head around until it is translated. Next, to center our model to the center of the block, we need to translate 0.5, 1.5, and 0.5. After that, we need to "bind" our texture to the render engine. To do that, create a new ResourceLocation variable, and for the argument type in your mod id, then a colon, and finally the location of your file inside that folder, with a .png at the end. My file is located in rsqtutorials\textures\tileentity, and its name is arrow.png, so my resource location's argument will be "rsqtutorials:textures/tileentity/arrow.png". The final step to binding your texture is to call the bindTexture() method in Minecraft's render engine, which is done with the code below:

    Minecraft.getMinecraft().renderEngine.bindTexture(resourcelocation);

The method should now look like this: 

    @Override
    public void renderTileEntityAt(TileEntity te, double i, double j, double k, float f)
    {
        TileEntityArrow tea = (TileEntityArrow) te;
        float x = (float) i;
        float y = (float) j;
        float z = (float) k;
        GL11.glPushMatrix();
        GL11.glTranslatef(x, y, z);
        GL11.glTranslatef(0.5F, 1.5F, 0.5F);

        ResourceLocation resourcelocation = new ResourceLocation(RSQTutorials.MODID + ":textures/tileentity/arrow.png");
        Minecraft.getMinecraft().renderEngine.bindTexture(resourcelocation);
    }

  We are almost done with the renderer class. To render the actual model, we need to push matrix again. Then, because of a bug(?) that exists in Minecraft, a 180° rotation on the Z axis(last argument) is required. Without the rotation, your model will render upside-down. Now, add another glRotatef(). For the first argument, type in "te.direction * 90," and the rotation should be on the Y axis(3rd argument). That code rotates the model depending on how it was placed. The renderTileEntityAt() method should look like this:

    @Override
    public void renderTileEntityAt(TileEntity te, double i, double j, double k, float f)
    {
        TileEntityArrow tea = (TileEntityArrow) te;
        float x = (float) i;
        float y = (float) j;
        float z = (float) k;
        GL11.glPushMatrix();
        GL11.glTranslatef(x, y, z);
        GL11.glTranslatef(0.5F, 1.5F, 0.5F);

        ResourceLocation textures = new ResourceLocation(RSQTutorials.MODID + ":textures/tileentity/arrow.png");
        Minecraft.getMinecraft().renderEngine.bindTexture(textures);

        GL11.glPushMatrix();
        GL11.glRotatef(180F, 0.0F, 0.0F, 1.0F);
        GL11.glRotatef(tea.direction * 90, 0.0F, 1.0F, 0.0F);
    }

  The next step is to, finally, call the model's render code. The code is shown below:

      model.render((Entity) null, 0.0F, 0.0F, -0.1F, 0.0F, 0.0F, 0.0625F);

   To finish the renderer class, add two GL11.glPopMatrix()s. For every glPushMatrix(), you should insert a glPopMatrix(). You are now complete with the renderer.
Model
  You will notice that there are errors in the model class that Techne exported. To fix them, set the package to the appropriate one, and let your IDE import whatever is needed. In the last method of the model class, named setRotationAngles(), insert an Entity parameter. Then, in the super.setRotationAngles(), add the entity variable you just created to the end of the arguments. The error should now be fixed, but another one should be in the render() method of your model. Simply add the Entity variable in the render() method to the end of the setRotationAngles() that the render() method calls. If you are confused, here is how the render() and setRotationAngles() methods should look like in your model class:

    public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5)
    {
        super.render(entity, f, f1, f2, f3, f4, f5);
        setRotationAngles(f, f1, f2, f3, f4, f5, entity);
        Main.render(f5);
        Diagonal1.render(f5);
        Diagonal2.render(f5);
    }

    public void setRotationAngles(float f, float f1, float f2, float f3, float f4, float f5, Entity e)
    {
        super.setRotationAngles(f, f1, f2, f3, f4, f5, e);
    }

  All the errors in your model class should be fixed. There is a couple more steps to this tutorial. The renderer class needs to be registered. In your client proxy, add this line:

    ClientRegistry.bindTileEntitySpecialRenderer(TileEntityArrow.class, new TileEntityArrowRenderer());

  Now, go to your main mod class, and in the init() method, add this code:

    GameRegistry.registerTileEntity(TileEntityArrow.class, "tileEntityArrow");

  You are now complete. If you launch Minecraft, your block should have a custom block model.

  All source code is available here at my githubIf you have any tutorial requests, please comment below. I am willing to do almost anything. Thanks for reading.

No comments:

Post a Comment