第五章:最后一步准备,1.8的Json模型、状态描述机制详解
<基于1.8 Forge的Minecraft mod制作经验分享>
1.8的所有纹理材质都需要一个Json来对其描述,这一块感觉是各大神的教程里面涉及最少最浅的,我就斗胆在这分享下我研究了多日的见解。
首先要明确几个概念:
-
textures,纹理贴图:
它实际上就是一张图片,在MC里要求必须是png格式的,并且习惯上大多是16x16像素,它非常小,像素非常低。但现在其实已经支持高像素的图片了,不过这不是重点。贴图通常都放在resources/assets/[你的mod的id]/textures下的某个子文件夹下,具体得看是运用在什么类型上,Block就放在blocks/文件夹里,Item就是items文件夹里。
-
models,模型:
这就是这篇文章的重点了,它是一个个Json的文件,里面存储了对某个物体的一系列描述。1.8的Forge里你是见不到以前的setTextureName、setBlockTextureName之类的方法的,取而代之的是,你需要向ItemModelMesher中注册一个模型。
现在是重点了,对于1.8的Json模型描述的运用,我分为Java、Json两部分来写好了。
-
Java
首先要明白,要让Forge加载某个方块/物品/实体(以下简称物体)并在MC里显示,你总得告诉它这个物体的纹理贴图是谁吧。为什么Forge不能根据物体名称来自动查找呢?因为一个物体可能有多张贴图啊,方块有不同的面(考虑草方块),物品有不同的状态(考虑弓箭拉开),实体有不同的品种(考虑羊的不同颜色)。所以,你不可能指望Forge根据名称来识别贴图,你要告诉它贴图是谁。当然,到了1.8以后,引入了模型的Json描述,你要告诉Forge的实际上是物体的Json描述文件,即model是谁,或者说有哪些model,然后在model里面再指定textures是谁。在实际代码里,物体需要被注册到GameRegistry里,注册时指定的键值即为这个物体的标识,而物体的模型,之前说过,要注册到ItemModelMesher里,所以你首先需要取得当前游戏的ItemModelMesher实例:
ItemModelMesher itemModelMesher = Minecraft.getMinecraft().getRenderItem().getItemModelMesher();
这段代码很容易理解,先取得MC实例,再取得它的物品渲染器实例,再取得模型生成器实例,即我们所需要的ItemModelMesher。但细心的童鞋可能会有个疑问,为什么是ItemModelMesher呢?如果我需要给Block指定模型也用ItemModelMesher吗?是的,虽然很奇怪,但确实是这样,你可以理解成无论是方块还是物品,拿在手上都一样。。。至于实体,等我弄清楚些再说吧,说错了误导人就不好了。当然,对于Block,需要用Item.getItemFromBlock(Block block);方法获取其对应的物品,再注册到ItemModelMesher里面。注册方法的原形:
public void register(Item item, int meta, ModelResourceLocation location);
,第一个参数是物品或从方块得到的物品(Item.getItemFromBlock(Block block);),第二个参数我也不理解,先填个0,第三个参数是关键,模型资源的位置,它的构造器是这样的:new ModelResourceLocation(String p_i46081_1_, String p_i46081_2_);
第一个String是存放路径+名字,通常是这种格式:[mod的Id]:[Json文件的名字],比如我的斗罗大陆mod的袖箭,就这么写:"douromod:sleeveBow",当然还记得我之前让你把modid写在主类的常量里了吗?所以这样更好:DouroMod.MODID + ":sleeveBow",第二个String一般填写"inventory",即告诉它去资源库里面找就好了。细心的童鞋又要发问了,不是说可能多个模型吗?这样才注册了一个model啊。是的,这个方法的重点是告诉Forge这个物体拥有模型,并会为其添加一个一般的默认的模型,即后面的ModelResourceLocation指定的那个。如果要添加多个模型,你还需要另一个方法:public static void addVariantName(Item item, String... names),可以看到它的String参数是不定的,所以你可以一次添加多个模型。但这里又有个比较恶心的地方,这个方法虽然名为add,但实际上更多的是set,也就是说如果你没有在names里面在写一遍之前register的模型,它会被直接set覆盖掉。另外,如果你注意到了我之前说的“一般的默认的模型“,可能会疑问我为何要给它加那么多关键字,因为,对于Block方块,注册时加的这个模型是无效的,你还需要再为它addVariantName,来重新申明一遍它的Item模型。别掀桌,我对此也表示很无奈和费解。
再注意一点,是的,这章注意点太多了,因为模型的坑实在太多,感觉1.8加入Json模型描述根本是个没考虑成熟就拿出来的方案。。。别跑题,要注意什么呢?要注意的是,以上所有的模型Json文件,都应该放在models/item/下面,而不论注册的物体本身是Block还是Item。仔细想想不难理解,所有的模型都注册到了ItemModelMesher里,当然都是去Item下面找。再注意,是Item,不是Items。textures文件夹里面的贴图是放在Blocks、Items下面的,而models文件夹里面的模型都是放在block、item下面的。什么?block又出来了?不是全放在Item下面吗?呵呵,童鞋,先恭喜你真的很认真,没被绕晕,然后我们一起诅咒这个倒霉的模型系统吧,当你为Block注册模型时,block下面仍然必须有一个Json描述,但它不是运用在代码里的,而是被另一个Json:blockstates/里面的状态描述Json说运用的,所以我放在下面的Json部分讲。
最后,再注意最最重要的一点,别吐槽我把这么重要的事情放在最后将,哥不是坑你,只是gang帮gang你cai治xiang治qi看lai文章不耐心的毛病~这个注意事项就是,模型的注册一定要在客户端完成!!!理由不用多说吧,服务器不需要渲染UI,更何况模型贴图。你可以跳转到ItemModelMesher类的源码,然后你将华丽丽的看到一个@SideOnly(Side.CLIENT)注解,记住,所有@SideOnly注释的类、方法,都得在相应的side调用。也就是说,你需要再ClientProxy里面进行模型的注册。这也是一个小技巧,以后不确定某个类/方法能不能在某个端用,就去找找它有没@SideOnly注解,同样的,对于你自己写的只能用于特定端的代码,也请给它打上@SideOnly。
-
Json
呼~累死了,我猜你已经明白模型这个坑有多大了吧?很不幸,咱其实还有一个相关的大坑没埋呢,那就是前文提到过的blockstates--方块状态。
按照我一贯的思路,咱需要先弄明白为什么会引入blockstates,它的意义何在?试想想MC里的木栅栏吧,当它立在不同的地方,周围有不同的连接物时,它的状态时不一样的对吧?这就是blockstates存在的意义,对方块不同的状态做描述。我们现在要先弄明白blockstates的工作机制:
首先,当我们创建了一个方块/物品的时候,我们肯定需要为其指定一个物体的id,来唯一标识这个物体。这个操作在注册物体时完成,
GameRegistry.registerBlock(block, "modid:blockId");
,后面那个blockId就是这个物体的唯一标识。通过这个标识,我们可以唯一确定一个物品。知道了怎么确定一个物体后,我们讨论blockstates的设计思路。blockstates顾名思义,是方块的状态,并且是复数,也就是方块的状态的集合。那么显然,它与模型、贴图是不同的,它对于每个方块来说是唯一的。于是,Mojang终于良心了一回:你不需要再像注册模型那样注册状态,程序运行时会自动根据你给方块设定的名称去查找。前面讨论过,id可以通过名称加前缀后缀来自动生成,所以这个名称实际上就代表了方块的id。于是当我们创建一个方块后,需要在blockstates下面同时创建一个Json文件,文件名要和你的方块设定的名称一致。方块名设定是这样的:block.setUnlocalizedName("yourBlock");,那么blockstates下面就需要同时建立yourBlock.json。一个典型的blockstates是这样的:
{ "variants": { "normal": { "model": "modid:yourBlockModel" } } }
其中,variants即变种的意思,normal表示普通,这是一个普通的方块,它只有一种普通的状态,这个状态的模型是yourBlockModel。注意这里的model没有s,它是一个模型,而不是一系列,所以单数,modid指定了要去这个id对应的mod的model里面寻找。
接着我们要在models文件夹下面,注意有s,因为它下面有一系列的模型,block、item等等。在models文件夹下面找到或新建block文件夹,注意有没s了,为什么呢?大概是为了和textures下面的block区分吧(画个圈圈诅咒某酱)。再在这个block文件夹里面新建yourBlockModel.json,一个典型的方块模型长这样:
{ "parent": "block/cube_all", "textures": { "all": "modid:blocks/yourBlockTexture" } }
parent指定了它继承自cube_all模型,你可以在MC源码的资源中找到它,它描述里一个六个面都用同一贴图的立方体模型,这个贴图指定为#all,所以我们继承它并且制定all为我们自己的贴图。注意这里并没有像之前那样用model指定类型,所以不能直接写modid:yourmodBlockTexture,而是需要写modid:blocks/,来制定去这个mod的textures/blocks文件夹里面寻找贴图,为什么要在textures里面你现在应该明白了吧?因为同之前的"model":一样,这里用"textures":指定了类型是纹理贴图,这里的s其实是惯用法,一般texture作为纹理讲都会加个s,因为纹理其实可以有很多层,会flash或ps等的应该对这点更能理解,比如后面你会看到textures下面会有"all"、"layout0"等。也就是说,这里的textures与之前model都只申明类型,不代表文件夹的名字,所以model对应models/而textures仍然对应textures/是没问题的。一定要仔细看我的这些注意事项,否则就一个文件名都能坑死你~
好了,接下来就简单了,找一张png贴图,命名为yourBlockTexture,放到textures/blocks/下面吧。
等等,我们是不是还忘了什么?是的,这只是填了一个blockstates的坑而已啊,真正的models/item/下面的Json内容还没讲呢。。。
好吧,一口气看到这里不容易了,先冲一包豆奶,抿上一口,心里默默的诅咒某酱挖了那么大一个坑,还附赠那么多恶心的小坑。
现在深呼吸,然后接着看吧:前文Java部分,我们曾经给一个物体注册并添加里一个默认的或多个包含默认在内的模型,这些模型都应该放在model/item文件夹下面。比如你再Java里为你的item申明了一个名为yourItemModel的模型,那么item文件夹里面需要一个名为yourItemModel的Json文件。一个典型的Item模型Json是这样的:
{ "parent": "builtin/generated", "textures": { "layer0": "modid:items/yourItemTextures" }, "display": { "thirdperson": { "rotation": [ -90, 0, 0 ], "translation": [ 0, 1, -3 ], "scale": [ 0.55, 0.55, 0.55 ] }, "firstperson": { "rotation": [ 0, -135, 25 ], "translation": [ 0, 4, 2 ], "scale": [ 1.7, 1.7, 1.7 ] } } }
这个Json看起来复杂说起来也简单,继承自一个名为generated的内部的描述,它的textures的layout0被指定了modid对应的mod的textures中items文件夹下的一张名为yourItemTextures的png。如果你之前一直认真看的话应该已经理解了MC的这套恶心的文件夹架构以及命名体系,否则我肯定你已经晕了。继承自generated的Json描述的是一个平面的模型,它以layout0作为贴图贴在李一个平面上。接着,display里面是对物品在第一人称视角和第三人称或其他玩家视角下的旋转角度、翻转方向和尺寸的描述。接下来,在textures/items/里面放一张同名png,ok。
细心的童鞋,对,想要弄懂1.8的这个模型大坑,你需要两个条件:1、细心,2、看我的帖子并关注我。细心的同学会发现这个Json是用来描述平面的Item的,那么对于之前的Block,它已经在models/block/文件夹里面有了一个描述,但我之前在Java部分说过,无论Block还是Item都需要在models/item/里面有一个Json描述,这是怎么回事呢?是这样的,方块在物品栏、手上其实也同时是一个Item,所以,你确实需要再models/item里面再放一个Json,来对这个方块对应的物品的模型做描述,一个典型的方块的Item的Json模型描述是这样的:
{ "parent": "modid:block/yourBlockModel", "display": { "thirdperson": { "rotation": [ 10, -45, 170 ], "translation": [ 0, 1.5, -2.75 ], "scale": [ 0.375, 0.375, 0.375 ] } } }
这里的parent比较有意思,可以看到它居然继承了我们放在models/block/下面的方块模型Json。仔细一想也不难理解,Block的Item本来就可以看作Block在某一视角上的平面投影,事实上在MC里正是如此,拿在手里的Block的Item是Block的顶点斜60度投影。因此,我们也没必要再在这里指定textures了,直接用block模型的贴图就好了。
呼,本章教程终于结束了。我说过,1.8的Json描述的机制是个巨大的坑,而且目前几乎没有比较完善的资料可寻。于是,哪怕写的不好,我也得斗胆试试这个坑的深浅,相信这些经验会对大家有所帮助的。