User Tools

Site Tools


zh_cn:tutorial:screen

This is an old revision of the document!


添加屏幕

屏幕是指的图形用户界面,其类继承了 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 方法在会以下时候下调用:

  • 屏幕创建时。
  • 屏幕尺寸调整时。在调用 init 之前,所有已存在的元素都会被移除。

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

  • addDrawable:元素会被渲染,但是你不可以通过鼠标指针或键盘选择它。
  • addSelectableChild:你可以选择并与之交互,但它不会被渲染。
  • addDrawableChild:元素会被渲染,也可交互,这是最常见的情况。

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):

  • Title:屏幕的标题,会在构造函数中指定。当你进入屏幕时,这个标题会被自动复述。
  • Position:告诉你当前正在选中的部件的位置。在原版中,是“屏幕控件,第%s个,共%s个”。此外,如果是在列表中,还会复述以下内容:“已选中列表的第%s行,共%s行”。
  • Hint:这个是指的当前选中或悬浮的元素的提示。例如,你可能会记得在上面的代码中创建 ButtonWidget 时有个 tooltip 方法,这个 tooltip 就是在这一部分复述的。
  • Usage:在原版中,用法为“使用鼠标指针或Tab键选择屏幕控件”。

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

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

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

添加文本

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

  @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);
  }

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

    final MultilineText multilineText = MultilineText.create(textRenderer, Text.literal("这个文本很长 ".repeat(20)), width - 20);
    multilineText.drawWithShadow(matrices, 10, height / 2, 16, 0xffffff);

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

滚动

The screen does not support scrolling, but you can add widgets that supports scrolling. EntryListWidget is a class for widgets that contains multiple entries and supports scrolling. However, instead of directly extending it, it is more suitable to extend AlwaysSelectedEntryListWidget or ElementListWidget, which already implemented navigation and narration. The difference is:

  • AlwaysSelectedEntryListWidget refers to a widget in which you can select a row. In widgets that extends the class, you usually select one entry in the list. Some vanilla examples are biome selection screen in the buffet (single biome) world option, and the language selection screen.
  • ElementListWidget refers to a widget where each row has child elements. In widgets that extends this class, you select and interacts the elements in the rows. Like a Screen, the ElementListWidget.Entry should have zero, one, or more child elements.

完成之前需要检查的事情

After finishing your screen, in order to avoid potential issues, please check:

  • whether the screen returns to the last screen (parent screen) when you press “Esc”
  • whether these classes exist only on client (which means they will not be loaded in the dedicated server)
  • whether elements are focused in the correct order when you press “Tab” to select them
  • whether the behaviors are correct when you resize
  • whether the narrations are correct when you use “Tab” or mouse cursor to select element while narration enabled
zh_cn/tutorial/screen.1682324943.txt.gz · Last modified: 2023/04/24 08:29 by solidblock