概述

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的两个类,ItemStackIBlockState

ItemStackIBlockState

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最常用的两个方法,可能就是withPropertycycleProperty方法了,前者用于设置BlockState,后者用于循环同一种属性对应的BlockState。不过和ItemStack不同,因为IBlockState的数量有限,所以是只读的,也就是说上述两个方法返回的引用已经变掉了。

获取和设置一个位置方块的BlockState也很简单,World类本身提供了getBlockStatesetBlockState方法,直接用就可以了。

设置多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的物品往往还调用了setMaxDamagesetHasSubTypes两个方法,前者的作用是使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类本身也没有调用作者在之前提到的setMaxDamagesetHasSubTypes两个方法,现在我们整理一下需要解决的问题:

  • 不同的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为我们提供了一个名为ItemMultiTextureItemBlock的子类,我们使用这个子类就可以了:

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类同时也在构造方法中调用了setMaxDamagesetHasSubtypes方法:

    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:

gold_furnace

src/main/resources/assets/fmltutor/textures/blocks/gold_furnace_side.png:

gold_furnace_side

src/main/resources/assets/fmltutor/textures/blocks/iron_furnace.png:

iron_furnace

src/main/resources/assets/fmltutor/textures/blocks/iron_furnace_side.png:

iron_furnace_side

上面的注册多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的排列顺序是字典序的,在这里就是按照burningfacingmaterial的顺序。

刚刚在设置方块对应的物品的模型与贴图时已经确定了两个模型,现在我们把剩下的两个模型补齐:

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:

gold_furnace_burning

src/main/resources/assets/fmltutor/textures/blocks/iron_furnace_burning.png:

iron_furnace_burning

现在打开游戏,一切模型和贴图都应该没有问题了。

一些和本节教程无关的代码

加个合成表什么的:

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;
    }

results matching ""

    No results matching ""