2D -> 3D(Mixin) 1.21 Fabric
本篇教程的视频
本篇教程的源代码
GitHub地址:TutorialMod-2D3D-Mixin-1.21
介绍
本期教程是Mixin的第二个实例,我们将仿写类似于三叉戟或者望远镜的物品,这类物品它在物品栏的GUI中显示的是二维的,但当我们拿在手上的时候是三维的
这里的三维模型,并不是指之前那种简单的加厚一层的三维模型,而是真正的三维模型,这里我们也要用到三维的建模软件,比如说BlockBench
BlockBench官网:BlockBench
BlockBench可以用来制作物品、方块、实体等等的模型,但要注意的是,Java版的模型和Bedrock(基岩)版的模型是不一样的,Java的限制得注意,比如不能超过3×3×3大小;只支持单轴旋转,并以22.5°为一个旋转单位
关于BlockBench的使用,这里就不多说了,可以自行搜索教程。本篇教程请先自行简单做一个模型,也可以到Github的源代码中下载
另外,在BlockBench的显示模式中,记得调整一下,包括GUI的模式、第一人称左右手、第三人称左右手等等
编写Mixin
查看源代码
这里我们先来看SPYGLASS(望远镜)这个物品的源代码
1 | public static final Item SPYGLASS = register("spyglass", new SpyglassItem(new Item.Settings().maxCount(1))); |
这个是注册的,我们可以看到这个物品是SpyglassItem类的实例,另外给它设置了最大堆叠数量为1
1 | public class SpyglassItem extends Item { |
但是我们去这个类中查找的时候,并没有看到什么渲染的方法,只有一些使用的方法,还有一些音效的播放
所以现在,我们得换个地方找。这里我们点击Items中的SPYGLASS进行跳转,在跳出来的预选栏中,我们可以看到用到了SPYGLASS的地方
在这一堆类中,我们只要找有关渲染(Render)的类即可。这里我们可以看到PlayerHeldItemFeatureRenderer和ItemRenderer这两个渲染类用到了SPYGLASS
这里我们要看的是ItemRenderer的东西,前者是用于渲染玩家使用望远镜的时候,把望远镜怼到脸上的这个动作的
1 | ... |
这里的代码是在renderItem方法中,我们可以看到,当renderMode是GUI、GROUND或者FIXED的时候,会调用this.models.getModelManager().getModel(SPYGLASS)来获取模型
这里传的SPYGLASS是望远镜的二维贴图的路径
也就是说,在GUI(包括物品栏等等)、GROUND(掉落物形式)或者FIXED(?这个我还真不清楚)的时候,会调用SPYGLASS的二维贴图(这个看游戏里面)
另外,这里的TRIDENT是三叉戟
1 | public BakedModel getModel(ItemStack stack, World world, LivingEntity entity, int seed) { |
其实这个类里面还有一个getModel方法也用到了SPYGLASS,但这个并不用看,顺着这些方法看一下,我们就可以看到这玩意是给renderItem方法传递BakedModel的
当然,这里它传的是SPYGLASS_IN_HAND,这个是望远镜的三维模型路径
结合上面的那一堆方法,我们可以理解为,当我们在物品栏中看到的是SPYGLASS的二维贴图,但当我们拿在手上的时候,是SPYGLASS_IN_HAND的三维模型
最后,显然易见,不论是望远镜还是三叉戟,这些东西都是硬编码的,所以我们要仿写的时候,得用Mixin
设计Mixin
首先我们来看看如何进行Mixin的编写,我们再看看源代码
1 | boolean bl = renderMode == ModelTransformationMode.GUI || renderMode == ModelTransformationMode.GROUND || renderMode == ModelTransformationMode.FIXED; |
这里的bl判断的是渲染模式
而后,对model进行了赋值,类型是BakedModel
所以我们要使用Mixin改变这里的model值,即当stack是我们的物品时,我们将model赋值为我们的物品模型
再看赋值的语句,这里的this.models是这个类中的一个私有变量,类型是ItemModels
所以我们还得先访问这个变量,才能进行下面的操作
创建ItemRenderAccessor接口
这里我们先创建一个接口,用于访问ItemRenderer中的ItemModels变量
1 |
|
这里我们使用@Accessor注解来获取ItemRenderer的models字段,这个注解在前面也讲到过,这里不再赘述
创建ItemRendererMixin类
现在,我们来创建用于修改这个变量的类
1 |
|
那么这里,我们是修改其中的一个变量,所以我们使用@ModifyVariable注解
1 |
|
这里的method是renderItem方法,因为它有一堆参数,所以后面的引用相当长
at是HEAD,表示在方法的头部进行修改,原本的这个renderItem方法是没有返回值的,这个位置随意,也可以自己测试一下
argsOnly是true,表示只有参数才会被修改
下面是我们创建的方法,因为修改的是其中的model变量,而它的变量类型是BakedModel,所以我们的方法返回值的类型是BakedModel
并且要注意,不论方法的形参有多少、是什么,BakedModel model这个形参必须是所有形参的第一个,否则会报错(即和返回值有关的形参放第一个)
后面的参数其实是renderItem方法的参数,这里我是直接搬过来的,用不到的参数其实可以不写
1 |
|
这里我们同样判断了renderMode,但这里是!=,这里我们想实现的是,物品仅在GUI模式下显示二维贴图,其他模式下均显示三维模型
然后指定我们的模型文件的路径,Identifier记得改
当然,如果不是我们的模型,就返回原本的model
不过,这里的ModItems.PLATE暂时会报错,因为我们还没有注册这个物品
物品注册
1 | public static final Item PLATE = registerItems("plate", new Item(new Item.Settings())); |
物品注册就使用最简单的方法,实例化一个Item即可,当然如果你有自己的物品类,则实例化对应的类即可
而后,假设说你已经做好了模型,放在了对应的位置,贴图也都搞好了。然而,当你进入游戏的时候,你还是会见到一个大大的黑紫块,这是为什么呢?
因为我们还有东西没写
PS:其实后面我自己再试了一遍,如果at的位置在HEAD,那么会变成黑紫块(包括物品栏和手持状态);但是at的位置在TAIL,则返回的是二维的贴图模型(包括物品栏和手持状态)(可以自己试试)
继续观察源代码
我们再来看一下ItemRenderer中的一些东西,比如说SPYGLASS_IN_HAND
1 | public static final ModelIdentifier SPYGLASS_IN_HAND = ModelIdentifier.ofInventoryVariant(Identifier.ofVanilla("spyglass_in_hand")); |
这个字段我们上面提到过,这个是望远镜的三维模型的路径,当我们点击它进行跳转的时候,我们可以看到另外一个类ModelLoader也调用了它
1 | this.loadItemModel(ItemRenderer.SPYGLASS_IN_HAND); |
看这个类的名字,我们就知道这个类是用来加载模型的,我们再看一下这个类的loadItemModel方法
这里的这个语句便是用来加载模型的
然后嘞,我们还是得通过Mixin来修改这个方法
创建ModelLoaderMixin类
1 |
|
然后我们通过@Inject注解来注入我们的语句
1 | protected abstract void loadItemModel(ModelIdentifier id); |
这里的<init>是ModelLoader的构造方法,我们在这个方法中注入我们的语句
@At中的target是loadItemModel方法的调用
ordinal是第几次调用的索引(从0开始),这里1的话就是在我们上面那个语句执行完以后再执行我们的语句
shift是在这个调用之后进行注入
而后我们这里的方法里面要调用loadItemModel方法,所以得利用@Shadow注解来引入这个方法
在这个方法中,我们调用了loadItemModel方法,传入了我们的模型路径,这里传的是物品的三维模型
在此之后,假设其他东西都设置对了,我们进入游戏就不再是黑紫块了
其他文件
3D模型文件
这里我们要在resources文件夹下的assets/tutorialmod/models/item文件夹中放入我们的模型文件plate_3d.json,这个是BlockBench导出的,不是数据生成的
1 | { |
如果你不想自己做,可以复制这里的
2D模型文件
我们可以使用数据生成来生成这个plate.json文件,也可以手写
1 | { |
如果用数据生成,则是这样写
1 | itemModelGenerator.register(ModItems.PLATE, Models.GENERATED); |
语言数据生成
1 | translationBuilder.add(ModItems.PLATE, "Plate"); |
贴图文件
这里因为我们对应的是两个模型,所以我们需要两个贴图文件,一个是plate.png,一个是plate_3d.png
后面那个也是BlockBench导出保存的
测试
在这些东西都设置完成以后,我们就可以进入游戏看看我们的物品了
如果都正确的话,那么我们的物品在物品栏中是二维的,但是拿在手上的时候是三维的,扔在地上的时候也是三维的











