本篇教程的视频

(待发布)

本篇教程的源代码

(待发布)

本篇教程目标

让自定义配方兼容REI,让REI能够显示自定义配方

介绍

上一期教程我们完成了自定义配方的编写,也让我们的方块实体能够通过配方去加工物品

那么,按照常规惯例,我们肯定希望像REIJEI这样的物品管理器能显示我们的配方

而这个就需要我们写适配代码了

注:Fabric系列的教程只讲REIJEI会在ForgeNeoForge的教程中讲,实际上两者差不多

添加依赖

那么首先我们需要添加REI的依赖,添加的教程实际上它的仓库里也有,可参见:REI

build.gradle中添加依赖

1
2
3
4
5
6
7
8
repositories {
maven { url "https://maven.shedaniel.me" }
}

dependencies {
modCompileOnly "me.shedaniel:RoughlyEnoughItems-api-fabric:12.1.785"
modRuntimeOnly "me.shedaniel:RoughlyEnoughItems-fabric:12.1.785"
}

版本的话同样还是可以找一个下载量大的版本

然后重新构建项目,下载相应的依赖,同样出现BUILD SUCCESSFUL即可

ModREIClientPlugins 类

接下来我们新建一个ModREIClientPlugins,并实现REIClientPlugin接口

1
2
3
4
5
6
7
8
9
10
11
12
public class ModREIClientPlugins implements REIClientPlugin {

@Override
public void registerCategories(CategoryRegistry registry) {
REIClientPlugin.super.registerCategories(registry);
}

@Override
public void registerDisplays(DisplayRegistry registry) {
REIClientPlugin.super.registerDisplays(registry);
}
}

顺便再重新两个方法,一个registerCategories,一个registerDisplays,待会再用

然后,我们还需要到fabric.mod.json文件中的entrypoints中添加这个类

1
2
3
4
5
6
"entrypoints": {
...
"rei_client": [
"com.besson.tutorial.compat.ModREIClientPlugins"
]
}

PortableOriginiumRigDisplay 类

新建类

接下来我们新建一个PortableOriginiumRigDisplay类,实现Display接口(注意是REI的类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PortableOriginiumRigDisplay implements Display {
@Override
public List<EntryIngredient> getInputEntries() {
return List.of();
}

@Override
public List<EntryIngredient> getOutputEntries() {
return List.of();
}

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

还需要实现三个方法

这个类是用来做展示我们的配方的,简单来说,也就是展示输入和对应的输出

添加字段

1
2
3
4
5
6
7
private final EntryIngredient input;
private final EntryIngredient output;

public PortableOriginiumRigDisplay(OreRigRecipe recipe) {
this.input = EntryIngredients.ofIngredient(recipe.getInput());
this.output = EntryIngredients.of(recipe.getOutput(null));
}

这里我们添加了两个字段,分别对应输入和输出,并创建相应的构造函数

EntryIngredient也同样是REI的类

构造函数中,用我们自定义配方的输入输出来初始化这里的输入输出

当然,记得在之前的配方中加上getInput方法,因为之前没有这个方法

1
2
3
public Ingredient getInput() {
return this.input;
}

重写 getInputEntries & getOutputEntries 方法

1
2
3
4
5
6
7
8
9
@Override
public List<EntryIngredient> getInputEntries() {
return List.of(input);
}

@Override
public List<EntryIngredient> getOutputEntries() {
return List.of(output);
}

重写这两个方法,传入我们刚刚创建的输入输出

至于另外一个方法我们等会再回过头来写

PortableOriginiumRigCategory 类

新建类

新建一个PortableOriginiumRigCategory类,实现DisplayCategory接口,泛型对应我们刚才写的PortableOriginiumRigDisplay

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PortableOriginiumRigCategory implements DisplayCategory<PortableOriginiumRigDisplay> {
@Override
public CategoryIdentifier<? extends PortableOriginiumRigDisplay> getCategoryIdentifier() {
return null;
}

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

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

另外再实现三个方法

相比较前面的Display类,这个类是用来创建一个标签页的,即在REI中我们方块实体专属的GUI界面

添加字段

1
2
3
4
5
6
7
public static final CategoryIdentifier<PortableOriginiumRigDisplay> PORTABLE_ORIGINIUM_RIG =
CategoryIdentifier.of(TutorialModRe.MOD_ID, "portable_originium_rig");

@Override
public CategoryIdentifier<? extends PortableOriginiumRigDisplay> getCategoryIdentifier() {
return PORTABLE_ORIGINIUM_RIG;
}

这里我们添加了一个字段,用来表示这个标签页ID

并重写getCategoryIdentifier方法,返回这个字段

另外,我们现在可以回过头去写PortableOriginiumRigDisplay类中的getCategoryIdentifier方法了

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

重写 getTitle & getIcon 方法

1
2
3
4
5
6
7
8
9
@Override
public Text getTitle() {
return Text.translatable("blockEntity.portable_originium_rig");
}

@Override
public Renderer getIcon() {
return EntryStacks.of(ModBlocks.PORTABLE_ORIGINIUM_RIG.asItem().getDefaultStack());
}

getTitle方法,返回这个标签页的标题,另一个就是返回这个标签页的图标

重写 setupDisplay 方法

接下来我们还需要手动重写一个setupDisplay,用于编辑这个界面上的配方的展示形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public List<Widget> setupDisplay(PortableOriginiumRigDisplay display, Rectangle bounds) {
Point startPoint = new Point(bounds.getCenterX() - 41, bounds.getCenterY() - 8);
List<Widget> widgets = new ArrayList<>();

widgets.add(Widgets.createRecipeBase(bounds));
widgets.add(Widgets.createArrow(new Point(startPoint.x + 25, startPoint.y)));

if (!display.getInputEntries().isEmpty()) {
widgets.add(Widgets.createSlot(new Point(startPoint.x, startPoint.y))
.entries(display.getInputEntries().get(0))
.markInput());
}
if (!display.getOutputEntries().isEmpty()) {
widgets.add(Widgets.createSlot(new Point(startPoint.x + 55, startPoint.y))
.entries(display.getOutputEntries().get(0))
.markOutput());
}

return widgets;
}

注意这里的PointWidget都是REI的类,导入时不要搞错

这里其实和绘制GUI的逻辑差不多,总体上来说是:

  1. 设置绘制起始点坐标(根据需要自己调整);
  2. 创建一个空的列表,用于存放绘制的元素;
  3. 绘制背景,这个createRecipeBase会创建一个默认的背景,也可以用createTexturedWidget,这样就可以使用自己的GUI贴图;
  4. 绘制箭头,这里也是用REI提供的默认箭头,如果你使用自定义GUI材质,上面有箭头的话就可以跳过了;
  5. 当输入、输出不为空时,各自添加一个Slot,并设置输入、输出标记;
  6. 最后返回这个列表,由其他方法根据这个列表绘制界面;

重写 registerCategories 方法

编写完两个类之后,我们还要回到ModREIPlugins类中重写registerCategories方法

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

这里我们写两条语句,一个是注册PortableOriginiumRigCategory类,一个是设置这个类对应的标签页工作台

重写 registerDisplays 方法

另一个方法也需要重写

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

这里我们把OreRigRecipe类中的Type字段传入,REI自己会寻找匹配的配方并填充到PortableOriginiumRigDisplay类中

这样我们的适配层就写完了,现在可以启动游戏进行测试了