概述
Minecraft从1.8开始引入了BlockState系统,并在一定程度上取代了在1.7.10和之前的Minecraft版本中,使用Metadata标记方块状态的方式。可以说BlockState系统是Minecraft 1.8的特色,这带来了一些变化,比如方块模型从此由assets文件夹下的一些json界定,而不是硬编码的方式。当然,这也同时为从1.7.10或者更早的版本升级上来的Mod开发者带来了一些不便。
BlockState系统的核心,可能就是让不同状态或属性,但类型相似的方块共享同一个id,只不过他们的BlockState不太一样。不过,为了方便存储,方块本身在世界中的存储方式仍然是Metadata,同样,方块对应的物品的存储方式也是Metadata。然而很不幸的是,BlockState和Metadata,并不是简单的对应关系。比如十六种羊毛对应着十六种不同的BlockState和十六种不同的物品,然而蛋糕的七种状态对应的七种BlockState,却只有一种对应的物品。
那么,在我们享用BlockState系统和Metadata为标记方块和物品带来的便利的同时,我们一定要记得一点,就是不管是方块(Block
的子类)还是物品(Item
的子类),在世界中的存储方式都是共享元,也就是同一个类的实例代表同一种类型的方块,而不是世界中的每一个方块和物品,都赋予一个Block
类或者Item
类的实例。
规划
为了接下来的教程需要,我们这里新添加一种方块:金属炉,现在我们整理一下需要做什么:
- 原版的金属一共有两种,铁和金,所以我们需要做铁炉子和金炉子两种
- 炉子一共有四种朝向,还有两种状态(正在燃烧和未工作)
- 虽然上述的状态一共有足足十六种(2x4x2),但需要显示在创造模式物品栏的物品类型应该只有两种(正常的未燃烧的两种炉子)
- 不同的物品我们需要不同的名称,比如这里就需要同时拥有“铁炉”和“金炉”两种名称
- 我们希望放置炉子的时候炉子方向能够对着玩家,并且在采掘或者使用中键选取的时候获取到我们想要的物品
- 我们需要建立十六种方块状态和两种物品类型的对应关系
- 我们需要声明十六种方块状态对应的十六种模型(因为其中大部分模型可以通过旋转得到,实际上不用那么多)
- 我们需要声明两种物品类型对应的模型和方块的对应关系
在我们开工动手前,我们先了解一下反映物品的不同的Metadata和方块的不同BlockState的两个类,ItemStack
和IBlockState
。
ItemStack
和IBlockState
ItemStack
用于反应一种描述物品堆叠的方式的类,一个ItemStack
的实例,不仅包括了物品的种类(Item
类的实例)和数量,还附加存储了一个Metadata,和一个NBT标签,所以理论上,一个ItemStack
的实例可以存储的信息是无限的。
物品的Metadata可以存储一个长度为16bit的数组,比如,有的读者可能也注意到了,在药水中,每一种类型的药水,用于表示它们的ItemStack
的实例,其Metadata都不同。NBT标签会在使用这个物品的时候,展示出一些非同寻常的特性。ItemStack
的Metadata有的时候还会被用来存储一个更为常见的数值:耐久。当ItemStack
中的物品存在耐久时,Metadata同时就表现出来了物品的耐久。
IBlockState
是一个接口,实现了这个接口的类的实例存储了一个方块的种类(Block
类的实例),和一个用于表示BlockStates的,由IProperty
接口和与其对应的Comparable
接口组成的键值对列表,值的可能数量是有限的,当然,Forge对这个类进行了扩展,这一部分我们暂且按下不表。
实现了IProperty
接口的非抽象类一共有四个:PropertyBool
(对应的类为Boolean
)、PropertyEnum
(对应的类为Enum
)、PropertyDirection
(对应的类为net.minecraft.util.EnumFacing
,表示四种方向)、和PropertyInteger
(对应的类为Integer
)。这四个类分别都有名为create
的静态方法,用作创建对应的实例。当然,因为需要保证值的可能数量有限,PropertyInteger
提供的整数是有上下限的。
本次教程制作的方块,显而易见地,需要一个PropertyDirection
(表示方向),一个PropertyBool
(表示是否正在燃烧),和一个PropertyEnum
(表示对应的金属类型)。
IBlockState
最常用的两个方法,可能就是withProperty
和cycleProperty
方法了,前者用于设置BlockState,后者用于循环同一种属性对应的BlockState。不过和ItemStack
不同,因为IBlockState
的数量有限,所以是只读的,也就是说上述两个方法返回的引用已经变掉了。
获取和设置一个位置方块的BlockState也很简单,World
类本身提供了getBlockState
和setBlockState
方法,直接用就可以了。
设置多BlockState方块
我们首先在包src/main/resources/assets/fmltutor/models/block
下,和通常的创建方块方式一样,新建一个文件BlockMetalFurnace.java
:
src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockMetalFurnace.java:
package com.github.ustc_zzzz.fmltutor.block;
import com.github.ustc_zzzz.fmltutor.creativetab.CreativeTabsLoader;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
public class BlockMetalFurnace extends Block
{
public BlockMetalFurnace()
{
super(Material.iron);
this.setUnlocalizedName("metalFurnace");
this.setHardness(2.5F);
this.setStepSound(Block.soundTypeMetal);
this.setCreativeTab(CreativeTabsLoader.tabFMLTutor);
}
}
然后我们设置三种新的IProperty
,并为此建立一个新的枚举类:
src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockMetalFurnace.java(部分):
public static enum EnumMaterial implements IStringSerializable
{
IRON("iron"), GOLD("gold");
private String name;
private EnumMaterial(String material)
{
this.name = material;
}
@Override
public String getName()
{
return this.name;
}
@Override
public String toString()
{
return this.name;
}
}
public static final PropertyDirection FACING = PropertyDirection.create("facing", EnumFacing.Plane.HORIZONTAL);
public static final PropertyBool BURNING = PropertyBool.create("burning");
public static final PropertyEnum<EnumMaterial> MATERIAL = PropertyEnum.create("material", EnumMaterial.class);
作为PropertyDirection
的参数,常用的实例有EnumFacing.Plane
的实例,EnumFacing.Axis
的实例等,分别代表不同的方向种类。
作为PropertyEnum
的参数,我们需要把对应的枚举类实现IStringSerializable
,并同时覆写getName
方法和toString
方法,以返回一个适合的字符串。
现在我们声明了三种IProperty
,分别名为"facing
"、"burning
"、和"material
"。那如何告知我们的方块使用了这三种IProperty
呢?我们这里需要覆写createBlockState
方法:
src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockMetalFurnace.java(部分):
@Override
protected BlockState createBlockState()
{
return new BlockState(this, FACING, BURNING, MATERIAL);
}
同时,我们还需要声明默认的BlockState,因此我们需要在构造方法中手动设置,这里我们调用了setDefaultState
方法以设置默认的BlockState:
src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockMetalFurnace.java(部分):
public BlockMetalFurnace()
{
super(Material.iron);
this.setUnlocalizedName("metalFurnace");
this.setHardness(2.5F);
this.setStepSound(Block.soundTypeMetal);
this.setCreativeTab(CreativeTabsLoader.tabFMLTutor);
this.setDefaultState(this.blockState.getBaseState().withProperty(FACING, EnumFacing.NORTH)
.withProperty(BURNING, Boolean.FALSE).withProperty(MATERIAL, EnumMaterial.IRON));
}
目前面向北未工作的铁炉子,占据了方块的默认BlockState。
BlockState和Metadata的相互对应
刚刚我们说到,为了方便存储,方块本身在世界中的存储方式仍然是Metadata,所以,我们必须在代码中指定BlockState和Metadata的相互对应关系。不过有一点需要注意的是,和物品的Metadata不同,方块的Metadata只存储4bit,也就是16种,除外,BlockState和Metadata之间的对应关系应为一一映射。一种常见的方式是通过位运算来实现。
我们先商议一下这四位都负责存储什么信息:
- 最后两位(第0位和第1位,代表的数有0、1、2、和3)表示四个方向
- 第2位负责存储工作状态(0代表未工作,4代表正在燃烧)
- 第3位负责存储方块类型(0代表铁,8代表金)
在确定了之后,我们这里通过覆写getStateFromMeta
方法和getMetaFromState
方法来设置映射关系。
src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockMetalFurnace.java(部分):
@Override
public IBlockState getStateFromMeta(int meta)
{
EnumFacing facing = EnumFacing.getHorizontal(meta & 3);
Boolean burning = Boolean.valueOf((meta & 4) != 0);
EnumMaterial material = EnumMaterial.values()[meta >> 3];
return this.getDefaultState().withProperty(FACING, facing).withProperty(BURNING, burning).withProperty(MATERIAL,
material);
}
@Override
public int getMetaFromState(IBlockState state)
{
int facing = state.getValue(FACING).getHorizontalIndex();
int burning = state.getValue(BURNING).booleanValue() ? 4 : 0;
int material = state.getValue(MATERIAL).ordinal() << 3;
return facing | burning | material;
}
但是,细心的读者可能注意到了,很多方块有着非常巨大的BlockState数,比如火焰有三千多种BlockState,红石粉有256种BlockState,很显然,如此数目众多的BlockState是没法和Metadata一一映射的,所以它们只有部分BlockState和Metadata建立了映射关系,那么这种映射关系是如果做到的呢?
这里我们用到的就是覆写getActualState
方法了。我们先看看这个方法的声明:
public IBlockState getActualState(IBlockState state, IBlockAccess worldIn, BlockPos pos) {...}
这个方法的作用,就是把参数里的通过Metadata一一映射的部分BlockState,映射到全部的BlockState。这里我们可以使用参数里的BlockPos
类的实例,也就是方块坐标,得到相邻方块的状态,从而补全其他的BlockState。当然,这个方法得到的BlockState是通过现有的BlockState算出来的,而不是存储在世界中的。
这里最后补充一点,如果一种类型的方块有着超过16种BlockState,一般说来,我们有两种方法:
- 通过设置多个方块来实现,比如原版的木头、木板、树叶等就有两种,因为可能的BlockState已经超过了16种
- 如果该方块不会在世界上大量生成,可能通过为方块添加TileEntity的方式(下一节会讲),以存储理论上无限的数据,不过游戏中大量的TileEntity会导致游戏卡顿
多Metadata物品
如果忽略作为方块的因素存在,一般的多Metadata的物品往往需要考虑两种因素:
- 不同的Metadata往往对应不同的物品,所以它们的名称也不一样
- 在创造模式物品栏中,不同的Metadata对应的物品应当同时出现,供玩家取用
- Minecraft原版的默认行为是将不同Metadata的物品合并在一起,这显然不是我们想要的,我们总不希望煤炭和木炭合并在一起吧
当然,即便物品是代表方块的,上面两个问题仍然存在,不过其实问题会得到简化,在后面的部分我们会讲到,不过为了方便理解,我们先讨论一下作为单纯的物品是怎么实现的。这里需要引起注意的是下面两个方法:
public String getUnlocalizedName(ItemStack stack) {...}
@SideOnly(Side.CLIENT)
public void getSubItems(Item itemIn, CreativeTabs tab, List<ItemStack> subItems) {...}
覆写前者解决第一个问题,覆写后者解决第二个问题。
此外,为了解决第三个问题,多Metadata的物品往往还调用了setMaxDamage
和setHasSubTypes
两个方法,前者的作用是使Minecraft原版不把Metadata认作耐久值,后者的作用是声明物品的多个Medadata代表不同的类型,也就是说,如果我们想要制作一个囊括多个Metadata的物品,这两个方法是一定会被调用的:
this.setMaxDamage(0);
this.setHasSubtypes(true);
现在我们把目标投向代表一个方块的物品,它的解决方案是什么。
ItemBlock
和创造模式物品栏
ItemBlock
类的实例是一种特殊的物品,这种物品代表和它对应的方块。ItemBlock
定义了一些常用的方法,如物品右键方块时的行为等,使其看起来像一个拿在手里的方块。在Minecraft 1.8.9对应的Forge版本中,注册方块的时候都自动注册了方块对应的ItemBlock
。
需要注意的一点是,从Minecraft 1.9对应的Forge版本开始,所有的方块对应的ItemBlock
,都需要手动注册。因此,以下的部分代码是没有必要的,而部分代码在注册方块的时候无论如何都是必须要做的。
作为方块的默认对应物品,ItemBlock
覆写了上面提到的两个方法以把控制权转交给方块:
public String getUnlocalizedName(ItemStack stack)
{
return this.block.getUnlocalizedName();
}
@SideOnly(Side.CLIENT)
public void getSubItems(Item itemIn, CreativeTabs tab, List<ItemStack> subItems)
{
this.block.getSubBlocks(itemIn, tab, subItems);
}
第一个问题我们先暂且搁置,第二个问题我们只需要覆写方块的getSubBlocks
方法就可以了:
src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockMetalFurnace.java(部分):
@Override
@SideOnly(Side.CLIENT)
public void getSubBlocks(Item itemIn, CreativeTabs tab, List<ItemStack> list)
{
list.add(new ItemStack(itemIn, 1, 0));
list.add(new ItemStack(itemIn, 1, 8));
}
现在我们讨论ItemBlock
物品和出现在世界上方块的对应关系。
ItemBlock
覆写了物品使用事件,并在物品被表现出以方块的方式放置时调用了物品的getMetadata
方法,这个方法把物品的Metadata对应为方块的Metadata。我们先来看看这个方法是怎么实现的:
public int getMetadata(int damage)
{
return 0;
}
正如读者所见,物品的所有Metadata都映射到了方块的同一个为零的Metadata,也就是同一个BlockState。
此外,ItemBlock
类本身也没有调用作者在之前提到的setMaxDamage
和setHasSubTypes
两个方法,现在我们整理一下需要解决的问题:
- 不同的Metadata往往对应不同的物品,然而现在它们的名称,也就是
getUnlocalizedName
方法的返回值,是一样的 - 当方块被放置时,物品不同的Metadata只能对应方块的同一种BlockState
- 现在对应不同BlockState的不同Metadata的物品会在物品栏合并到一起
- 如果我们使用的不是默认的
ItemBlock
,那么我们需要显式声明并注册我们自己的ItemBlock
最后一个问题其实很好解决,我们阻止Forge注册默认的ItemBlock
并注册我们自己的就可以了。我们在BlockLoader
类中添加一些辅助代码,下面我们就会用到这个方法:
src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockLoader.java(部分):
private static void register(Block block, ItemBlock itemBlock, String name)
{
GameRegistry.registerBlock(block.setRegistryName(name), (Class<? extends ItemBlock>) null);
GameRegistry.registerItem(itemBlock.setRegistryName(name));
GameData.getBlockItemMap().put(block, itemBlock);
}
对于剩下两个问题,幸运的是,Minecraft为我们提供了一个名为ItemMultiTexture
的ItemBlock
的子类,我们使用这个子类就可以了:
src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockLoader.java(部分):
public BlockLoader(FMLPreInitializationEvent event)
{
register(grassBlock, "grass_block");
register(fluidMercury, "fluid_mercury");
register(metalFurnace, new ItemMultiTexture(metalFurnace, metalFurnace, new Function<ItemStack, String>()
{
@Override
public String apply(ItemStack input)
{
return BlockMetalFurnace.EnumMaterial.values()[input.getMetadata() >> 3].getName();
}
}), "metal_furnace");
}
前两个参数传入这个方块的实例,而关于第三个参数,如果大家接触过Java8的话,想必对Function
类有一些印象。不过这里的Function
类是第三方库(Guava)提供的而不是Java原生提供的。这里的Function
类的apply
方法和Java8的类似,这里的作用是根据不同的Metadata生成不同的unlocalizedName
后缀。这个后缀是如何被使用的呢,我们看看ItemMultiTexture
类覆写的getUnlocalizedName
方法就知道了:
public String getUnlocalizedName(ItemStack stack)
{
return super.getUnlocalizedName() + "." + (String)this.nameFunction.apply(stack);
}
现在我们去修改语言文件:
src/main/resources/assets/fmltutor/lang/en_US.lang(部分):
tile.metalFurnace.iron.name=Iron Furnace
tile.metalFurnace.gold.name=Gold Furnace
src/main/resources/assets/fmltutor/lang/zh_CN.lang(部分):
tile.metalFurnace.iron.name=铁炉
tile.metalFurnace.gold.name=金炉
此外,ItemMultiTexture
类覆写了getMetadata
方法,使得物品的Metadata可以和对应方块的Metadata一一对应,进一步和BlockState一一对应:
public int getMetadata(int damage)
{
return damage;
}
ItemMultiTexture
类同时也在构造方法中调用了setMaxDamage
和setHasSubtypes
方法:
public ItemMultiTexture(Block block, Block block2, Function<ItemStack, String> nameFunction)
{
super(block);
this.theBlock = block2;
this.nameFunction = nameFunction;
this.setMaxDamage(0);
this.setHasSubtypes(true);
}
方块的放置和获取
现在如果我们打开F3,把方块放置在地上的时候,我们会注意到一些我们想要的IProperty
并没有被设置,比如方块的朝向,我们需要手动覆写onBlockPlaced
方法,以返回一个我们满意的IBlockState
:
src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockMetalFurnace.java(部分):
@Override
public IBlockState onBlockPlaced(World worldIn, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ,
int meta, EntityLivingBase placer)
{
IBlockState origin = super.onBlockPlaced(worldIn, pos, facing, hitX, hitY, hitZ, meta, placer);
return origin.withProperty(FACING, placer.getHorizontalFacing().getOpposite());
}
然后我们解决在采掘或者使用中键选取的时候获取到的物品问题,这里我们需要覆写damageDropped
方法,该方法把方块的BlockState映射到物品的Metadata,这里所有的BlockState,都被正确地映射到了物品的两种对应的Metadata上:
src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockMetalFurnace.java(部分):
@Override
public int damageDropped(IBlockState state)
{
return state.getValue(MATERIAL).ordinal() << 3;
}
多Metadata物品的模型与渲染
我们这里首先观察一下ItemModelMesher
类的register
方法了,也就是我们之前注册物品渲染的时候使用的:
public void register(Item item, int meta, ModelResourceLocation location) {...}
第一个参数我们传入的是方块对应的物品,第三个参数表示的是模型位置,对应于models/item
文件夹下,第二个参数当时我们没有说明,不过现在读者应该会很容易看出来,指的就是对应的Metdadata,那问题简单多了,我们再写一个方法稍稍包装下:
src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockLoader.java(部分):
@SideOnly(Side.CLIENT)
private static void registerRender(Block block, int meta, String name)
{
ModelResourceLocation model = new ModelResourceLocation(name, "inventory");
ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(block), meta, model);
}
@SideOnly(Side.CLIENT)
private static void registerRender(Block block)
{
registerRender(block, 0, block.getRegistryName());
}
然后调用它:
src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockLoader.java(部分):
@SideOnly(Side.CLIENT)
public static void registerRenders()
{
registerRender(grassBlock);
registerRender(metalFurnace, 0, "iron_furnace");
registerRender(metalFurnace, 8, "gold_furnace");
}
完善对应的模型(这里的模型不是简单的方块,可以参照原版熔炉的模型进行对比):
src/main/resources/assets/fmltutor/models/item/gold_furnace.json:
{
"parent": "fmltutor:block/gold_furnace",
"display": {
"thirdperson": {
"rotation": [ 10, -45, 170 ],
"translation": [ 0, 1.5, -2.75 ],
"scale": [ 0.375, 0.375, 0.375 ]
}
}
}
src/main/resources/assets/fmltutor/models/block/gold_furnace.json:
{
"parent": "block/orientable",
"textures": {
"top": "fmltutor:blocks/gold_furnace_side",
"front": "fmltutor:blocks/gold_furnace",
"side": "fmltutor:blocks/gold_furnace_side"
}
}
src/main/resources/assets/fmltutor/models/item/iron_furnace.json:
{
"parent": "fmltutor:block/iron_furnace",
"display": {
"thirdperson": {
"rotation": [ 10, -45, 170 ],
"translation": [ 0, 1.5, -2.75 ],
"scale": [ 0.375, 0.375, 0.375 ]
}
}
}
src/main/resources/assets/fmltutor/models/block/iron_furnace.json:
{
"parent": "block/orientable",
"textures": {
"top": "fmltutor:blocks/iron_furnace_side",
"front": "fmltutor:blocks/iron_furnace",
"side": "fmltutor:blocks/iron_furnace_side"
}
}
使用的四张因为作者没有熟悉的美工,修改原版材质得到的残不忍睹的材质图:
src/main/resources/assets/fmltutor/textures/blocks/gold_furnace.png:
src/main/resources/assets/fmltutor/textures/blocks/gold_furnace_side.png:
src/main/resources/assets/fmltutor/textures/blocks/iron_furnace.png:
src/main/resources/assets/fmltutor/textures/blocks/iron_furnace_side.png:
上面的注册多Metadata物品的模型和渲染,不仅仅是方块,对于一般的物品也是适用的,所以为了方便读者参考,作者也在ItemLoader
类里添加了相应的注册方式,不过这些内容仅作为参考,和方块对应物品的渲染模型注册就没有关系了:
src/main/java/com/github/ustc_zzzz/fmltutor/item/ItemLoader.java(部分):
@SideOnly(Side.CLIENT)
private static void registerRender(Item item, int meta, String name)
{
ModelResourceLocation model = new ModelResourceLocation(name, "inventory");
ModelLoader.setCustomModelResourceLocation(item, meta, model);
}
@SideOnly(Side.CLIENT)
private static void registerRender(Item item)
{
registerRender(item, 0, item.getRegistryName());
}
多BlockState方块的模型与渲染
现在的创造模型物品栏里的方块,其模型与渲染应该是正常的了。这里我们讨论这一节剩下的最后一个问题,也就是方块本身的模型与渲染。
就方块本身的模型而言,Minecraft会去blockstates
文件夹下找和方块的注册名称相同的json
文件(这里就是assets.fmltutor.blockstates.metal_furnace.json
),从而进一步加载模型。我们新建这个文件,并填入以下内容:
src/main/resources/assets/fmltutor/blockstates/metal_furnace.json:
{
"variants": {
"burning=false,facing=east,material=gold": { "model": "fmltutor:gold_furnace", "y": 90, "uvlock": true },
"burning=true,facing=east,material=gold": { "model": "fmltutor:gold_furnace_burning", "y": 90, "uvlock": true },
"burning=false,facing=north,material=gold": { "model": "fmltutor:gold_furnace" },
"burning=true,facing=north,material=gold": { "model": "fmltutor:gold_furnace_burning" },
"burning=false,facing=south,material=gold": { "model": "fmltutor:gold_furnace", "y": 180, "uvlock": true },
"burning=true,facing=south,material=gold": { "model": "fmltutor:gold_furnace_burning", "y": 180, "uvlock": true },
"burning=false,facing=west,material=gold": { "model": "fmltutor:gold_furnace", "y": 270, "uvlock": true },
"burning=true,facing=west,material=gold": { "model": "fmltutor:gold_furnace_burning", "y": 270, "uvlock": true },
"burning=false,facing=east,material=iron": { "model": "fmltutor:iron_furnace", "y": 90, "uvlock": true },
"burning=true,facing=east,material=iron": { "model": "fmltutor:iron_furnace_burning", "y": 90, "uvlock": true },
"burning=false,facing=north,material=iron": { "model": "fmltutor:iron_furnace" },
"burning=true,facing=north,material=iron": { "model": "fmltutor:iron_furnace_burning" },
"burning=false,facing=south,material=iron": { "model": "fmltutor:iron_furnace", "y": 180, "uvlock": true },
"burning=true,facing=south,material=iron": { "model": "fmltutor:iron_furnace_burning", "y": 180, "uvlock": true },
"burning=false,facing=west,material=iron": { "model": "fmltutor:iron_furnace", "y": 270, "uvlock": true },
"burning=true,facing=west,material=iron": { "model": "fmltutor:iron_furnace_burning", "y": 270, "uvlock": true }
}
}
这里把三种IProperty
对应的十六种BlockState和四种模型进行了对应,后面的y
参数表示模型绕Y轴旋转的角度(三百六十度为一圈,同样可用的还有x
参数),unlock
参数表示贴图是否随方块的转动而转动(有兴趣的读者可以把这里的炉子和没有添加uvlock
参数的原版熔炉进行比较,通过放置方向的不同以观察贴图的变化),然后Minecraft就会去models/block
文件夹下寻找对应的模型并加载。
这里可能需要注意的一点是,Minecraft要求这里的IProperty
的排列顺序是字典序的,在这里就是按照burning
、facing
、material
的顺序。
刚刚在设置方块对应的物品的模型与贴图时已经确定了两个模型,现在我们把剩下的两个模型补齐:
src/main/resources/assets/fmltutor/models/block/gold_furnace_burning.json:
{
"parent": "block/orientable",
"textures": {
"top": "fmltutor:blocks/gold_furnace_side",
"front": "fmltutor:blocks/gold_furnace_burning",
"side": "fmltutor:blocks/gold_furnace_side"
}
}
src/main/resources/assets/fmltutor/models/block/iron_furnace_burning.json:
{
"parent": "block/orientable",
"textures": {
"top": "fmltutor:blocks/iron_furnace_side",
"front": "fmltutor:blocks/iron_furnace_burning",
"side": "fmltutor:blocks/iron_furnace_side"
}
}
新添加的两张同样惨不忍睹的贴图:
src/main/resources/assets/fmltutor/textures/blocks/gold_furnace_burning.png:
src/main/resources/assets/fmltutor/textures/blocks/iron_furnace_burning.png:
现在打开游戏,一切模型和贴图都应该没有问题了。
一些和本节教程无关的代码
加个合成表什么的:
src/main/java/com/github/ustc_zzzz/fmltutor/crafting/CraftingLoader.java(部分):
GameRegistry.addRecipe(new ShapedOreRecipe(new ItemStack(BlockLoader.metalFurnace, 1, 0), new Object[]
{
"###", "# #", "###", '#', "blockIron"
}));
GameRegistry.addRecipe(new ShapedOreRecipe(new ItemStack(BlockLoader.metalFurnace, 1, 8), new Object[]
{
"###", "# #", "###", '#', "blockGold"
}));
为了方便观察,我们设定每次点击炉子都改变其工作状态(名为"burning
"的IProperty
):
src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockMetalFurnace.java(部分):
@Override
public boolean onBlockActivated(World worldIn, BlockPos pos, IBlockState state, EntityPlayer playerIn,
EnumFacing side, float hitX, float hitY, float hitZ)
{
worldIn.setBlockState(pos, state.cycleProperty(BURNING));
return true;
}