1. TileEntity
Tile 这个单词的本意是“瓷砖”或“瓦砖”。实际上这里它和 Block 是一个意思。所以它是一个给方块用的概念。 实际上 Mojang 很早(大约 1.8 的时候)就给它改名“Block Entity”了。 出于统一术语的考虑,本教程仍然主要使用“TileEntity”这个称呼,但仍会在特定情况下使用“BlockEntity”或它的中文译名“方块实体”。三种称呼在本教程中指代的是同一概念。
1.1. 为什么要有 TileEntity?
TileEntity 是一种和实体类似,可以存储一个 NBT 标签的特殊对象。
这意味着,借助 TileEntity,你可以在一个方块中存储超过 4 bits 的数据。
TileEntity 可以通过实现 ITickable
接口来获得每秒刷新 20 次的能力。
可以说,绝大部分看上去功能异常强大的方块都是基于 TileEntity 的。
1.2. 起点:声明该 Block 持有 TileEntity
public final class MyLavaFurnace extends Block {
public MyLavaFurnace() {
super(Material.ROCK, MapColor.LIGHT_BLUE);
}
@Override
public boolean hasTileEntity(IBlockState state) {
return true;
}
@Override
public TileEntity createTileEntity(World world, IBlockState state) {
return new MyLavaFurnaceEntity();
}
}
public final class MyLavaFurnaceEntity extends TileEntity {
// 暂时什么都没有
}
1.3. TileEntity 的注册
TileEntity 虽然需要注册,但它并不走 Forge 的注册表系统,反映在下面两点上:
TileEntity
本身并不是享元对象,也因此不实现IForgeRegistryEntry<TileEntity>
。TileEntity
的注册通过GameRegistry.registerTileEntity
方法完成。
一个比较常见的做法是在注册方块的时候一并注册 TileEntity。
@SubscribeEvent
public static void onBlockRegistration(RegistryEvent.Register<Block> event) {
event.getRegistry().register(new MyLavaFurnace().setRegistryName("my_mod", "lava_furnace"));
// 第一个参数是你要注册的 TileEntity 对应的 class 对象。必须 extends TileEntity。
// 第二个参数的要求和方块的注册名一致。
GameRegistry.registerTileEntity(MyLavaFurnaceEntity.class, new ResourceLocation("my_mod", "lava_furnace"));
}
1.4. 持有数据
TileEntity
最大的用途在于持有超过 4 bit 的数据,它的数据由一个专门的 NBTTagCompound
保存。
public final class MyLavaFurnaceEntity extends TileEntity {
private int progress;
private int fuel;
// 注意,虽然 TileEntity 本身是抽象类,但它并没有任何抽象方法。
// 有鉴于 Minecraft 反序列化 TileEntity 的方式,TileEntity 必须要有 public 的
// 零参构造器。这里我们省略构造器不写,即使用隐式声明的默认构造器。
@Override
public void readFromNBT(NBTTagCompound tag) {
// 从 NBT 标签中读数据,以反序列化 TileEntity
// 一定要调用 super.readFromNBT
super.readFromNBT(tag);
this.progress = tag.getInteger("Progress");
this.fuel = tag.getInteger("Fuel");
}
@Override
public NBTTagCompound writeToNBT(NBTTagCompound tag) {
// 向 NBT 标签中写数据,以序列化 TileEntity
// 在返回前务必写入必须的数据
// 一定要调用 super.writeToNBT(tag)
tag.setInteger("Progress", this.progress);
tag.setInteger("Fuel", this.fuel);
return super.writeToNBT(tag);
}
}
1.5. 我们还需要再深入些
现在我们稍微改造一些 MyLavaFurnace
,使之可以通过右击的方式接受熔岩作为燃料:
public final class MyLavaFurnace extends Block {
public MyLavaFurnace() {
super(Material.ROCK, MapColor.LIGHT_BLUE);
}
@Override
public boolean hasTileEntity(IBlockState state) {
return true;
}
@Override
public TileEntity createTileEntity(World world, IBlockState state) {
return new MyLavaFurnaceEntity();
}
@Override
public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) {
TileEntity tile = world.getTileEntity(pos);
if (tile instanceof MyLavaFurnaceEntity && !world.isRemote) {
player.setHeldItem(hand, ((MyLavaFurnaceEntity)tile).tryAcceptFuel(player.getHeldItem(hand)));
player.sendStatusMessage(new TextComponentString("Fuel: " + ((MyLavaFurnaceEntity) tile).getFuel()), true);
return true;
}
return false;
}
}
public final class MyLavaFurnaceEntity extends TileEntity {
private int progress;
private int fuel;
/**
* @param fuel 玩家当前手持的物品(见 MyLavaFurnace.onBlockActivated)
* @return 尝试消耗燃料后的剩余物品,有可能仍然是未经修改的 fuel 实例
*/
public ItemStack tryAcceptFuel(ItemStack fuel) {
if (fuel.getItem() == Items.LAVA_BUCKET) {
this.fuel += 1000;
return new ItemStack(Items.BUCKET);
} else {
return fuel;
}
}
public int getFuel() {
return this.fuel;
}
@Override
public void readFromNBT(NBTTagCompound tag) {
super.readFromNBT(tag);
this.progress = tag.getInteger("Progress");
this.fuel = tag.getInteger("Fuel");
}
@Override
public NBTTagCompound writeToNBT(NBTTagCompound tag) {
tag.setInteger("Progress", this.progress);
tag.setInteger("Fuel", this.fuel);
return super.writeToNBT(tag);
}
}