Table of Contents

添加屏幕

屏幕是指的图形用户界面,其类继承了 Screen,允许用户进行交互并实现一些功能。屏幕一个例子是你的模组的配置屏幕。屏幕仅在客户端存在,因此可将相应的类注解为 @Environment(EnvType.CLIENT)

你可以使用 mixin 以在现有的屏幕中,加入你的屏幕的链接。但是很多情况下,我们可以实现 Mod Menu 模组中的 ModMenuApi,这样就能够在模组菜单屏幕中,通过配置按钮来进入屏幕。本文章不会讲述如何实现 ModMenuApi

添加部件

一个屏幕应当有多个“部件”,也就是屏幕中的元素。我们在 init 方法中添加部件。

@Environment(EnvType.CLIENT)
public class TutorialScreen extends Screen {
  protected TutorialScreen() {
    // 此参数为屏幕的标题,进入屏幕中,复述功能会复述。
    super(Text.literal("我的教程屏幕"));
  }
 
 
  public ButtonWidget button1;
  public ButtonWidget button2;
 
  @Override
  protected void init() {
    button1 = ButtonWidget.builder(Text.literal("按钮 1"), button -> {
      System.out.println("你点击了按钮 1!");
    })
        .dimensions(width / 2 - 205, 20, 200, 20)
        .tooltip(Tooltip.of(Text.literal("按钮 1 的提示")))
        .build();
    button2 = ButtonWidget.builder(Text.literal("按钮 2"), button -> {
      System.out.println("你点击了按钮 2!");
    })
        .dimensions(width / 2 + 5, 20, 200, 20)
        .tooltip(Tooltip.of(Text.literal("按钮 2 的提示")))
        .build();
 
    addDrawableChild(button1);
    addDrawableChild(button2);
  }
}

“init”方法

init 方法在会以下时候下调用:

你可以使用 addDrawableaddSelectableChildaddDrawableChild 来给屏幕添加元素,区别在于:

ButtonWidget.builder(…).builder() 中,你可以用过 sizeposition 指定按钮的大小和位置,也可以直接使用 dimensionstooltip 指定按钮的提示,这个提示会在鼠标指针移到或者使用 Tab 选择到元素时渲染和复述。其中,Tooltip.of 接收两个参数,第一个用于显示,第二个(可选)用于复述。

在 1.19.3 之前的版本,没有 ButtonWidget.builder(…),这种情况下你可以直接使用 ButtonWidget 的构造方法。

可以在构造方法中实例化部件吗

有些用户可能会在构造方法中或类的初始化中实例化部件,例如,代码可能会像这样:

  public ButtonWidget button1 = ButtonWidget.builder(...).build();
  public ButtonWidget button2 = ButtonWidget.builder(...).build();
 
  @Override
  protected void init() {
    addDrawableChild(button1);
    addDrawableChild(button2);
  }

这也是可以了,其优点就是,如果部件有多个“状态”(例如 CyclingWidget 的当前选择,或者在 TextFieldWidget 中已输入的文本),这些状态在重新调整屏幕大小之后,不会被重置。但是,调整尺寸时,屏幕的 widthheight 会改变,但元素的位置和大小没有改变,因此在这种情况下,你需要在 init 方法中,更新元素的大小和位置。

  @Override
  protected void init() {
    button1.setPosition(width / 2 - 205, 20);
    button2.setPosition(width / 2 + 5, 20);
 
    addDrawableChild(button1);
    addDrawableChild(button2)
  }

注意顺序

添加大量元素之后,所有这些元素都可以渲染和选择。有些人可能不关心元素添加的顺序,因为所有这些部件都是同时渲染的。但是,如果你可能按 Tab 键来选择元素,你可能发现这些元素的选中的顺序是乱的。因此,请确保元素是以正确的顺序添加的,Tab 键的行为会依赖这一顺序。

上级屏幕

我通过另一个屏幕(如 Mod Menu 屏幕)访问了这个屏幕,但是我按下 Esc 键返回的时候,直接跳回到了主屏幕,而不是上一级屏幕,为什么?

这是因为你没有指定上一级屏幕,close 方法直接跳回了主屏幕。

添加一个 parent 参数和字段,并在 close 方法中使用它。

  private final Screen parent;
 
  protected TutorialScreen(Screen parent) {
    super(Text.literal("我的教程屏幕"));
    this.parent = parent;
  }
 
  @Override
  public void close() {
    client.setScreen(parent);
  }

添加复述

默认情况下,在复述功能启用时,屏幕的标题以及你鼠标悬浮或选中的元素的信息会被复述。如果屏幕需要额外的复述(例如渲染了一些文本但没有添加为部件),你可以覆盖 addScreenNattaionsaddElementNarrations。这些方法接收一个 NarrationBuilder,在这里你可以使用其 add 方法以添加复述消息。复述的消息分为以下部分(NarrationPart):

除了对屏幕的复述之外,你还可以自定义元素的复述,也就是覆盖元素的类的 appendNarrations 方法,屏幕复述完成后,就会复述元素。

在添加复述的方法中,使用 narrationBuilder.nextMessage() 可以在当前复述消息之后添加复述,而不是替换复述中的已存在的部分。

在有些类中,你可能需要重复的复述,而不是只复述一次。例如,在加载世界时,加载的百分比会重复复述,告诉用户当前的加载状态。你可以在 rendertick 方法中,调用 narrateScreenIfNarrationEnabled。对于更多细节,请参考 LevelLoadingScreen 的源代码。

添加文本

render 方法中,你可以调用像 textRenderer.drawdrawTextWithShadowdrawCenteredTextWithShadow 这样的方法,以在屏幕中渲染文本。

  // 对于 1.20 以下版本
  @Override
  public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
    super.render(matrices, mouseX, mouseY, delta);
    drawCenteredTextWithShadow(matrices, textRenderer, Text.literal("你必须看到我"), width / 2, height / 2, 0xffffff);
  }
 
  // 对于 1.20 及以上的版本
  @Override
  public void render(DrawContext context, int mouseX, int mouseY, float delta) {
    super.render(context, mouseX, mouseY, delta);
    context.drawCenteredTextWithShadow(textRenderer, Text.literal("你必须看到我"), width / 2, height / 2, 0xffffff);
  }

如果你担心文本太长超出了屏幕的边界。你可以使用 MultilineText 这样文本可以恰当地换行。

    final MultilineText multilineText = MultilineText.create(textRenderer, Text.literal("这个文本很长 ".repeat(20)), width - 20);
 
    // 对于 1.20 以下版本
    multilineText.drawWithShadow(matrices, 10, height / 2, 16, 0xffffff);
    // 对于 1.20 及以下的版本
    multilineText.drawWithShadow(context, 10, height / 2, 16, 0xffffff);

另一个选择是使用 TextWidgetMultilineTextWidget。这些部件默认不会被选中也不会被复述,因为其 active 字段为 false

滚动

屏幕不支持滚动,但是你可以添加支持滚动的部件。EntryListWidget 用于包含多个项并支持滚动的部件。不过,一般不是直接继承它,而是继承 AlwaysSelectedEntryListWidgetElementListWidget 更加合适,这些类实现了导航和复述。二者的区别在于:

完成之前需要检查的事情

在完成你的屏幕之后,为避免潜在的问题,请检查: