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, int seed) { World world, LivingEntity entity, |
其实这个类里面还有一个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
导出保存的
测试
在这些东西都设置完成以后,我们就可以进入游戏看看我们的物品了
如果都正确的话,那么我们的物品在物品栏中是二维的,但是拿在手上的时候是三维的,扔在地上的时候也是三维的