-
Notifications
You must be signed in to change notification settings - Fork 49
Getting Started with GUIs
If you prefer to watch a video tutorial, there is one available on YouTube for Minecraft 1.15: https://www.youtube.com/watch?v=Ca769FY4pOg
- If your block has an inventory,
InventoryProvider
on the Block and/or BlockEntity (More information on how to add an inventory in the fabric docs) - If you want numeric fields,
PropertyDelegateHolder
on the Block and/or BlockEntity -
NamedScreenHandlerFactory
on the BlockEntity- An implementation of
Block.createScreenHandlerFactory
in your block class for accessing the block entity. Vanilla'sBlockWithEntity
implements this for you if you extend it.
- An implementation of
Later in the process we'll implement the onUse
method on the Block.
SyncedGuiDescription
is the abstract superclass for shared gui state. Subclass this and add whatever widgets you'll need to your root panel:
public class ExampleGuiDescription extends SyncedGuiDescription {
private static final int INVENTORY_SIZE = 1;
public ExampleGuiDescription(int syncId, PlayerInventory playerInventory, ScreenHandlerContext context) {
super(MyMod.SCREEN_HANDLER_TYPE, syncId, playerInventory, getBlockInventory(context, INVENTORY_SIZE), getBlockPropertyDelegate(context));
WGridPanel root = new WGridPanel();
setRootPanel(root);
root.setSize(300, 200);
root.setInsets(Insets.ROOT_PANEL);
WItemSlot itemSlot = WItemSlot.of(blockInventory, 0);
root.add(itemSlot, 4, 1);
root.add(this.createPlayerInventoryPanel(), 0, 3);
root.validate(this);
}
}
The last line in your constructor should be validating the root panel. This finds the right size for your GUI and registers all the itemslots with vanilla so that they sync and hover properly.
public class ExampleBlockScreen extends CottonInventoryScreen<ExampleGuiDescription> {
public ExampleBlockScreen(ExampleGuiDescription gui, PlayerEntity player, Text title) {
super(gui, player, title);
}
}
Subclassing CottonInventoryScreen
is optional but highly recommended. You could use the class as provided, but many GUI addons identify a gui by its Screen class, so creating a per-gui subclass helps sync up things like "recipes" buttons and helps other modders interact with your code.
Somewhere in a "main" ModInitializer so that it runs on both client and server:
SCREEN_HANDLER_TYPE = Registry.register(Registries.SCREEN_HANDLER, ExampleBlock.ID,
new ScreenHandlerType<>((syncId, inventory) -> new ExampleGuiDescription(syncId, inventory, ScreenHandlerContext.EMPTY),
FeatureFlags.VANILLA_FEATURES));
Somewhere in a "client" ModInitializer so that it runs only on client startup:
HandledScreens.<ExampleGuiDescription, ExampleBlockScreen>register(MyMod.SCREEN_HANDLER_TYPE, (gui, inventory, title) -> new ExampleBlockScreen(gui, inventory.player, title));
Note that you in most cases have to have the manual generics specified for the HandledScreens.register
call; the compiler has trouble with calling that method.
This is the same thing, but for the client. Again, the other side of this is coming right up. To configure the initializers, you will see an entrypoints
object in the fabric.mod.json
file. Make sure you have the entrypoints "main"
and "client"
configured to your wishes.
Vanilla has an implementation in LootableContainerBlockEntity
that most container block entities
should extend. Other block entities can do it themselves like this:
@Override
public Text getDisplayName() {
// Using the block name as the screen title
return Text.translatable(getCachedState().getBlock().getTranslationKey());
}
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory inventory, PlayerEntity player) {
return new ExampleGuiDescription(syncId, inventory, ScreenHandlerContext.create(world, pos));
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
// You need a Block.createScreenHandlerFactory implementation that delegates to the block entity,
// such as the one from BlockWithEntity
player.openHandledScreen(state.createScreenHandlerFactory(world, pos));
return ActionResult.SUCCESS;
}
At this point, you should be up and running. Try it out!