本篇教程的视频

本篇教程的源代码

GitHub地址:TutorialMod-Fuel-1.20.1

本篇教程目标

  • 学会使用Fabric API为模组添加燃料
  • (选做)初步使用Mixin,添加燃料

查看源代码

虽然我们可以直接使用Fabric API来添加燃料,但为了使用Mixin,我们还是需要查看源代码

这里我们先来找找原版的那些燃料,以COAL为例

1
public static final Item COAL = register("coal", new Item(new Item.Settings()));

这是Items类中,COAL的注册,但它和别的物品一样,没有什么特别之处

这个时候,我们就得看它用在哪里了

CTRL + ALT + F7,查找所有引用COAL的地方

排除其他的一些类之后,我们就可以锁定到AbstractFurnaceBlockEntity这个类了

AbstractFurnaceBlockEntity类

这个是游戏中熔炉高炉这些方块实体的基类,当然我们今天的重点不在方块实体上,我们看到这里面引用COAL的地方

1
2
3
4
5
6
7
public static Map<Item, Integer> createFuelTimeMap() {
Map<Item, Integer> map = Maps.<Item, Integer>newLinkedHashMap();
...
addFuel(map, Items.COAL, 1600);
...
return map;
}

我们看到一个createFuelTimeMap方法,这个方法返回一个Map,里面存储了所有燃料的物品和对应的燃烧时间

就像这里的COAL,燃烧时间是1600tick(熔炉),也就是80秒

addFuel方法

addFuel方法就是向这个Map里面添加燃料的,我们来看看这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static void addFuel(Map<Item, Integer> fuelTimes, ItemConvertible item, int fuelTime) {
Item item2 = item.asItem();
if (isNonFlammableWood(item2)) {
if (SharedConstants.isDevelopment) {
throw (IllegalStateException)Util.throwOrPause(
new IllegalStateException(
"A developer tried to explicitly make fire resistant item " + item2.getName(null).getString() + " a furnace fuel. That will not work!"
)
);
}
} else {
fuelTimes.put(item2, fuelTime);
}
}

这个方法很简单,本质就是最后的那个put方法,向Map里面添加燃料

当然如果这个物品是不能燃烧的木头物品,那么就会抛出一个异常,否则就会向Map里面添加燃料

然鹅,可惜的是,原版并没有为我们提供直接添加燃料的方法,它都已经在createFuelTimeMap方法中直接写死了

要想将我们的物品也加到这里来,就得使用Mixin

Mixin简述

Mixin的官方WikiMixin Wiki

Fabric Wiki上的Mixin教程:Fabric Wiki - Mixin

Mixin的相关内容可以参考这两个链接,其官方Wiki是对Mixin原理的解释,并不是教程

现在,我们来聊聊这个Mixin。首先看这个词,Mixin,翻译过来就是混入,其实质就是将我们的一部分代码混入到源代码之中,这是Mixin最基本的功能

那我们何时采用Mixin呢?

假设说,你在开发过程中,哎!觉得源代码里面有个方法写的挺好(也可能是你必须得用这个方法)

但是嘞,它那个方法层层叠叠,又调了本类或者其他类的方法,或者说它本类里面的私有字段……反正就是一堆,并不像我们注册物品和方块那样简单,直接搬源代码里的方法就行

要想把那个方法完整写好,就得搬其他的杂七杂八的方法,就变成了“为了一碗醋包了一盘饺子”,而且大多数情况下是不好搬的。它会拖泥带水,带一堆杂七杂八的方法或者字段

这个时候,你可能就需要考虑Mixin了

就像我们这里的这个createFuelTimeMap方法,它这里的addFuel又是另一个方法,而且是私有的……这时,我们得用Mixin

另外的情况就是你想修改游戏的一些机制,比如粒子上限;或者说拦截一些事件,或者增加某些功能,这个时候也可以考虑Mixin

最后,还是那句话,在使用Mixin之前,请确保你对你要修改的目标类及其相关的类足够熟悉,不然很容易写崩

添加燃料

燃料物品注册

我们先完成前面的操作,再来整后面的那些东西

先来注册物品

1
public static final Item ANTHRACITE = registerItems("anthracite", new Item(new Item.Settings()));

加入物品栏

别忘了将物品加入物品栏

1
entries.add(ModItems.ANTHRACITE);

使用Mixin添加燃料

Minecraft Development这个插件可以装一下,它提供了对Mixin的支持

这里我们创建一个AbstractFurnaceBlockEntityMixin

并使用@Mixin注解,指向目标类AbstractFurnaceBlockEntity

1
2
3
4
@Mixin(AbstractFurnaceBlockEntity.class)
public class AbstractFurnaceBlockEntityMixin {

}

这个类要添加到<modid>.mixins.json文件中,也可以用插件来自动添加

1
2
3
4
5
6
7
8
9
10
11
12
{
"required": true,
"package": "com.besson.tutorial.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"AbstractFurnaceBlockEntityMixin",
"ExampleMixin"
],
"injectors": {
"defaultRequire": 1
}
}

接下来我们就要这里面写一些方法

这里我们要使用@Inject注解,这个注解是注入代码到目标方法

1
2
3
4
@Inject(method = "createFuelTimeMap", at = @At("TAIL"))
private static void addFuelItems(CallbackInfoReturnable<Map<Item, Integer>> cir) {
cir.getReturnValue().put(ModItems.ANTHRACITE, 1600);
}

method是注入的目标方法,目标方法自然是我们上面提到的createFuelTimeMap

at是注入的位置,TAIL表示在目标方法最后,但在return之前注入代码

cir是回调信息,它里面包含了目标方法的一些信息,比如返回值等,这个可以利用插件来补全

cir.getReturnValue()获取返回值,在createFuelTimeMap方法中,就是那个map
这里我们直接将我们的物品加入进去,燃烧时间设置为1600tick

这样,我们就完成了一个燃料的注册

当然,这里我们是直接绕开了addFuel方法,更严谨一点应该是要调用这个方法的

不过,如果你能保证不把原版不能燃烧的东西加进去,也无伤大雅

这里的这个Mixin案例比较简单,并没有涉及到其他的复杂的东西,
我讲的也不是非常详细,Mixin这个东西,还是得自己实践,多写写也就会了

Fabric API添加燃料

那么Mixin不会写,我们就拿现成的API来写吧

Fabric的本质也就是Mixin,只是它给我们封装好方法了,用就完了

我们到模组主类的onInitialize方法中写上

1
FuelRegistry.INSTANCE.add(ModItems.ANTHRACITE, 1600);

这样,我们就完成了一个燃料的注册

很简单对吧,不过你去看它底层,本质就是Mixin,不过用API要比我们自己写简单得多

两个方法取一个你自己喜欢的来写就好了

资源文件

语言文件数据生成

1
translationBuilder.add(ModItems.ANTHRACITE, "Anthracite");

物品模型数据生成

1
itemModelGenerator.register(ModItems.ANTHRACITE, Models.GENERATED);

测试

不要忘了贴图,然后我们就可以启动游戏去测试了