Containermenüs
VORAUSSETZUNGEN
Du solltest zunächst den Abschnitt Blockcontainer lesen, um dich mit der Erstellung einer Containerblock-Entität vertraut zu machen.
Wenn man einen Container, wie zum Beispiel eine Truhe, öffnet, sind hauptsächlich zwei Dinge erforderlich, um dessen Inhalt anzuzeigen:
- ein
Screen, der für das Rendering des Inhalts und des Hintergrunds auf dem Bildschirm zuständig ist. - ein
Menu, das die Shift-Klick-Logik und die Synchronisation zwischen Server und Client handhabt.
In diesem Leitfaden erstellen wir eine Erdkiste mit einem 3x3-Container, auf den man durch einen Rechtsklick und das Öffnen einer Oberfläche zugreifen kann.
Den Block erstellen
Zunächst möchten wir einen Block und eine Block-Entität erstellen; weitere Informationen findest du im Leitfaden Blockcontainer.
java
public class DirtChestBlock extends BaseEntityBlock {
@Override
public @Nullable BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new DirtChestBlockEntity(pos, state);
}
// ...
}1
2
3
4
5
6
7
2
3
4
5
6
7
java
public class DirtChestBlockEntity extends BlockEntity implements ImplementedContainer {
public static final int CONTAINER_SIZE = 3 * 3;
private final NonNullList<ItemStack> items = NonNullList.withSize(CONTAINER_SIZE, ItemStack.EMPTY);
// ...
}1
2
3
4
5
6
2
3
4
5
6
INFO
Da wir einen 3x3-Container wollen, müssen wir die Größe der Elemente auf 9 festlegen.
Das Menü öffnen
Wir möchten das Menü irgendwie öffnen können, daher werden wir das innerhalb der Methode useWithoutItem umsetzen:
java
@Override
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) {
if (!level.isClientSide() && level.getBlockEntity(pos) instanceof DirtChestBlockEntity dirtChest) {
player.openMenu(dirtChest);
}
return InteractionResult.SUCCESS;
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Den MenuProvider implementieren
Um die Menü-Funktionalität hinzuzufügen, müssen wir nun MenuProvider in der Block-Entität implementieren:
java
public class DirtChestBlockEntity extends BlockEntity implements ImplementedContainer, MenuProvider {
@Override
@NonNull
public Component getDisplayName() {
return Component.translatable("block.example-mod.dirt_chest");
}
@Override
public @Nullable AbstractContainerMenu createMenu(int containerId, Inventory inventory, Player player) {
return null;
}
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Die Methode getDisplayName gibt den Namen des Blocks zurück, der oben auf der Oberfläche angezeigt wird.
Das Menü erstellen
createMenu erwartet, dass wir ein Menü zurückgeben, aber wir haben noch keines für unseren Block erstellt. Um dies zu tun, werden wir eine Klasse DirtChestMenu erstellen, welche von AbstractContainerMenu erbt:
java
public class DirtChestMenu extends AbstractContainerMenu {
private final Container container;
// Client-side constructor
public DirtChestMenu(final int containerId, final Inventory inventory) {
this(containerId, inventory, new SimpleContainer(DirtChestBlockEntity.CONTAINER_SIZE));
}
// Server-side constructor
public DirtChestMenu(final int containerId, final Inventory inventory, final Container container) {
super(ModMenuType.DIRT_CHEST, containerId);
checkContainerSize(container, DirtChestBlockEntity.CONTAINER_SIZE);
this.container = container;
// Some containers do custom logic when opened by a player.
container.startOpen(inventory.player);
int rows = 3;
int columns = 3;
// Add the slots for our container in a 3x3 grid.
for (int y = 0; y < rows; y++) {
for (int x = 0; x < columns; x++) {
int slot = x + y * 3;
this.addSlot(new Slot(container, slot, 62 + x * 18, 17 + y * 18));
}
}
// Add the player inventory slots.
this.addStandardInventorySlots(inventory, 8, 84);
}
@Override
public ItemStack quickMoveStack(Player player, int slotIndex) {
Slot slot = this.slots.get(slotIndex);
if (!slot.hasItem()) {
return ItemStack.EMPTY;
}
ItemStack stack = slot.getItem();
ItemStack clicked = stack.copy();
if (slotIndex < container.getContainerSize()) {
if (!this.moveItemStackTo(stack, container.getContainerSize(), this.slots.size(), true)) {
return ItemStack.EMPTY;
}
} else if (!this.moveItemStackTo(stack, 0, container.getContainerSize(), false)) {
return ItemStack.EMPTY;
}
if (stack.isEmpty()) {
slot.setByPlayer(ItemStack.EMPTY);
} else {
slot.setChanged();
}
return clicked;
}
@Override
public boolean stillValid(Player player) {
return container.stillValid(player);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
Der clientseitige Konstruktor wird auf dem Client aufgerufen, wenn der Server möchte, dass dort ein Menü geöffnet wird. Es erstellt einen leeren Container, der dann automatisch mit dem tatsächlichen Container auf dem Server synchronisiert wird.
Der serverseitige Konstruktor wird auf dem Server aufgerufen, und da er den Inhalt des Containers kennt, kann er diesen direkt als Argument übergeben.
quickMoveStack übernimmt den Shift-Klick auf Items innerhalb des Menüs. Dieses Beispiel bildet das Verhalten von Vanilla Menüs wie Truhen und Spendern nach.
Zuerst müssen wir das Menü in einer neuen Klasse ModMenuType registrieren:
java
public class ModMenuType {
public static final MenuType<DirtChestMenu> DIRT_CHEST = register("dirt_chest", DirtChestMenu::new);
public static <T extends AbstractContainerMenu> MenuType<T> register(
String name,
MenuType.MenuSupplier<T> constructor
) {
return Registry.register(BuiltInRegistries.MENU, name, new MenuType<>(constructor, FeatureFlagSet.of()));
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Wir können jetzt den Rückgabewert von createMenu in der Block-Entität setzen, um unser Menü zu verwenden:
java
@Override
public @Nullable AbstractContainerMenu createMenu(int containerId, Inventory inventory, Player player) {
return new DirtChestMenu(containerId, inventory, this);
}1
2
3
4
2
3
4
INFO
Die Methode createMenu wird nur auf dem Server aufgerufen, daher rufen wir den serverseitigen Konstruktor auf und übergeben this (die Block-Entität) als Container-Parameter.
Die Oberfläche erstellen
Um tatsächlich den Inhalt des Containers auf dem Client anzuzeigen, müssen wir außerdem eine Oberfläche für unser Menü erstellen. Wir werden eine neue Klasse erstellen, welche von AbstractContainerScreen erbt:
java
public class DirtChestScreen extends AbstractContainerScreen<DirtChestMenu> {
private static final Identifier CONTAINER_TEXTURE = Identifier.withDefaultNamespace("textures/gui/container/dispenser.png");
public DirtChestScreen(DirtChestMenu abstractContainerMenu, Inventory inventory, Component component) {
super(abstractContainerMenu, inventory, component);
}
@Override
protected void init() {
super.init();
// Center the title
this.titleLabelX = (this.imageWidth - this.font.width(this.title)) / 2;
}
@Override
protected void renderBg(GuiGraphics graphics, float delta, int mouseX, int mouseY) {
int xo = (this.width - this.imageWidth) / 2;
int yo = (this.height - this.imageHeight) / 2;
graphics.blit(RenderPipelines.GUI_TEXTURED, CONTAINER_TEXTURE, xo, yo, 0.0F, 0.0F, this.imageWidth, this.imageHeight, BACKGROUND_TEXTURE_WIDTH, BACKGROUND_TEXTURE_HEIGHT);
}
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
super.render(graphics, mouseX, mouseY, delta);
// Render item tooltips
this.renderTooltip(graphics, mouseX, mouseY);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Als Hintergrund für diese Oberfläche verwenden wir einfach die Standardtextur der Werfer-Oberfläche, da unsere Erdkiste dasselbe Slot-Layout verwendet. Du könntest alternativ deine eigene Textur für CONTAINER_TEXTURE bereitstellen.
Da es sich hierbei um eine Oberfläche für ein Menü handelt, müssen wir es außerdem auf dem Client mit der Methode MenuScreens#register() registrieren:
java
public class ExampleModScreens implements ClientModInitializer {
@Override
public void onInitializeClient() {
MenuScreens.register(ModMenuType.DIRT_CHEST, DirtChestScreen::new);
}
}1
2
3
4
5
6
2
3
4
5
6
Wenn du dein Spiel geladen hast, solltest du nun eine Erdkiste haben, die du mit einem Rechtsklick öffnen kannst, um ein Menü aufzurufen und Items darin zu verstauen.


