Меню контейнерів 26.1.2
Посібник, що пояснює, як створити просте меню для блока-контейнера.
ПЕРЕДУМОВИ
Вам слід спочатку прочитати про блоки-контейнери, щоб ознайомитися зі створенням блока-сутності-контейнера.
Під час відкриття контейнера, наприклад скрині, для показу його вмісту потрібні дві речі:
Screen, який обробляє рендер вмісту та тла на екрані.Menu, яке обробляє логіку натискання клавіш Shift і синхронізацію між сервером і клієнтом.
У цьому посібнику ми створимо скриню з ґрунту із контейнером 3x3, до якого можна отримати доступ, натиснувши ПКМ та відкривши екран.
Створення блока
По-перше, ми хочемо створити блок і блок-сутність; читайте більше в посібнику блоків-контейнерів.
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
На додаток до звичайних методів блока-сутності, нам потрібно перевизначити метод stillValid. Цей метод буде викликатися щоразу, щоб перевірити, чи потрібно примусово вивести гравця з меню. Ми використаємо усталену реалізацію цього з ContainerHelper, яка перевіряє, чи наш блок-сутність усе ще існує та чи гравець знаходиться в межах діапазону взаємодії.
java
@Override
public boolean stillValid(Player player) {
return Container.stillValidBlockEntity(this, player);
}1
2
3
4
2
3
4
Після впровадження нашого меню воно закриється автоматично, коли гравця відштовхнуть.
Відкриття меню
Ми хочемо мати можливість якимось чином відкрити меню, тому ми впораємося з цим за допомогою методу 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 static final int SLOTS_ROWS = 3;
private static final int SLOTS_COLUMNS = 3;
private static final int SLOTS_COUNT = SLOTS_ROWS * SLOTS_COLUMNS;
private static final int CONTAINER_START = 0;
private static final int CONTAINER_END = SLOTS_COUNT;
private static final int INVENTORY_START = CONTAINER_END;
private static final int INVENTORY_END = INVENTORY_START + Inventory.INVENTORY_SIZE;
private static final int CONTAINER_START_X = 62;
private static final int CONTAINER_START_Y = 17;
private static final int INVENTORY_START_X = 8;
private static final int INVENTORY_START_Y = 84;
private final Container container;
// Client-side constructor
public DirtChestMenu(final int containerId, final Inventory inventory) {
this(containerId, inventory, new SimpleContainer(SLOTS_COUNT));
}
// Server-side constructor
public DirtChestMenu(final int containerId, final Inventory inventory, final Container container) {
super(ModMenuType.DIRT_CHEST, containerId);
checkContainerSize(container, SLOTS_COUNT);
this.container = container;
// Some containers do custom logic when opened by a player.
container.startOpen(inventory.player);
// Add the slots for our container in a 3x3 grid.
this.add3x3GridSlots();
// Add the player inventory slots.
this.addStandardInventorySlots(inventory, INVENTORY_START_X, INVENTORY_START_Y);
}
private void add3x3GridSlots() {
for (int y = 0; y < SLOTS_ROWS; y++) {
for (int x = 0; x < SLOTS_COLUMNS; x++) {
final int slot = x + y * SLOTS_COLUMNS;
this.addSlot(new Slot(
this.container,
slot,
CONTAINER_START_X + x * SLOT_SIZE,
CONTAINER_START_Y + y * SLOT_SIZE
));
}
}
}
@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_END) {
// If the clicked slot is in the container, try moving the item to the player inventory.
// When moving into the player's inventory, we iterate over slots in a reversed order; starting from the last hotbar slot to the first inventory slot.
if (!this.moveItemStackTo(stack, INVENTORY_START, INVENTORY_END, /* backwards: */ true)) {
return ItemStack.EMPTY;
}
} else {
// Else, move the item from the player inventory to the container.
if (!this.moveItemStackTo(stack, CONTAINER_START, CONTAINER_END, /* backwards: */ 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);
}
@Override
public void removed(Player player) {
super.removed(player);
this.container.stopOpen(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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
Клієнтський конструктор викликається на клієнті, коли сервер хоче, щоб він відкрив меню. Він створює порожній контейнер, який потім автоматично синхронізується з фактичним контейнером на сервері.
Серверний конструктор викликається на сервері, і оскільки він знає вміст контейнера, він може безпосередньо передати його як аргумент.
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
Після завантаження гри у вас має бути скриня з ґрунту, меню якої ви можете відкрити за допомогою ПКМ та зберігати там предмети.






