概述

通过前面部分的讲述我们知道,一个多BlockState的方块的BlockState和对应模型的映射关系是在blockstates文件夹下的名为<方块id>.json的文件中描述的。我们也可以意识到,虽然一个方块的BlockState中,属性(IProperty)只有几种,但是排列组合下来,每一种的可能情况乘在一起,有的时候数量还是十分惊人的。我们从两种方面简化这一问题:减少同一个json中需要声明的数量,和简化复杂而且过于重复的声明方式。

一方面,我们知道,原版的很多方块中,同一个方块的不同BlockState往往分配在不同的json文件中,此外,这些文件中,有的属性(IProperty)并不存在描述(比如树叶的腐败等等,这些也不需要描述,因为它们的模型和贴图没有区别)。为了做到这些,我们往往需要一种名为BlockStateMapper的机制,本节的任务之一就是讲述这种机制。

另一方面,以作者曾经拿来举例过的火焰方块,有着三千多种BlockState,虽然部分BlockState不影响绘制,但是每一种可能情况的组合在blockstates文件夹下的json中就是一行,这就导致了描述原版的火焰方块的json有着足足196个非空行。在Minecraft 1.9及以上的版本中这个问题得到了一定的改善,不过更好的办法其实是Forge提供的描述BlockState的方式,这也就是本节的另一任务。

BlockStateMapper

一个常见的BlockState json大概是这个样子的:

{
    "variants": {
        // ...
        // ...
        // ...
    }
}

Minecraft使用一个ModelResourceLocation标记上面的每一行注释替代的位置,比如之前我们创建的两个崭新的熔炉,就共有十六行,现在我们使用ModelResourceLocation的方式描述一下:

  • fmltutor:metal_furnace#burning=false,facing=east,material=gold
  • fmltutor:metal_furnace#burning=true,facing=east,material=gold
  • fmltutor:metal_furnace#burning=false,facing=north,material=gold
  • fmltutor:metal_furnace#burning=true,facing=north,material=gold
  • fmltutor:metal_furnace#burning=false,facing=south,material=gold
  • fmltutor:metal_furnace#burning=true,facing=south,material=gold
  • fmltutor:metal_furnace#burning=false,facing=west,material=gold
  • fmltutor:metal_furnace#burning=true,facing=west,material=gold
  • fmltutor:metal_furnace#burning=false,facing=east,material=iron
  • fmltutor:metal_furnace#burning=true,facing=east,material=iron
  • fmltutor:metal_furnace#burning=false,facing=north,material=iron
  • fmltutor:metal_furnace#burning=true,facing=north,material=iron
  • fmltutor:metal_furnace#burning=false,facing=south,material=iron
  • fmltutor:metal_furnace#burning=true,facing=south,material=iron
  • fmltutor:metal_furnace#burning=false,facing=west,material=iron
  • fmltutor:metal_furnace#burning=true,facing=west,material=iron

这里的fmltutor:metal_furnace这一名称是文件名和文件所在位置提供的(assets.fmltutor.blockstates.metal_furnace.json),这里我们开始讨论本节开头中我们想要解决的问题:

  • 我们希望文件名以material决定,也就是把铁炉子的json和金炉子的json分开
  • 因为诸如irongold这种名字容易引起重复(我要是再做个金属箱子咋整),所以我们想要在后面加上一个名为_furnace的后缀以区分开来(iron_furnacegold_furnace
  • 教程的下一节会使用OBJ模型,而那时名为facing的属性就不需要了,因为到时候自有解决旋转问题的办法,不过这一节还是留着

这里就是我们提到的BlockStateMapper派上用场的时候了:所有的BlockStateMapper都是一个名为IStateMapper的接口的实现。

注册BlockStateMapper的方法也很简单,我们只要在客户端的preInit阶段调用ModelLoadersetCustomStateMapper方法就可以了:

src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockLoader.java(部分):

    @SideOnly(Side.CLIENT)
    private static void registerStateMapper(Block block, IStateMapper mapper)
    {
        ModelLoader.setCustomStateMapper(block, mapper);
    }

那我们就对之前我们制作的两个炉子注册一下:

src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockLoader.java(部分):

    @SideOnly(Side.CLIENT)
    public static void registerRenders()
    {
        registerStateMapper(metalFurnace,
                new StateMap.Builder().withName(BlockMetalFurnace.MATERIAL).withSuffix("_furnace").build());

        registerRender(grassBlock);
        registerRender(metalFurnace, 0, "iron_furnace");
        registerRender(metalFurnace, 8, "gold_furnace");
    }

没错,就是这句:

src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockLoader.java(部分):

        registerStateMapper(metalFurnace,
                new StateMap.Builder().withName(BlockMetalFurnace.MATERIAL).withSuffix("_furnace").build());

这里我们用到了原版提供的一个Builder。这个Builder提供了三个方法用于满足我们之前提出的需求,然后最后再调用一次build方法产生一个IStateMapper

  • withName方法用于指定描述BlockState的json和哪个IProperty有关
  • withSuffix方法用于在指定文件名后加上后缀,以防止混淆
  • ignore方法传入一个变长参数,用于指定什么IProperty在指定模型的时候被忽略掉,不过这里我们没有需要忽略掉的参数,在下一节的时候,facing这一IProperty就要被忽略掉了

现在我们再次整理一下:

  • fmltutor:gold_furnace#burning=false,facing=east
  • fmltutor:gold_furnace#burning=true,facing=east
  • fmltutor:gold_furnace#burning=false,facing=north
  • fmltutor:gold_furnace#burning=true,facing=north
  • fmltutor:gold_furnace#burning=false,facing=south
  • fmltutor:gold_furnace#burning=true,facing=south
  • fmltutor:gold_furnace#burning=false,facing=west
  • fmltutor:gold_furnace#burning=true,facing=west
  • fmltutor:iron_furnace#burning=false,facing=east
  • fmltutor:iron_furnace#burning=true,facing=east
  • fmltutor:iron_furnace#burning=false,facing=north
  • fmltutor:iron_furnace#burning=true,facing=north
  • fmltutor:iron_furnace#burning=false,facing=south
  • fmltutor:iron_furnace#burning=true,facing=south
  • fmltutor:iron_furnace#burning=false,facing=west
  • fmltutor:iron_furnace#burning=true,facing=west

也就是说,现在BlockState对应模型的指定,已经由blockstates文件夹下的gold_furnace.jsoniron_furnace.json完全接管了,之前我们在同一个文件夹下创立的metal_furnace.json,可以直接删掉了。

现在我们再把目光投到这两个json文件上来。

Forge提供的描述BlockStates的方式

很明显,我们之前说过Minecraft原版描述BlockStates的方式,使得其对应的json文件会变得非常长,这里我们就需要Forge为我们提供的格式了。这一格式不仅可以简化描述,还支持引用第三方模型格式,也就是本节开始提到过的B3D模型和OBJ模型。

这里我们为了方便讲解,我们先直接给出简化结果,然后再进行分析:

src/main/resources/assets/fmltutor/blockstates/gold_furnace.json:

{
    "forge_marker": 1,
    "defaults": {
        "model": "orientable",
        "textures": {
            "top": "fmltutor:blocks/gold_furnace_side",
            "side": "fmltutor:blocks/gold_furnace_side"
        }
    },
    "variants": {
        "inventory": [{
             "textures": { "front": "fmltutor:blocks/gold_furnace" }
        }],
        "burning": {
            "true": { "textures": { "front": "fmltutor:blocks/gold_furnace_burning" } },
            "false": { "textures": { "front": "fmltutor:blocks/gold_furnace" } }
        },
        "facing": {
            "east": { "y": 90, "uvlock": true },
            "north": {},
            "south": { "y": 180, "uvlock": true },
            "west": { "y": 270, "uvlock": true }
        }
    }
}

src/main/resources/assets/fmltutor/blockstates/iron_furnace.json:

{
    "forge_marker": 1,
    "defaults": {
        "model": "orientable",
        "textures": {
            "top": "fmltutor:blocks/iron_furnace_side",
            "side": "fmltutor:blocks/iron_furnace_side"
        }
    },
    "variants": {
        "inventory": [{
             "textures": { "front": "fmltutor:blocks/iron_furnace" }
        }],
        "burning": {
            "true": { "textures": { "front": "fmltutor:blocks/iron_furnace_burning" } },
            "false": { "textures": { "front": "fmltutor:blocks/iron_furnace" } }
        },
        "facing": {
            "east": { "y": 90, "uvlock": true },
            "north": {},
            "south": { "y": 180, "uvlock": true },
            "west": { "y": 270, "uvlock": true }
        }
    }
}

我们这里以铁炉子为例进行分析:

首先,我们需要加上表示Forge提供的第三方json格式的标志:

    "forge_marker": 1,

然后就是这个名为default的标签了,这个标签设定了一个方块它默认的状态是什么样子的,也就是所有BlockState的共性,这里铁炉子的共性就是它们都是同一种类型的方块,其他的只是方向和贴图不同:

    "defaults": {
        "model": "orientable",
        "textures": {
            "top": "fmltutor:blocks/iron_furnace_side",
            "side": "fmltutor:blocks/iron_furnace_side"
        }
    },

现在细心的读者可能已经注意到了Forge声明模型和Minecraft原版声明模型的方式的区别。为了帮助读者回忆,我们这里从之前的json里抽两行出来:

        "burning=false,facing=north,material=iron": { "model": "fmltutor:iron_furnace" },
        "burning=true,facing=north,material=iron": { "model": "fmltutor:iron_furnace_burning" },

现在读者可能已经注意到了:原版的方式是把模型的声明,托管到models/block文件夹下的一个模型json中,这里就是assets.fmltutor.models.block.iron_furnace.json,和assets.fmltutor.models.block.iron_furnace_burning.json。而Forge声明模型的方式则是直接在blockstates文件夹下的json声明,也就是目前我们看到的那样。这样的好处显而易见,就是可以直接指定贴图等应该在models/block下的json模型里指定的东西,也就是在上面我们看到的topside两个。注意在blockstates文件夹下的json里,使用的模型位置一律不带block/路径标记,而models/block下的模型json是带的。

下面就是声明variants的环节了,我们看看Forge是怎么简化声明的。inventory部分作者稍后会讲,这里先搁置,我们看看剩下的部分:

        "burning": {
            "true": { "textures": { "front": "fmltutor:blocks/iron_furnace_burning" } },
            "false": { "textures": { "front": "fmltutor:blocks/iron_furnace" } }
        },
        "facing": {
            "east": { "y": 90, "uvlock": true },
            "north": {},
            "south": { "y": 180, "uvlock": true },
            "west": { "y": 270, "uvlock": true }
        }

现在的结论就很简单了:最后得到的模型,其实就是所有属性(IProperty)本身的简单组合。我们这里定义了burningfacing两种属性的所有情况,Forge在加载模型的时候,只需要把它们简单地组合,并和defaults提供的组合在一起就可以了,这比Minecraft原版的还是要清晰不少的。这里defaults没有声明的部分,比如front对应的模型,就可以和不同属性的组合来补齐。

现在我们的方块都在blockstates文件夹下的json里完成模型声明了,那方块对应的物品怎么办呢?这时候我们就需要提到Forge的另一个机制,也就是:在models/item文件夹下如果没有找到想要的物品模型,就会去blockstates文件夹下的同名json里去找对应的variant对应的模型。

现在我们先来回顾一下注册两个炉子对应的物品渲染的语句:

    @SideOnly(Side.CLIENT)
    public static void registerRenders()
    {
        registerStateMapper(metalFurnace,
                new StateMap.Builder().withName(BlockMetalFurnace.MATERIAL).withSuffix("_furnace").build());

        registerRender(grassBlock);
        registerRender(metalFurnace, 0, "iron_furnace");
        registerRender(metalFurnace, 8, "gold_furnace");
    }

    @SideOnly(Side.CLIENT)
    private static void registerRender(Block block, int meta, String name)
    {
        Item item = Item.getItemFromBlock(block);
        ResourceLocation location = new ResourceLocation(FMLTutor.MODID, name);
        ModelLoader.setCustomModelResourceLocation(item, meta, new ModelResourceLocation(location, "inventory"));
    }

现在我们注册了两个物品模型,分别是fmltutor:iron_furnace#inventoryfmltutor:gold_furnace#inventory,对应于我们之前添加的两种炉子对应的两种物品(一个Item的两种Metadata)。如果像之前的那样,Forge就会去找assets.fmltutor.models.item.iron_furnace.json,和assets.fmltutor.models.item.gold_furnace.json,并读取模型,事实上之前我们也是那么做的,在这两个json中我们让方块对应物品继承方块的模型,不过这里的方块模型都在blockstates文件夹下的json里声明了,我们就不能像之前一样照猫画虎。这个时候,Forge就会去blockstates文件夹下,去寻找iron_furnace.jsongold_furnace.json两个文件,并试图从名为inventoryvariant里读取模型信息了。

现在之前我们在models/itemmodels/block文件夹下的,用于描述两种炉子和两种炉子对应物品的共六个json,加上之前已经删掉的一个json共七个,可以通通删除了,因为相关的东西,已经在assets.fmltutor.blockstates.iron_furnace.json,和assets.fmltutor.blockstates.gold_furnace.json里声明了。

这一部分总共被删除的文件有如下:

  • assets.fmltutor.blockstates.metal_furnace.json
  • assets.fmltutor.models.block.gold_furnace_burning.json
  • assets.fmltutor.models.block.gold_furnace.json
  • assets.fmltutor.models.block.iron_furnace_burning.json
  • assets.fmltutor.models.block.iron_furnace.json
  • assets.fmltutor.models.item.gold_furnace.json
  • assets.fmltutor.models.item.iron_furnace.json

打开游戏试试吧~

results matching ""

    No results matching ""