概述
通过前面部分的讲述我们知道,一个多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
分开 - 因为诸如
iron
和gold
这种名字容易引起重复(我要是再做个金属箱子咋整),所以我们想要在后面加上一个名为_furnace
的后缀以区分开来(iron_furnace
和gold_furnace
) - 教程的下一节会使用OBJ模型,而那时名为
facing
的属性就不需要了,因为到时候自有解决旋转问题的办法,不过这一节还是留着
这里就是我们提到的BlockStateMapper
派上用场的时候了:所有的BlockStateMapper
都是一个名为IStateMapper
的接口的实现。
注册BlockStateMapper
的方法也很简单,我们只要在客户端的preInit
阶段调用ModelLoader
的setCustomStateMapper
方法就可以了:
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.json
和iron_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
模型里指定的东西,也就是在上面我们看到的top
和side
两个。注意在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
)本身的简单组合。我们这里定义了burning
和facing
两种属性的所有情况,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#inventory
和fmltutor: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.json
和gold_furnace.json
两个文件,并试图从名为inventory
的variant
里读取模型信息了。
现在之前我们在models/item
和models/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
打开游戏试试吧~