Меню контейнеров 26.1.2
Руководство по созданию простого меню для блока-контейнера.
ТРЕБОВАНИЯ
Сначала прочитайте Контейнеры блоков, чтобы ознакомиться с созданием блочной сущности-контейнера.
При открытии контейнера, например сундука, для отображения его содержимого в основном нужны две вещи:
Screen, который отвечает за отрисовку содержимого и фона на экране.Menu, который обрабатывает логику shift-клика и синхронизацию между сервером и клиентом.
В этом руководстве мы создадим сундук из земли с контейнером 3×3, который можно открыть правым кликом и открыть экран.
Создание блока
Сначала нам нужно создать блок и блочную сущность; подробнее см. в разделе Контейнеры блоков.
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
Открытие меню
Мы хотим иметь возможность открывать меню, поэтому обработаем это в методе useWithoutItem:
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
Реализация MenuProvider
Чтобы добавить функциональность меню, теперь нам нужно реализовать MenuProvider в блочной сущности:
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
Метод getDisplayName возвращает название блока, которое будет отображаться в верхней части экрана.
Создание меню
createMenu требует вернуть меню, но мы ещё не создали его для нашего блока. Для этого мы создадим класс DirtChestMenu, который наследуется от AbstractContainerMenu:
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.
// TODO: is this intended to use this. ?
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 < this.container.getContainerSize()) {
if (!this.moveItemStackTo(stack, this.container.getContainerSize(), this.slots.size(), true)) {
return ItemStack.EMPTY;
}
} else if (!this.moveItemStackTo(stack, 0, this.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 this.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
66
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
66
Клиентский конструктор вызывается на клиенте, когда сервер запрашивает открытие меню. Он создаёт пустой контейнер, который затем автоматически синхронизируется с реальным контейнером на сервере.
Серверный конструктор вызывается на сервере, и поскольку он знает содержимое контейнера, он может напрямую передать его в качестве аргумента.
quickMoveStack обрабатывает перемещение предметов по Shift-клику внутри меню. Этот пример повторяет поведение ванильных меню, таких как сундуки и раздатчики.
Затем нам нужно зарегистрировать меню в новом классе ModMenuType:
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
Теперь мы можем задать возвращаемое значение createMenu в блочной сущности, чтобы использовать наше меню:
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
Метод createMenu вызывается только на сервере, поэтому мы используем серверный конструктор и передаём в него this (блочную сущность) как параметр контейнера.
Создание экрана
Чтобы действительно отобразить содержимое контейнера на клиенте, нам также нужно создать экран для нашего меню. Создадим новый класс, который наследуется от AbstractContainerScreen:
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);
// Center the title
this.titleLabelX = (this.imageWidth - this.font.width(this.title)) / 2;
}
@Override
public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) {
super.extractBackground(graphics, mouseX, mouseY, delta);
graphics.blit(RenderPipelines.GUI_TEXTURED, CONTAINER_TEXTURE, this.leftPos, this.topPos, 0.0F, 0.0F, this.imageWidth, this.imageHeight, BACKGROUND_TEXTURE_WIDTH, BACKGROUND_TEXTURE_HEIGHT);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
В качестве фона этого экрана мы используем стандартную текстуру экрана раздатчика, так как наш земляной сундук использует такую же раскладку слотов. В качестве альтернативы вы можете использовать собственную текстуру для CONTAINER_TEXTURE.
Поскольку это экран для меню, нам также нужно зарегистрировать его на клиенте с помощью метода MenuScreens#register():
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
После запуска игры у вас должен появиться земляной сундук, по которому можно кликнуть правой кнопкой, чтобы открыть меню и хранить в нём предметы.






