User Tools

Site Tools


tutorial:screenhandler

This is an old revision of the document!


Creating a Container Block (DRAFT) (NEW)

In this tutorial we will create simple Storage Block similar to a dispenser. Explaining the ScreenHandler API from Fabric and Vanilla Minecraft along the way.

Let us first explain some vocabulary:

Screenhandler: A ScreenHandler is a class responsible for Synchronizing inventory contents between the Client and the Server. It can sync integer values like furnace progress aswell, which will be showed in the next Tutorial.

Screen: The Screen class only exists on the client and will render the background and other decorations for your ScreenHandler on the Screen

Further Reading

This and the following Tutorials are heavily inspired by the ScreenHandlerAPI ExampleMod. It can be found here: ExampleMod on Github The documentation for the Fabric ScreenHandlerAPI can be found here: ##javadoc link here##

Block and BlockEntity classes

First we need to create the Block and its BlockEntity

BiggerChestBlock.java
  1. public class BoxBlock extends BlockWithEntity {
  2. protected BoxBlock(Settings settings) {
  3. super(settings);
  4. }
  5.  
  6. @Override
  7. public BlockEntity createBlockEntity(BlockView world) {
  8. return new BoxBlockEntity();
  9. }
  10.  
  11. @Override
  12. public BlockRenderType getRenderType(BlockState state) {
  13. //With inheriting from BlockWithEntity this defaults to INVISIBLE, so we need to change that!
  14. return BlockRenderType.MODEL;
  15. }
  16.  
  17. @Override
  18. public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
  19. if (!world.isClient) {
  20. //This will call the createScreenHandlerFactory method from blockWithEntity, which will return our blockEntity casted
  21. //to a namedScreenHandlerFactory
  22. NamedScreenHandlerFactory screenHandlerFactory = state.createScreenHandlerFactory(world, pos);
  23.  
  24. if (screenHandlerFactory != null) {
  25. //With this call the server will request the client to open the appropriate Screenhandler
  26. player.openHandledScreen(screenHandlerFactory);
  27. }
  28. }
  29. return ActionResult.SUCCESS;
  30. }
  31.  
  32.  
  33. //This method will drop all items onto the ground when the block is broken
  34. @Override
  35. public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
  36. if (state.getBlock() != newState.getBlock()) {
  37. BlockEntity blockEntity = world.getBlockEntity(pos);
  38. if (blockEntity instanceof BoxBlockEntity) {
  39. ItemScatterer.spawn(world, pos, (BoxBlockEntity)blockEntity);
  40. // update comparators
  41. world.updateComparators(pos,this);
  42. }
  43. super.onStateReplaced(state, world, pos, newState, moved);
  44. }
  45. }
  46.  
  47. @Override
  48. public boolean hasComparatorOutput(BlockState state) {
  49. return true;
  50. }
  51.  
  52. @Override
  53. public int getComparatorOutput(BlockState state, World world, BlockPos pos) {
  54. return ScreenHandler.calculateComparatorOutput(world.getBlockEntity(pos))
  55. }
  56. }

Now we will create our BlockEntity, it will use the ImplementedInventory Interface from the Inventory Tutorial

BoxBlockEntity.java
  1. public class BoxBlockEntity extends BlockEntity implements NamedScreenHandlerFactory, ImplementedInventory {
  2. private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(9, ItemStack.EMPTY);
  3.  
  4. public BoxBlockEntity() {
  5. super(ExampleMod.BOX_BLOCK_ENTITY);
  6. }
  7.  
  8.  
  9. //From the ImplementedInventory Interface
  10.  
  11. @Override
  12. public DefaultedList<ItemStack> getItems() {
  13. return inventory;
  14.  
  15. }
  16.  
  17. //These Methods are from the NamedScreenHandlerFactory Interface
  18. //createMenu creates the ScreenHandler itself
  19. //getDisplayName will Provide its name which is normally shown at the top
  20.  
  21. @Override
  22. public ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {
  23. //We provide *this* to the screenHandler as our class Implements Inventory
  24. //Only the Server has the Inventory at the start, this will be synced to the client in the ScreenHandler
  25. return new BoxScreenHandler(syncId, playerInventory, this);
  26. }
  27.  
  28. @Override
  29. public Text getDisplayName() {
  30. return new TranslatableText(getCachedState().getBlock().getTranslationKey());
  31. }
  32.  
  33. @Override
  34. public void fromTag(BlockState state, CompoundTag tag) {
  35. super.fromTag(state, tag);
  36. inventory = DefaultedList.ofSize(invsize, ItemStack.EMPTY);
  37. Inventories.fromTag(tag, this.inventory);
  38. }
  39.  
  40. @Override
  41. public CompoundTag toTag(CompoundTag tag) {
  42. super.toTag(tag);
  43. Inventories.toTag(tag, this.inventory);
  44. return tag;
  45. }
  46. }
ExampleMod.java
  1. public class ExampleMod implements ModInitializer {
  2.  
  3. public static final Block BOX_BLOCK;
  4. public static final BlockItem BOX_BLOCK_ITEM;
  5. public static final BlockEntityType<BoxBlockEntity> BOX_BLOCK_ENTITY;
  6. public static final ScreenHandlerType<BoxScreenHandler> BOX_SCREEN_HANDLER;
  7.  
  8. public static final String MOD_ID = "testmod";
  9. // a public identifier for multiple parts of our bigger chest
  10. public static final Identifier BOX = new Identifier(MOD_ID, "bigger_chest_block");
  11.  
  12. static {
  13. BOX_BLOCK = Registry.register(Registry.BLOCK, BOX, new BoxBlock(FabricBlockSettings.copyOf(Blocks.CHEST)));
  14. BOX_BLOCK_ITEM = Registry.register(Registry.ITEM, BOX, new BlockItem(BOX_BLOCK, new Item.Settings().group(ItemGroup.MISC)));
  15.  
  16. //The parameter of build at the very end is always null, do not worry about it
  17. BOX_BLOCK_ENTITY = Registry.register(Registry.BLOCK_ENTITY_TYPE, BOX, BlockEntityType.Builder.create(BoxBlockEntity::new, BOX_BLOCK).build(null));
  18.  
  19. BOX_SCREEN_HANDLER = ScreenHandlerRegistry.registerSimple(BOX, BoxScreenHandler::new);
  20. }
  21.  
  22. @Override
  23. public void onInitialize() {
  24.  
  25. }
  26. }

Container GUI and Screen

We need a ScreenHandler Class and a HandledScreen Class to display and sync the GUI. ScreenHandler classes are used to synchronize GUI state between the server and the client. HandledScreen classes are fully client-sided and are responsible for drawing GUI elements.

BoxScreenHandler.java
  1. public class BoxScreenHandler extends ScreenHandler {
  2. private final Inventory inventory;
  3.  
  4. //This constructor gets called on the client when the server wants it to open the screenHandler,
  5. //The client will call the other constructor with an empty Inventory and the screenHandler will automatically
  6. //sync this empty inventory with the inventory on the server
  7. public BoxScreenHandler(int syncId, PlayerInventory playerInventory) {
  8. this(syncId, playerInventory, new SimpleInventory(9));
  9. }
  10.  
  11. //This constructor gets called from the BlockEntity on the server without calling the other constructor first, the server knows the inventory of the container
  12. //and can therefore directly provide it as an argument. This inventory will then be synced to the Client
  13. public BoxScreenHandler(int syncId, PlayerInventory playerInventory, Inventory inventory) {
  14. super(ExampleMod.BOX_SCREEN_HANDLER, syncId);
  15. checkSize(inventory, 9);
  16. this.inventory = inventory;
  17. //some inventories do custom logic when a player opens it.
  18. inventory.onOpen(playerInventory.player);
  19.  
  20. //This will place the slot in the correct locations for a 3x3 Grid. The slots exist on both server and client!
  21. //This will not render the background of the slots however, this is the Screens job
  22. int m;
  23. int l;
  24. //Our inventory
  25. for (m = 0; m < 3; ++m) {
  26. for (l = 0; l < 3; ++l) {
  27. this.addSlot(new Slot(inventory, l + m * 3, 62 + l * 18, 17 + m * 18));
  28. }
  29. }
  30. //The player inventory
  31. for (m = 0; m < 3; ++m) {
  32. for (l = 0; l < 9; ++l) {
  33. this.addSlot(new Slot(playerInventory, l + m * 9 + 9, 8 + l * 18, 84 + m * 18));
  34. }
  35. }
  36. //The player Hotbar
  37. for (m = 0; m < 9; ++m) {
  38. this.addSlot(new Slot(playerInventory, m, 8 + m * 18, 142));
  39. }
  40.  
  41. }
  42.  
  43. @Override
  44. public boolean canUse(PlayerEntity player) {
  45. return this.inventory.canPlayerUse(player);
  46. }
  47.  
  48. // Shift + Player Inv Slot
  49. @Override
  50. public ItemStack transferSlot(PlayerEntity player, int invSlot) {
  51. ItemStack newStack = ItemStack.EMPTY;
  52. Slot slot = this.slots.get(invSlot);
  53. if (slot != null && slot.hasStack()) {
  54. ItemStack originalStack = slot.getStack();
  55. newStack = originalStack.copy();
  56. if (invSlot < this.inventory.size()) {
  57. if (!this.insertItem(originalStack, this.inventory.size(), this.slots.size(), true)) {
  58. return ItemStack.EMPTY;
  59. }
  60. } else if (!this.insertItem(originalStack, 0, this.inventory.size(), false)) {
  61. return ItemStack.EMPTY;
  62. }
  63.  
  64. if (originalStack.isEmpty()) {
  65. slot.setStack(ItemStack.EMPTY);
  66. } else {
  67. slot.markDirty();
  68. }
  69. }
  70.  
  71. return newStack;
  72. }
  73. }
BiggerChestBlock.java
  1. public class BoxScreen extends HandledScreen<ScreenHandler> {
  2. //A path to the gui texture. In this example we use the texture from the dispenser
  3. private static final Identifier TEXTURE = new Identifier("minecraft", "textures/gui/container/dispenser.png");
  4.  
  5. public BoxScreen(ScreenHandler handler, PlayerInventory inventory, Text title) {
  6. super(handler, inventory, title);
  7. }
  8.  
  9. @Override
  10. protected void drawBackground(MatrixStack matrices, float delta, int mouseX, int mouseY) {
  11. RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
  12. client.getTextureManager().bindTexture(TEXTURE);
  13. int x = (width - backgroundWidth) / 2;
  14. int y = (height - backgroundHeight) / 2;
  15. drawTexture(matrices, x, y, 0, 0, backgroundWidth, backgroundHeight);
  16. }
  17.  
  18. @Override
  19. public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
  20. renderBackground(matrices);
  21. super.render(matrices, mouseX, mouseY, delta);
  22. drawMouseoverTooltip(matrices, mouseX, mouseY);
  23. }
  24.  
  25. @Override
  26. protected void init() {
  27. super.init();
  28. // Center the title
  29. titleX = (backgroundWidth - textRenderer.getWidth(title)) / 2;
  30. }
  31. }
BiggerChestBlock.java
  1. @Environment(EnvType.CLIENT)
  2. public class ExampleClientMod implements ClientModInitializer {
  3. @Override
  4. public void onInitializeClient() {
  5. ScreenRegistry.register(ExampleMod.BOX_SCREEN_HANDLER, BoxScreen::new);
  6. }
  7. }
ExampleMod.java
  1. public class ExampleMod implements ModInitializer {
  2. [...]
  3. public static final ScreenHandlerType<BoxScreenHandler> BOX_SCREEN_HANDLER;
  4. [...]
  5. static {
  6. [...]
  7. //We use registerSimple here because our Entity is not an ExtendedScreenHandlerFactory
  8. //but a NamedScreenHandlerFactory.
  9. //In a later Tutorial you will see what ExtendedScreenHandlerFactory can do!
  10. BOX_SCREEN_HANDLER = ScreenHandlerRegistry.registerSimple(BOX, BoxScreenHandler::new);
  11. }
  12.  
  13. @Override
  14. public void onInitialize() {
  15.  
  16. }
  17. }
tutorial/screenhandler.1597420764.txt.gz · Last modified: 2020/08/14 15:59 by manymoney2