Меню контейнерів
ПЕРЕДУМОВИ
Вам слід спочатку прочитати про блоки-контейнери, щоб ознайомитися зі створенням блока-сутності-контейнера.
Під час відкриття контейнера, наприклад скрині, для показу його вмісту потрібні дві речі:
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
Відкриття меню
Ми хочемо мати можливість якимось чином відкрити меню, тому ми впораємося з цим за допомогою методу 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.
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
Клієнтський конструктор викликається на клієнті, коли сервер хоче, щоб він відкрив меню. Він створює порожній контейнер, який потім автоматично синхронізується з фактичним контейнером на сервері.
Серверний конструктор викликається на сервері, і оскільки він знає вміст контейнера, він може безпосередньо передати його як аргумент.
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);
}
@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
Для тла цього екрана ми просто використовуємо стандартну текстуру екрана роздавача, тому що наша скриня з ґрунту використовує той самий макет слотів. Ви також можете надати власну текстуру для 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
Після завантаження гри у вас має бути скриня з ґрунту, меню якої ви можете відкрити за допомогою ПКМ та зберігати там предмети.



