-
Notifications
You must be signed in to change notification settings - Fork 51
Getting Started with GUIs
If you prefer to watch a video tutorial. There is one available on YouTube: 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
-
NameableContainerProvider
on the BlockEntity
Later in the process we'll implement the onUse
method on the Block.
CottonCraftingController
is the abstract superclass for shared gui state. Subclass this and add whatever components you'll need to your root panel:
public class ExampleBlockController extends CottonCraftingController {
public ExampleBlockController(int syncId, PlayerInventory playerInventory, BlockContext context) {
super(RecipeType.SMELTING, syncId, playerInventory, getBlockInventory(context), getBlockPropertyDelegate(context));
WGridPanel root = new WGridPanel();
setRootPanel(root);
root.setSize(300, 200);
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<ExampleBlockController> {
public ExampleBlockScreen(ExampleBlockController container, PlayerEntity player) {
super(container, player);
}
}
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:
ContainerProviderRegistry.INSTANCE.registerFactory(ExampleBlock.ID, (syncId, id, player, buf) -> new ExampleBlockController(syncId, player.inventory, BlockContext.create(player.world, buf.readBlockPos())));
This snippet assumes that the "open gui" packet contains a BlockPos for the block you clicked on. We'll write the other side of this shortly.
Somewhere in a "client" ModInitializer so that it runs only on client startup:
ScreenProviderRegistry.INSTANCE.registerFactory(ExampleBlock.ID, (syncId, identifier, player, buf) -> new ExampleBlockScreen(new ExampleBlockController(syncId, player.inventory, BlockContext.create(player.world, buf.readBlockPos())), player));
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.
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if (world.isClient) return ActionResult.PASS;
BlockEntity be = world.getBlockEntity(pos);
if (be!=null && be instanceof ExampleBlockEntity) {
ContainerProviderRegistry.INSTANCE.openContainer(ExampleBlock.ID, player, (packetByteBuf -> packetByteBuf.writeBlockPos(pos)));
}
return ActionResult.SUCCESS;
}
The important thing here is that ID
is the same Identifier provided to both ContainerProviderRegistry
and ScreenProviderRegistry
. At the other end of the connection, the gui will be looked up and activated based on ID. Also, we're writing a blockpos into the packet buffer to help the other end construct a BlockContext, which we did above. You can write any data into the packet here, as long as your providers know how to decode it at gui startup.
At this point, you should be up and running. Try it out!