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

添加复述

By default, when narration is enabled, the screen title and information of the element you hover or focus on will be narrated. If the screen requires extra narrations (for example it has some texts rendered but not added as a widget), you can override addScreenNarrations or addElementNarrations. The methods take a NarrationBuilder, in which you can use add method to add narration messages. The narration messages are divided into the following parts (NarrationPart):

  • Title: The title of the screen, which is defined in the constructor. When you enter the screen, the title will be automatically narrated.
  • Position: Telling you the position of the widget you are focusing on. In vanilla, it is: “Screen element %s out of %s”. Besides, if in a list widget, the following will also be narrated: “Selected list row %s out of %s”.
  • Hint: This refers the tooltip of the element you focus on or hovers on. For example, you may remember about the tooltip method when you create the ButtonWidget in the code above. The tooltip is narrated in this part.
  • Usage: In vanilla, the usage is: “Use mouse cursor or Tab button to select element”.

Besides the narration of the screen, you can also customize the narration of the element, by overriding appendNarrations method of the class of that element. The element is narrated after the narration of the screen.

In the method of appending narrations, using narrationBuilder.nextMessage() can append narrations after the current narrations, instead of replacing existing part of the narration.

In some cases, you want repetitive narrations, instead of narrating only once. For example, when loading a level, the percentage of loading is narrated repetitively, telling the user the current loading status. You can call narrateScreenIfNarrationEnabled in the render or tick method. For more details, you may refer to sources of LevelLoadingScreen.

添加文本

In render method, you can invoke methods like textRenderer.draw, drawTextWithShadow or drawCenteredTextWithShadow to render a text on the screen.

  @Override
  public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
    super.render(matrices, mouseX, mouseY, delta);
    drawCenteredTextWithShadow(matrices, textRenderer, Text.literal("You must see me"), width / 2, height / 2, 0xffffff);
  }

If you're concerned that the text can be pretty long and may exceed the screen limit, you can use MultilineText so it can be wrapped smartly.

    final MultilineText multilineText = MultilineText.create(textRenderer, Text.literal("The text is pretty long ".repeat(20)), width - 20);
    multilineText.drawWithShadow(matrices, 10, height / 2, 16, 0xffffff);

Another alterative is using TextWidget or MultilineTextWidget. They are by default not selectable or narratable because their active field is 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.1682324240.txt.gz · Last modified: 2023/04/24 08:17 by solidblock