Monday, March 2, 2015

Minecraft Tutorials: A Basic Block

This tutorial is a continuation of my tutorial series on modding Minecraft.  It will discuss adding a custom block with a custom name and texture.  It assumes you already have a basic mod class created.  If you don't, you can read about that here.

The block we will be adding will be pretty much the most common new ore added by a Minecraft mod, copper ore!  I'm mostly doing this because it leads in to discussing all sorts of other Minecraft modding concepts, like items, recipes, forge's ore dictionary, adding to world generation, etc.

I prefer to keep my blocks in their own sub package, so we will begin by creating a new package, mine is me.codasylph.grindermod.blocks.  In that package we will create a new class, CopperOre which extends Block.  Our class looks like this:

package me.codasylph.grindermod.blocks;

import net.minecraft.block.Block;

public class CopperOre extends Block
{
       public CopperOre()
       {

       } 
}

Right now we have an error because we need an explicit constructor. Lets write one.

public CopperOre()
{
   super(Material.rock);
}

We want it to be public because it is going to be called by an outside class.  Right now it just calls the super constructor, which take one parameter, a material.  We set the material to rock because well, in game our block is some stone with bits of ore in it.  Note: one property of the rock material is that it cannot be harvested without a tool.

Technically we're done with this class, we could go register this block right now and it would exist as a new block in Minecraft, but it wouldn't have a name or a texture or anything unique about it, so lets add some more information.

Above the constructor lets add a String variable called unlocalized name, like this:
private final String unlocalizedName = "copperOre";

Now inside our constructor let's add the following:
super(Material.rock);
this.setBlockName(unlocalizedName);
this.setBlockTextureName("grindermod:"+unlocalizedName);
this.setHardness(3.0F);
this.setResistance(5.0F);
this.setHarvestLevel("pickaxe",1);
this.setCreativeTab(CreativeTabs.tabBlock);

Ok, so what is all this doing?
setHardness() indicates how long it takes to mine our block, and 3.0 is equivalent to gold or iron ore.
setResistance() indicates how resistant the block is to explosions, and again we set it about the same as vanilla Minecraft ores.
setHarvestLevel() takes two parameters, the first indicates what kind of tool is used to harvest this block, and the second indicates what minimum material is needed to harvest it.  1 is Stone, so a stone or better pickaxe is needed to harvest our block.
setCreativeTab() indicates where you can find the block in creative mode, for most mods you would create your own new tab where the blocks and items for the mod would be found, but for now we'll just lump it in with other vanilla Minecraft blocks.

Note: if you have your modid stored in a class with all your strings you can of course do setBlockTextureName(Constants.modId + ":" + unlocalizedName) or what have you.  What this method really does is tells forge how to find the texture, in this case it's looking for a png file named copperOre in the folder assets/grindermod/textures/blocks. (All textures in Minecraft are pngs.)

Speaking of this now is as good time as any to go ahead a make a new package in the resources folder.  My package will be assets.grindermod.textures.blocks and in it we will place this image file:

We are done with this class.  It should look more or less like this:

package me.codasylph.grindermod.blocks;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.creativetab.CreativeTabs;

public class CopperOre extends Block
{
       private final String unlocalizedName = "copperOre";
      
       public CopperOre()
       {
              super(Material.rock);
              this.setBlockName(unlocalizedName);
              this.setBlockTextureName("grindermod:"+unlocalizedName);
              this.setHardness(3.0F);
              this.setResistance(5.0F);
              this.setHarvestLevel("pickaxe",1);
              this.setCreativeTab(CreativeTabs.tabBlock);
       }
}

Now we want to register our block.  Until it is registered forge doesn't really know the block exists. The fastest way to do this is to add this line:
GameRegistry.registerBlock(new CopperOre()"copperOre");
to the preInit() method of our main mod file (GinderMod.class, in this case).

This would work fine, but because most mods require a bunch of new blocks, I prefer to create a new class to load them all in and keep my main mod file looking a lot cleaner.

To do that I'll create a new class in the package me.codasylph.grindermod.blocks called ModBlocks.

In it I will instantiate a public static Block named copperOre and initialize it to a new instance of the CopperOre class and add a new public static method called init, my call to registerBlock() will go in this method, and it will all look like this:

package me.codasylph.grindermod.blocks;

import cpw.mods.fml.common.registry.GameRegistry;
import net.minecraft.block.Block;

public class ModBlocks
{
       public static Block copperOre = new CopperOre();
      
       public static void init()
       {
              GameRegistry.registerBlock(copperOre, copperOre.getUnlocalizedName());
       }

}

From now on any time I add a new block I will register it in this init method.

Now we need to make sure init actually gets called.  I will do that by heading over to GrinderMod.class and adding a call to it to the preInit method.  At this point GrinderMod.class will look like this:

package me.codasylph.grindermod;

import me.codasylph.grindermod.blocks.CopperOre;
import me.codasylph.grindermod.blocks.ModBlocks;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.registry.GameRegistry;

@Mod(modid = "grindermod", name = "Grinder Mod", version = "1.0")

public class GrinderMod
{     
       @Mod.EventHandler
       public void preInit(FMLPreInitializationEvent event)
       {
              ModBlocks.init();
       }

       @Mod.EventHandler
       public void init(FMLInitializationEvent event)
       {

       }

       @Mod.EventHandler
       public void postInit(FMLPostInitializationEvent event)
       {
             
       }

}

If you run the test environment now you should be able to find our new block in the creative tab for blocks, and it should have the texture above, but it will have a funny tile.something name.  

To give it a proper local name we will have to create a lang file.  I will do this by going creating a new package in my resources folder assets.grindermod.lang.  In here I will create a new file called en_US.lang.  In the body of this new file I'll type:
tile.copperOre.name=Copper Ore
Note that it is important that there are no spaces between the unlocalized name and the equals sign or the equals sign and the localized name.

Now when you load the testing environment again our new block should appear in the blocks creative tab with the name Copper Ore.

Some final notes: there are a lot more customize-able aspects of the Block than those mentioned in this tutorial.  It might be beneficial to check out the declaration of the vanilla Block class and see what other things can be tweaked!

And that, as they say is that!  My next tutorial will discuss creating a custom Item.

You can get to the table of contents by clicking here.

No comments:

Post a Comment