本篇教程的视频

本篇教程的源代码

Github地址:TutorialMod-REI-1.21

介绍

我们在上一篇教程中自定义了一个适用于我们的方块实体的配方类型,在本期教程中,我们将使用REIRoughly Enough Items)来展示这个配方类型

REI是一个物品管理器,我们可以通过这个模组查看各类物品的配方

REI的官方Github仓库:REI Github

REI的官方Wiki也有详细的教程,我们可以在这里找到REI的API文档:REI Wiki

其他的物品管理器也可以实现类似的功能,比如JEI,这里我们就不再赘述

准备工作

在开始写我们的代码之前,我们需要先引入REI的依赖

安装的教程在其Github仓库和Wiki中都有详细的介绍

添加Maven仓库

build.gradle中的repositories添加REI的Maven仓库

1
2
3
repositories {
maven { url "https://maven.shedaniel.me" }
}

添加依赖

我们再到dependencies中添加REI的依赖

1
2
3
4
dependencies {
modCompileOnly "me.shedaniel:RoughlyEnoughItems-api-fabric:VERSION"
modRuntimeOnly "me.shedaniel:RoughlyEnoughItems-fabric:VERSION"
}

这里的版本是要我们自己填的,去哪里找呢?

我们直接到各大模组网站上去找其版本即可,比如CurseForgeModrinth

我这里做视频教程时是16.0.744,现在1.21最新的是16.0.788(写这篇教程时),不过我们这里还是拿16.0.744来做

1
2
3
4
dependencies {
modCompileOnly "me.shedaniel:RoughlyEnoughItems-api-fabric:16.0.744"
modRuntimeOnly "me.shedaniel:RoughlyEnoughItems-fabric:16.0.744"
}

然鹅,这里的写法虽然是官方的写法,但是它是有问题的,我们还需做进一步修改

1
2
3
4
dependencies {
modCompileOnly "me.shedaniel:RoughlyEnoughItems-api-fabric:16.0.744"
modImplementation "me.shedaniel:RoughlyEnoughItems-fabric:16.0.744"
}

这里我们将modRuntimeOnly改为modImplementation,这样才能在我们的IDE中正常运行!!!

不过,在1.20中,按照原先的写法没有问题,现在这个bug不知道有没有修好

重载gradle

随后我们重载gradle,等待REI下载完成

当然,gradle的这些东西还是老生常谈,下不动就挂加速器

创建ModREIClientPlugins类

下载完成以后,我们就可以开始写我们的代码了

首先,我们创建一个ModREIClientPlugins类,实现REIClientPlugin接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ModREIClientPlugins implements REIClientPlugin {
@Override
public void registerCategories(CategoryRegistry registry) {

}

@Override
public void registerDisplays(DisplayRegistry registry) {

}

@Override
public void registerScreens(ScreenRegistry registry) {

}
}

这里要重写几个方法,里面原来的东西我们就先删掉好了,其实这里的都是注册

待会我们也会来写这里的注册‘

添加entrypoints

接下来我们还得到fabric.mod.json中添加entrypoints,这里我们添加rei_cliententrypoints

1
2
3
"rei_client": [
"com.besson.tutorialmod.compat.ModREIClientPlugins"
]

这里的com.besson.tutorialmod.compat.ModREIClientPlugins是我们的ModREIClientPlugins类的路径

这样我们启动游戏时,REI也会运行,同时我们的ModREIClientPlugins也会运行

注册配方类型

接下来我们就要注册我们的配方类型了,等一下就要重写ModREIClientPlugins中的registerCategories方法了

创建PolishingMachineCategory类

我们先创建一个PolishingMachineCategory类,实现DisplayCategory<BasicDisplay>接口(REI API中的,不要导错类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class PolishingMachineCategory implements DisplayCategory<BasicDisplay> {

@Override
public CategoryIdentifier<? extends BasicDisplay> getCategoryIdentifier() {
return null;
}

@Override
public Text getTitle() {
return null;
}

@Override
public Renderer getIcon() {
return null;
}
}

这里我们要重写几个方法,这里的getCategoryIdentifier方法返回的是我们的配方类型CategoryIdentifier,这个我们等一下再写

getTitlegetIcon方法分别是我们的配方类型的标题和图标,这个显然易见

不过首先我们得先写一个GUI的材质文件的路径,因为我们待会得渲染我们的GUI

1
public static final Identifier TEXTURE = Identifier.of(TutorialMod.MOD_ID, "textures/gui/polishing_machine_gui.png");

这里的polishing_machine_gui.png是我们的GUI的材质文件,就是我们之前写方块实体的时候用的那个

而后,我们再创建一个CategoryIdentifier类型的变量

1
2
public static final CategoryIdentifier<PolishingMachineDisplay> POLISHING_MACHINE =
CategoryIdentifier.of(TutorialMod.MOD_ID, "polishing_machine");

这里的polishing_machine是我们的配方类型ID,当然也用我们自己的命名空间

当然,这里的PolishingMachineDisplay是我们待会要写的一个类,现在会报错,我们先不管,等这个类写好之后就不会有了

重写getCategoryIdentifier方法

接下来我们就要重写getCategoryIdentifier方法了

1
2
3
4
@Override
public CategoryIdentifier<? extends BasicDisplay> getCategoryIdentifier() {
return POLISHING_MACHINE;
}

返回我们上面写的POLISHING_MACHINE即可

重写getTitle方法

这个直接套用我们前面写的语言文件的Title即可

1
2
3
4
@Override
public Text getTitle() {
return Text.translatable("container.polishing_machine");
}

重写getIcon方法

这个是获取我们的GUI的图标,就和我们写物品栏一样,可以直接拿方块作为其图标

1
2
3
4
@Override
public Renderer getIcon() {
return EntryStacks.of(ModBlocks.POLISHING_MACHINE.asItem().getDefaultStack());
}

重写setupDisplay方法

这个方法是用来设置我们的Display的,即显示我们的GUI(另外我们还会再写一个Display类,那个是用于显示各种配方文件的)

也正是在这个方法里面,如果按照官方的依赖添加方法,这里的类引入就会出问题(这个类明明在那里,它就是找不到)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public List<Widget> setupDisplay(BasicDisplay display, Rectangle bounds) {
final Point startPoint = new Point(bounds.getCenterX() - 87, bounds.getCenterY() - 45);
List<Widget> widgets = new ArrayList<>();

widgets.add(Widgets.createTexturedWidget(TEXTURE, new Rectangle(startPoint.x, startPoint.y, 175, 82)));

if (!display.getInputEntries().isEmpty()) {
widgets.add(Widgets.createSlot(new Point(startPoint.x + 80, startPoint.y + 11)).entries(display.getInputEntries().get(0)));
}
if (!display.getOutputEntries().isEmpty()) {
widgets.add(Widgets.createSlot(new Point(startPoint.x + 80, startPoint.y + 59)).entries(display.getOutputEntries().get(0)));
}
return widgets;
}

首先我们要新建一个渲染的起始点startPoint,这个是我们的GUI的起始点(左上角),我们可以根据这个点来渲染我们的GUI

这个是得设置一下的,不然我们的GUI就会显示得很奇怪

在REI中,这个显示是分两层的,底层是REI的GUI,也就是外面的轮廓,包括那些框框和上面的可以点的标签一样的东西;而在此之上的一层是我们自己的GUI,所以我们GUI的位置是得自己设置的

下面的widgets是一个List,我们可以往里面添加我们GUI的各个部分,先加我们GUI的材质及其位置,后面的175,82代表GUI的终点坐标,因为我们的GUI是带玩家物品栏和快捷栏的,这没必要显示在REI上,所以就截取了一部分

再加我们的Slot,这个Slot是我们的输入和输出的位置

这两个输入和输出的位置是和我们前面在方块实体中写的是大差不差的

那么最后返回这个widgets

重写getDisplayHeight方法

这个方法是用来设置REIGUI高度的(也就是REI的GUI背景要显示的高度)

因为我们的GUIREIGUI上的,所以这里的高度要设置得合适

1
2
3
4
@Override
public int getDisplayHeight() {
return 90;
}

注册配方类型

随后我们就要在ModREIClientPlugins中注册我们的配方类型了

1
2
3
4
5
@Override
public void registerCategories(CategoryRegistry registry) {
registry.add(new PolishingMachineCategory());
registry.addWorkstations(PolishingMachineCategory.POLISHING_MACHINE, EntryStacks.of(ModBlocks.POLISHING_MACHINE));
}

这里我们先注册我们的PolishingMachineCategory,然后再注册我们的工作站Workstations),这个也就是将我们的方块实体放到REI的工作站的标签中

这样我们的配方类型就注册完成了

注册配方显示

在此之后,我们要注册一个用于显示配方的类,上面的类只负责渲染了GUI,但我们的配方还没用到啊

所以这里我们就来写这么一个东西

创建PolishingMachineDisplay类

我们先创建一个PolishingMachineDisplay类,继承BasicDisplay

1
2
3
4
5
6
7
8
9
10
11
12
public class PolishingMachineDisplay extends BasicDisplay {

public PolishingMachineDisplay(List<EntryIngredient> inputs, List<EntryIngredient> outputs) {
super(inputs, outputs);
}


@Override
public CategoryIdentifier<?> getCategoryIdentifier() {
return null;
}
}

再写一个构造方法

这里的构造方法我们还得写一个,用于注册

1
2
3
public PolishingMachineDisplay(RecipeEntry<PolishingMachineRecipe> recipe) {
super(getInputList(recipe.value()), List.of(EntryIngredient.of(EntryStacks.of(recipe.value().getResult(null)))));
}

当然,这里我们还得创建一个getInputList方法,用于获取我们的输入

创建getInputList方法

1
2
3
4
5
6
private static List<EntryIngredient> getInputList(PolishingMachineRecipe value) {
if (value.getIngredients().isEmpty()) {
return Collections.emptyList();
}
return EntryIngredients.ofIngredients(value.getIngredients());
}

这里就用上我们自己的配方类型了,将我们的配方类型传入,获取其输入和输出的东西

getIngredients()方法就是我们在配方类型中写重写的方法

重写getCategoryIdentifier方法

这个就返回我们前面写的CategoryIdentifier即可

1
2
3
4
@Override
public CategoryIdentifier<?> getCategoryIdentifier() {
return PolishingMachineCategory.POLISHING_MACHINE;
}

注册配方显示

重写registerDisplays方法,注册我们的配方显示

1
2
3
4
@Override
public void registerDisplays(DisplayRegistry registry) {
registry.registerRecipeFiller(PolishingMachineRecipe.class, PolishingMachineRecipe.Type.INSTANCE, PolishingMachineDisplay::new);
}

这里的PolishingMachineRecipe是我们的配方类型,PolishingMachineRecipe.Type.INSTANCE是我们的配方类型的TypePolishingMachineDisplay::new是我们的PolishingMachineDisplay的构造方法(我们后来再加的那个)

注册屏幕

还有一个方法是注册我们的屏幕,用上我们之前写的屏幕类
@Override
public void registerScreens(ScreenRegistry registry) {
registry.registerClickArea(screen -> new Rectangle(75, 30, 20, 30), PolishingMachineScreen.class,
PolishingMachineCategory.POLISHING_MACHINE);
}

这里的`Rectangle(75, 30, 20, 30)`是我们的`GUI`的点击区域,`PolishingMachineScreen.class`是我们的`GUI`的类

这样我们的`REI`相关的东西就写完了,接下来我们就可以进入游戏去观察一下了