PREREQUISITES
You should first read Block Containers to familiarize yourself with creating a container block entity.
When opening a container, like a chest, mainly two things are needed to display the contents of it:
- a
Screenwhich handles rendering the contents and background onto the display. - a
Menuthat handles shift-clicking logic and syncing between the server and client.
In this guide, we will create a dirt chest with a 3x3 container that can be accessed by right-clicking and opening a screen.
Creating the Block
First, we want to create a block and block entity; read more in the Block Containers guide.
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 {
private static final int CONTAINER_SIZE = 9;
private final NonNullList<ItemStack> items = NonNullList.withSize(CONTAINER_SIZE, ItemStack.EMPTY);
// ...
}1
2
3
4
5
6
2
3
4
5
6
INFO
As we want a 3x3 container, we need to set the size of items to 9.
Opening the Menu
We want to be able to open the menu somehow, so we will handle that within the useWithoutItem method:
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
Implementing MenuProvider
To add the menu functionality, we now need to implement MenuProvider in the block entity:
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
The getDisplayName method returns the name of the block, which will be displayed at the top of the screen.
Creating the Menu
createMenu wants us to return a menu, but we haven't created one for our block yet. To do this, we'll create a DirtChestMenu class which extends AbstractContainerMenu:
java
public class DirtChestMenu extends AbstractContainerMenu {
private static final int CONTAINER_SIZE = 9;
private final Container container;
// Client-side constructor
public DirtChestMenu(final int containerId, final Inventory inventory) {
this(containerId, inventory, new SimpleContainer(CONTAINER_SIZE));
}
// Server-side constructor
public DirtChestMenu(final int containerId, final Inventory inventory, final Container container) {
super(ModMenuType.DIRT_CHEST, containerId);
checkContainerSize(container, 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
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
The client-side constructor gets called on the client when the server wants it to open a menu. It creates an empty container which then gets automatically synced with the actual container on the server.
The server-side constructor is called on the server, and because it knows the contents of the container, it can directly pass it as an argument.
quickMoveStack handles shift-clicking items within the menu. This example replicates the behavior of vanilla menus like chests and dispensers.
Then we need to register the menu in a new ModMenuType class:
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
We can now set the return value of createMenu in the block entity to use our menu:
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
The createMenu method only gets called on the server, so we call the server-side constructor and pass in this (the block entity) as the container parameter.
Creating the Screen
To actually display the contents of the container on the client, we also need to create a screen for our menu. We'll make a new class which extends 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);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
For this screen's background, we're just using the default Dispenser screen texture, because our dirt chest uses the same slot layout. You could alternatively provide your own texture for CONTAINER_TEXTURE.
Because this is a screen for a menu, we also need to register it on the client with the MenuScreens#register() method:
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
Upon loading your game, you should now have a dirt chest which you can right-click to open a menu and store items in.


