Mixin 1.21 Fabric
本篇教程的视频
本篇教程的源代码
GitHub地址:TutorialMod-Mixin-1.21
介绍
本篇教程就是简单讲讲Mixin的常用注解和方法,以及Mixin的基本使用方法
在后续的教程中,我们会更加深入地讲解Mixin的使用方法,并举一些例子来说明
Mixin的官方Wiki:Mixin Wiki
Fabric Wiki上的Mixin教程:Fabric Wiki - Mixin
Mixin的相关内容可以参考这两个链接,其官方Wiki是对Mixin原理的解释,并不是教程
现在,我们来聊聊这个Mixin。首先看这个词,Mixin
,翻译过来就是混入
,其实质就是将一部分代码混入到源代码之中,这是Mixin最基本的功能
那我们何时采用Mixin呢?
假设说,你在开发过程中,哎!觉得源代码里面有个方法写的挺好(也可能是你必须得用这个方法)
但是嘞,它那个方法层层叠叠,又调了本类或者其他类的方法,或者说它本类里面的私有字段……反正就是一堆,并不像我们注册物品和方块那样简单,直接搬源代码里的方法就行
要想把那个方法完整写好,就得搬其他的杂七杂八的方法,就变成了“为了一碗醋包了一盘饺子”,而且大多数情况下是不好搬的。它会拖泥带水,带一堆杂七杂八的方法或者字段
这个时候,你可能就需要考虑Mixin了。
另外的情况就是你想修改游戏的一些机制,比如粒子上限;或者说拦截一些事件,或者增加某些功能,这个时候也可以考虑Mixin
注意事项
Mixin的使用需要一定的Java基础,这是肯定的。同时也要求你对Minecraft的一些机制有一定的了解,源代码是如何实现这些功能的,这样你才能更好地使用Mixin
所以我的建议还是先研究源代码,能够看懂源代码在干什么,然后再来尝试使用Mixin
除此之外,Mixin也最好不要大量使用,除了有可能破坏游戏的平衡性,你也不能保证不和其他模组产生冲突(除非就装这么一个模组)。一些光影和优化的模组,它们是会和具有特殊渲染的模组发生冲突的
Such as OptiFine
,随着高版本的侵入性越来越强,和其他模组的冲突也越来越多,一些光影显示也会出错。所以现在我玩Forge也不再使用OptiFine了
所以兼容性也是一个值得考虑的问题,能够避免冲突是最好的
Mixin也不要写得乱七八糟,很容易让游戏崩掉,这就不是模组兼容性的问题了
常用注解
这些内容Fabric Wiki上有详细的解释,如果你想了解更多,可以查看Fabric Wiki
这里建议安装一个插件(IDEA插件)Minecraft Development
,它可以帮助你修正Mixin的一些参数,自动将相关类加到<modid>.mixin.json
文件中
这个json
文件在你的模组的resources
文件夹下,所有Mixin相关的类在这个文件中都要声明
模板文件中已经有了一个Mixin类,并且也在这个json
文件中声明了
1 | { |
@Mixin
@Mixin
注解是Mixin的核心注解,用于指定Mixin的目标类,也就是后面括号中的类
1 |
|
我们直接来看当时模板文件自带的那个Mixin类,它的目标类是MinecraftServer.class
,这个类是Minecraft服务端的主类,它的loadWorld
方法是在服务端启动时加载世界的方法
在编写这个类的时候,其实我们也可以把目标类继承的父类和实现的接口写上,这样我们在编写相关方法的时候也可以采用其父类或接口的东西
所以这个ExampleMixin
类完整来写应该是这样(不过大多数情况下不需要这样)
1 |
|
然后我们的类也是可以声明为abstract
、interface
等等,根据具体情况来决定
@Inject
@Inject
注解是注入
,用于指定注入的位置和方法。这个也是用的比较多的一个注解
1 |
|
我们还是拿模板中的例子来说
@Inject
注解的at
参数是指定注入的位置
@At
注解的参数有很多,比如说这里的HEAD
后面我们还将接触到TAIL
,RETURN
,INVOKE
等等,具体的可以查看Fabric Wiki
method
参数是指定注入的方法,这个方法是目标类的方法,我们可以在这个方法的前面或者后面插入我们的代码(具体位置是配合这前面的at
来的)
当然,后续我们也会接触到一长串的method
语句,因为如果这个方法中的参数很多,它就可能需要引用过来(接触到了再讲吧)
方法及其参数
我们接下来先看看@Inject
注解的方法及其参数
1 |
|
这个方法的参数是CallbackInfo info
,这个参数是Mixin的一个回调信息,它可以通过上面所说的插件修正补全,我们可以通过这个参数来获取一些信息
方法名的话并不是固定的,你可以随便写,但是最好还是按照规范来写,这样别人看起来也方便
另外在这个方法中,这里打印了一句话,目标类的这个方法是在服务端加载世界的时候调用的,所以这句话会在我们加载世界的时候打印出来(你可以启动游戏看一下)
@Accessor
@Accessor
注解是用于访问私有字段的,这个注解也是用的比较多的一个注解
1 |
|
这个例子是一个接口,它的目标类是GrassColors.class
,这个类是用于获取草方块颜色的类
1 | private static int[] colorMap = new int[65536]; |
在目标类中,有一个私有字段colorMap
,假设你要访问它(虽然好像访问它也没什么用),我们就可以使用@Accessor
注解
注解中的参数是这个字段的名字
这个注解有两个方法,一个是获取get
这个字段的值,一个是设置set
这个字段的值
获取这个字段的值,我们的方法名必须以get
开头,不然会报错;设置这个字段的值,我们的方法名必须以set
开头,不然也会报错
这个方法的返回值和参数类型必须和这个字段的类型一致,不然也会报错
使用
如果你要获取这个字段的值,你可以到我们主类的初始化
方法中调用这个方法,然后获取这个字段的值
1 | int[] colorMap = GrassColorsMixin.getColorMap(); |
我们这里通过日志输出这个字段的长度,这个字段的长度是65536
,这个字段是一个int
数组,长度是65536
,这个字段是用于存储草方块的颜色的
启动游戏我们可以在日志中看到这个长度
如果你要设置这个字段的值,你可以到我们主类的初始化
方法中调用这个方法,然后设置这个字段的值
1 | int[] newColorMap = new int[128]; |
这里我们设置了一个长度为128
的新的colorMap
,然后我们获取这个字段的长度,这个长度是128
,这个字段的长度就变成了128
启动游戏我们也可以在日志中看到这个长度
不过它并不会改变游戏中的草方块的颜色,因为其他的方法还会重新设置这个字段的值(等于说绕了一圈这个数组又被重新设置为65536的长度了)
当然,这也是在开发过程中需要注意的。有些时候你改一个地方看起来起作用了,实际上还有别的方法会影响,导致你的修改就没有效果。
所以在真正的开发过程中,你需要更加深入地了解这个方法,这个字段是如何被使用的,这样你才能更好地使用Mixin,考虑得周全一点
@Invoker
@Invoker
注解是用于调用私有方法的,这个注解也是用的比较多的一个注解
1 |
|
这个例子是Fabric Wiki上的一个例子,这个类是用于调用末影人传送的方法
这个类中有一个私有方法teleportTo
1 | private boolean teleportTo(double x, double y, double z) { |
这个方法是用于末影人传送的,但Fabric Wiki上并没有举出具体的使用方法,这里的话也暂时搁置一下(嗯,这是一个不太好的例子,不过我也想不出来了qwq)
总结
这篇教程比较草率,毕竟Mixin的东西很多,也较为复杂
我自己的模组开发过程中也没怎么用过Mixin,所以也不太熟悉,只是简单地了解了一下
那么在后续的教程中,我们会更加深入地讲解Mixin的使用方法