初心者modderの備忘録

マイクラのmodを作りたくて、初めて見たのですが難しくて忘れそうなので自分用の備忘録も兼ねてブログにしようと思います

8日目 TileEntityの追加

今回は、TileEntityの追加をやっていきたいと思います。
TileEntityっていうのはチェストみたいに、ブロックそれぞれが独立してデータをもっているようなブロックだと思ってます。

一応、GUIの実装まで書こうと思っているのですが、長くなりそうなのとファイルの数が多くなりそうなので3回くらいに分けて書いていこうと思います。


まずは極めて単純なTileEntityを作っていきます。
GUIを使わない場合は2つファイルがあればTileEntityを作れます。

Block○○.class
TileEntity○○.class
の二つですね。

BlockクラスにTileEntityの機能を追加するようなイメージでしょうか。

BlockTomato.java

package test4mod;

import net.minecraft.block.BlockContainer;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumBlockRenderType;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

public class BlockTomato extends BlockContainer{

	public BlockTomato() {
		super(Material.ROCK);
		this.setCreativeTab(CreativeTabs.DECORATIONS);
		this.setRegistryName("block_tomato");
		this.setUnlocalizedName("block_tomato");
		this.setHardness(5.0F);
		this.setResistance(1.0F);
	}

	@Override
	public TileEntity createNewTileEntity(World worldIn, int meta) {
		return new TileEntityTomato();
	}

	@Override
    public EnumBlockRenderType getRenderType(IBlockState state)
    {
        return EnumBlockRenderType.MODEL;
    }

	@Override
	public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ)
	{
		if (!world.isRemote) {
			
			TileEntityTomato te = (TileEntityTomato) world.getTileEntity(pos);
			te.increment();
			System.out.println("Called "+te.getCounter()+" ,"+testmod.dispPos(pos));
		}
		return true;
	}

	@Override
	public void breakBlock(World world, BlockPos pos, IBlockState state) {
		super.breakBlock(world, pos, state);
	}
}


TileEntityに使うブロックはインターフェース ITileEntityProviderをimplementsする必要があります。今回は、直接は書かれていませんが親クラスのBlockContainerで ITileEntityProviderをimplementsしているので大丈夫ですね。

26行目からOverrideしているcreateNewTileEntityによってタイルエンティティを生成しているようです。
次に、33行目のgetRenderTypeなんですが、これは恐らくデフォルトでINVISIBLEになっているのでOverrideしてMODELSにしないとブロックの描写が変になりました。

38行目からの処理は右クリックしたときの処理なのですが、ここでTileEntityを利用しています。
基本的にEntityの処理はサーバーのみで行うらしく、!world.isRemoteの場合に、TileEntityを生成します。
そのあと、TileEntityTomato.classで定義したメソッドincrement();によって、TileEntityTomato内のフィールド変数counterの値を+1します。
続いて、System.outでコンソール上にカウンターの値とブロックの座標を表示させています。

dispはデバッグようにメインのクラスの中で書いているメソッドです。

	static String dispPos(BlockPos pos) {
		String str ="x="+pos.getX()+", y="+pos.getY()+", z="+pos.getZ();
		return str;
	}


最後、50行目はブロックが破壊されたときの処理です。これをかいておくことでブロックがなくなったときに、そのTileEntityがなくなったことを認識させているイメージです。



続いて、TileEntityTomatoの中身を書いていきます。

TileEntityTomato.java

package test4mod;

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

public class TileEntityTomato extends TileEntity{
	private int counter=0;

	@Override
	public void readFromNBT(NBTTagCompound compound) {
		this.counter = compound.getInteger("counter");
		super.readFromNBT(compound);
	}

	@Override
	public NBTTagCompound writeToNBT(NBTTagCompound compound) {
		compound.setInteger("counter", counter);
		return super.writeToNBT(compound);
	}

	public void increment() {
		counter++;
	}

	public int getCounter() {
		return counter;
	}
}

まずTileEntityのクラスなのでTileEntityを継承します。counterは今回保存している値です。

9行目は保存した値の読み取りです。
this.counter = compound.getInteger("counter");
引数の文字列"counter"はキーと言われていて、同じキーを使ってwrtiteされた値を読み込みます。


15行目が保存する値の書き込みです。
compound.setInteger("counter", counter);の第一引数がキー、第二引数が保存する変数です。

increment()は呼び出されるたびに、値を増やすメソッドで今回はこれを使って、右クリックされた回数を数えています。

最後に、counterがプライベートな変数なので、外から値が参照できるようにゲッターを作っています。


これらをメインのクラスに登録します。

testmod.java

package test4mod;

import net.minecraft.block.Block;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.registry.ForgeRegistries;
import net.minecraftforge.fml.common.registry.GameRegistry;

@Mod(modid = testmod.modId, name = testmod.name, version = testmod.version)
public class testmod {

	public static final String modId = "testmod";
	public static final String name = "testmod";
	public static final String version = "1.0.0";

	@Mod.Instance(modId)
	public static testmod instance;

	public static final Block TomatoBlock = new BlockTomato();
	public static final TileEntity TETomato = new TileEntityTomato();

	@Mod.EventHandler
	public void preInit(FMLPreInitializationEvent event) {
    	ForgeRegistries.ITEMS.register(new ItemBlock(TomatoBlock).setRegistryName("testmod", "block_tomato"));
    	ForgeRegistries.BLOCKS.register(TomatoBlock);

    	if(event.getSide().isClient()) {

    		ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(TomatoBlock), 0, new ModelResourceLocation(new ResourceLocation("testmod", "block_tomato"), "inventory"));

    		GameRegistry.registerTileEntity(TETomato.getClass(),new ResourceLocation("testmod","block_tomato"));
         	}
	}

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

	}

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

	}
}


TileEntityは
GameRegistry.registerTileEntity(TETomato.getClass(),new ResourceLocation("testmod","block_tomato"));
で登録しました。


実行してみます。

f:id:json_fileman:20190108025013p:plain



座標の違うブロックごとにちゃんと違う値が表示されています。
これでとりあえず、TileEntityを追加することができました!



ここからは余談ですが、TileEntityを使わないとどうなるかをテストしてみます。
BlockTomatoからTileEntityを外して以下のように変更しました。

BlockTomato_ed.java

package test4mod;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

public class BlockTomato extends Block{
	private int counter=0;

	public BlockTomato() {
		super(Material.ROCK);
		this.setCreativeTab(CreativeTabs.DECORATIONS);
		this.setRegistryName("block_tomato");
		this.setUnlocalizedName("block_tomato");
		this.setHardness(5.0F);
		this.setResistance(1.0F);
	}

	@Override
	public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ)
	{
		if (!world.isRemote) {
			counter++;
			System.out.println("Called "+this.counter+" ,"+testmod.dispPos(pos));
		}
		return true;
	}
}


結果がこちらです。

f:id:json_fileman:20190108025833p:plain

座標の異なるブロックが、同じcounterの値を共有しています。
javaの詳しい話はあまりわかりませんが、マイクラではブロックのインスタンスは1つだけ生成されてそれを使いまわしているそうです。
なので、ブロックごとに異なる状態を保ちたい場合にはTileEntityが必要になるようです。

例えば、右クリックするとなにかアイテムを消費するかわりにカウントをプラスして、カウントが一定値を超えたらなにか別のアイテムをドロップするみたいな機能はつくれそうですね。

ただ、このままだと少し寂しいので、次回はイベントリの使い、次々回にGUIの実装をできたらいいなと思います。