Skip to content

EnderRealmFastGUI 使用指南

基础用法

简单 GUI

java
import cn.enderrealm.fastgui.inventory.config.ContainerType;
import cn.enderrealm.fastgui.inventory.config.SlotPermission;
import cn.enderrealm.fastgui.inventory.core.InventoryGUI;

// 创建一个简单的菜单
InventoryGUI menu = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("主菜单")
    .rows(1)  // 1行 = 9格
    .containerPermission(SlotPermission.READ_ONLY)
    .playerInventoryPermission(SlotPermission.READ_ONLY)
    .slot(0, new ItemStack(Material.CHEST), click -> {
        click.getPlayer().sendMessage("你点击了箱子!");
    }, SlotPermission.INTERACT_ONLY)
    .slot(1, new ItemStack(Material.EMERALD), click -> {
        click.getPlayer().sendMessage("你点击了绿宝石!");
    }, SlotPermission.INTERACT_ONLY)
    .build();

menu.open(player);

自定义尺寸

java
// 箱子支持 1-6 行(9-54 格)
InventoryGUI bigChest = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("大箱子")
    .rows(6)  // 6行 = 54格
    .build();

// 或者直接指定大小
InventoryGUI customSize = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("自定义大小")
    .size(27)  // 27格
    .build();

其他容器类型

java
// 漏斗(固定 5 格)
InventoryGUI hopper = InventoryGUI.builder()
    .type(ContainerType.HOPPER)
    .title("漏斗菜单")
    .build();

// 熔炉(固定 3 格)
InventoryGUI furnace = InventoryGUI.builder()
    .type(ContainerType.FURNACE)
    .title("熔炉菜单")
    .build();

// 投掷器(固定 9 格)
InventoryGUI dropper = InventoryGUI.builder()
    .type(ContainerType.DROPPER)
    .title("投掷器菜单")
    .build();

权限控制

区域默认权限

java
InventoryGUI gui = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("权限示例")
    .rows(3)
    .containerPermission(SlotPermission.READ_ONLY)       // 容器区域默认只读
    .playerInventoryPermission(SlotPermission.FULL_ACCESS) // 背包区域可自由操作
    .build();

单独 slot 权限

java
InventoryGUI gui = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("商店")
    .rows(3)
    .containerPermission(SlotPermission.READ_ONLY)
    .playerInventoryPermission(SlotPermission.READ_ONLY)
    // 商品展示(只读)
    .slot(10, diamondItem, SlotPermission.READ_ONLY)
    // 购买按钮(可交互)
    .slot(13, buyButton, click -> handleBuy(click), SlotPermission.INTERACT_ONLY)
    // 取货区(可拿走)
    .slot(16, resultItem, SlotPermission.TAKE_ONLY)
    // 材料输入(可放入)
    .slot(22, new ItemStack(Material.AIR), SlotPermission.PLACE_ONLY)
    .build();

自定义权限

java
import cn.enderrealm.fastgui.inventory.config.SlotPermission;

// 使用 Builder 创建自定义权限
SlotPermission customPermission = SlotPermission.builder()
    .clickable(true)
    .takeable(false)
    .placeable(true)
    .movable(false)
    .build();

gui.slot(10, item, click -> { ... }, customPermission);

背包权限控制

背包区域分为两部分:

  • 快捷栏:索引 0-8(底部 9 格)
  • 主背包:索引 9-35(上方 27 格)

设置整个背包区域权限

java
InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("示例")
    .rows(3)
    .playerInventoryPermission(SlotPermission.READ_ONLY) // 整个背包只读
    .build();

分别设置快捷栏和主背包权限

java
InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("商店")
    .rows(3)
    .containerPermission(SlotPermission.READ_ONLY)        // 容器只读
    .playerInventoryPermission(SlotPermission.READ_ONLY)   // 背包默认只读
    .playerHotbarPermission(SlotPermission.INTERACT_ONLY)  // 快捷栏可交互
    .playerMainPermission(SlotPermission.FULL_ACCESS)      // 主背包完全自由
    .build();

单独设置背包中的某个 slot

java
InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("示例")
    .rows(3)
    .containerPermission(SlotPermission.READ_ONLY)
    .playerInventoryPermission(SlotPermission.READ_ONLY)
    // 背包快捷栏第 1 格可交互
    .playerSlot(0, hotbarItem, click -> { ... }, SlotPermission.INTERACT_ONLY)
    // 背包主区域第 1 格可拿走
    .playerSlot(9, mainItem, SlotPermission.TAKE_ONLY)
    .build();

权限优先级

  1. playerSlot() 单独设置的权限(最高)
  2. playerHotbarPermission / playerMainPermission 区域权限
  3. playerInventoryPermission 整体默认权限(最低)
java
InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("优先级示例")
    .rows(3)
    .playerInventoryPermission(SlotPermission.READ_ONLY)   // 默认只读
    .playerHotbarPermission(SlotPermission.INTERACT_ONLY)  // 快捷栏可交互
    .playerSlot(0, specialItem, SlotPermission.TAKE_ONLY)  // 第 1 格可拿走(覆盖快捷栏权限)
    .build();

完整示例:商店背包权限

java
InventoryGUI shop = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("商店")
    .rows(6)
    // 容器区域
    .containerPermission(SlotPermission.READ_ONLY)
    .slot(10, shopItem1, click -> buyItem(click), SlotPermission.INTERACT_ONLY)
    .slot(11, shopItem2, click -> buyItem(click), SlotPermission.INTERACT_ONLY)
    .slot(14, resultItem, SlotPermission.TAKE_ONLY)
    // 背包区域
    .playerInventoryPermission(SlotPermission.READ_ONLY)   // 默认只读
    .playerHotbarPermission(SlotPermission.INTERACT_ONLY)  // 快捷栏可交互
    .playerMainPermission(SlotPermission.FULL_ACCESS)      // 主背包自由移动
    .build();

shop.open(player);

跨区域权限控制

权限系统会自动处理跨区域操作(如从背包移动物品到容器):

  • 背包→容器:检查背包 slot 的 takeable + 容器区域是否有任何 slot 的 placeable
  • 容器→背包:检查容器 slot 的 takeable

示例:背包物品可移动,但不能放入容器

java
InventoryGUI gui = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("仓库")
    .rows(3)
    // 容器区域:只读,不允许放入
    .containerPermission(SlotPermission.READ_ONLY)
    // 背包区域:完全自由
    .playerInventoryPermission(SlotPermission.FULL_ACCESS)
    .build();

// 结果:
// - 背包物品可以在背包内自由移动 ✓
// - 背包物品不能放入容器 ✓
// - 容器物品不能拿走 ✓

示例:允许放入特定容器 slot

java
InventoryGUI gui = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("合成台")
    .rows(3)
    // 容器区域:默认只读
    .containerPermission(SlotPermission.READ_ONLY)
    // 材料输入区:可放入
    .slot(10, new ItemStack(Material.AIR), SlotPermission.PLACE_ONLY)
    .slot(11, new ItemStack(Material.AIR), SlotPermission.PLACE_ONLY)
    .slot(12, new ItemStack(Material.AIR), SlotPermission.PLACE_ONLY)
    // 结果输出区:可拿走
    .slot(14, resultItem, SlotPermission.TAKE_ONLY)
    // 背包区域:完全自由
    .playerInventoryPermission(SlotPermission.FULL_ACCESS)
    .build();

// 结果:
// - 背包物品可以放入材料输入区 ✓
// - 背包物品不能放入其他容器 slot ✓
// - 结果可以拿走 ✓

填充功能

填充所有空位

java
ItemStack filler = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
ItemMeta meta = filler.getItemMeta();
meta.setDisplayName(" ");
filler.setItemMeta(meta);

InventoryGUI gui = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("填充示例")
    .rows(3)
    .fill(filler, SlotPermission.READ_ONLY)  // 填充所有空位
    .slot(13, centerItem, SlotPermission.INTERACT_ONLY)  // 中间放物品
    .build();

填充指定范围

java
InventoryGUI gui = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("范围填充")
    .rows(6)
    .fillRange(0, 9, topBorder, SlotPermission.READ_ONLY)    // 第一行填充边框
    .fillRange(45, 54, bottomBorder, SlotPermission.READ_ONLY) // 最后一行填充边框
    .build();

事件处理

点击事件

java
InventoryGUI gui = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("点击示例")
    .rows(1)
    .slot(0, item, click -> {
        Player player = click.getPlayer();
        int slot = click.getSlot();
        ClickType clickType = click.getClickType();
        ItemStack currentItem = click.getCurrentItem();
        
        player.sendMessage("你点击了 slot " + slot);
        player.sendMessage("点击类型: " + clickType);
        
        // 取消事件(默认已取消)
        click.setCancelled(true);
    }, SlotPermission.INTERACT_ONLY)
    .build();

关闭事件

java
InventoryGUI gui = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("关闭示例")
    .rows(1)
    .onClose(close -> {
        Player player = close.getPlayer();
        player.sendMessage("你关闭了 GUI!");
        // 清理状态
        playerData.remove(player.getUniqueId());
    })
    .build();

拖拽事件

java
InventoryGUI gui = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("拖拽示例")
    .rows(1)
    .containerPermission(SlotPermission.FULL_ACCESS)  // 允许拖拽
    .onDrag(drag -> {
        Player player = drag.getPlayer();
        Set<Integer> slots = drag.getRawSlots();
        
        player.sendMessage("你拖拽了 " + slots.size() + " 个格子");
        
        // 检查是否允许拖拽
        for (int slot : slots) {
            if (!isAllowedSlot(slot)) {
                drag.setCancelled(true);
                return;
            }
        }
    })
    .build();

打开事件

java
InventoryGUI gui = InventoryGUI.builder()
    .type(ContainerType.CHEST)
    .title("打开示例")
    .rows(1)
    .onOpen(open -> {
        Player player = open.getPlayer();
        player.sendMessage("你打开了 GUI!");
        // 记录日志
        logPlayerOpen(player);
    })
    .build();

实际案例

商店 GUI

java
public void openShop(Player player) {
    InventoryGUI shop = InventoryGUI.builder()
        .type(ContainerType.CHEST)
        .title("商店")
        .rows(6)
        .containerPermission(SlotPermission.READ_ONLY)
        .playerInventoryPermission(SlotPermission.READ_ONLY)
        // 商品
        .slot(10, createShopItem(Material.DIAMOND, 100), click -> {
            if (buyItem(click.getPlayer(), "diamond", 100)) {
                click.getPlayer().sendMessage("购买成功!");
            }
        }, SlotPermission.INTERACT_ONLY)
        .slot(11, createShopItem(Material.EMERALD, 50), click -> {
            if (buyItem(click.getPlayer(), "emerald", 50)) {
                click.getPlayer().sendMessage("购买成功!");
            }
        }, SlotPermission.INTERACT_ONLY)
        // 取货区
        .slot(14, new ItemStack(Material.CHEST), SlotPermission.TAKE_ONLY)
        .slot(15, new ItemStack(Material.CHEST), SlotPermission.TAKE_ONLY)
        .build();
    
    shop.open(player);
}

合成台 GUI

java
public void openCraftingGUI(Player player) {
    int[] craftSlots = {10, 11, 12, 19, 20, 21, 28, 29, 30};
    
    InventoryGUIBuilder builder = InventoryGUI.builder()
        .type(ContainerType.CHEST)
        .title("自定义合成台")
        .rows(6)
        .containerPermission(SlotPermission.READ_ONLY)
        .playerInventoryPermission(SlotPermission.READ_ONLY);
    
    // 填充边框
    ItemStack filler = createFiller();
    for (int i = 0; i < 54; i++) {
        if (!isCraftSlot(i, craftSlots)) {
            builder.slot(i, filler, SlotPermission.READ_ONLY);
        }
    }
    
    // 材料输入区(可放入)
    for (int slot : craftSlots) {
        builder.slot(slot, new ItemStack(Material.AIR), SlotPermission.PLACE_ONLY);
    }
    
    // 结果输出区(可拿走)
    builder.slot(24, createResultItem(), SlotPermission.TAKE_ONLY);
    
    // 返回按钮
    builder.slot(49, createBackButton(), click -> {
        openMainMenu(click.getPlayer());
    }, SlotPermission.INTERACT_ONLY);
    
    builder.build().open(player);
}

分页菜单

java
private Map<Player, Integer> playerPages = new HashMap<>();

public void openPagedMenu(Player player, int page) {
    List<ItemStack> items = getItems();
    int totalPages = (int) Math.ceil((double) items.size() / 45);
    
    if (page < 0) page = 0;
    if (page >= totalPages) page = totalPages - 1;
    
    playerPages.put(player, page);
    
    InventoryGUIBuilder builder = InventoryGUI.builder()
        .type(ContainerType.CHEST)
        .title("菜单 " + (page + 1) + "/" + totalPages)
        .rows(6)
        .containerPermission(SlotPermission.READ_ONLY)
        .playerInventoryPermission(SlotPermission.READ_ONLY)
        .onClose(close -> playerPages.remove(close.getPlayer()));
    
    // 添加物品
    int start = page * 45;
    int end = Math.min(start + 45, items.size());
    for (int i = start; i < end; i++) {
        final int index = i;
        builder.slot(i - start, items.get(i), click -> {
            handleItemClick(click.getPlayer(), index);
        }, SlotPermission.INTERACT_ONLY);
    }
    
    // 翻页按钮
    final int currentPage = page;
    if (currentPage > 0) {
        builder.slot(45, createPrevButton(), click -> {
            openPagedMenu(click.getPlayer(), currentPage - 1);
        }, SlotPermission.INTERACT_ONLY);
    }
    if (currentPage < totalPages - 1) {
        builder.slot(53, createNextButton(), click -> {
            openPagedMenu(click.getPlayer(), currentPage + 1);
        }, SlotPermission.INTERACT_ONLY);
    }
    
    builder.build().open(player);
}

最佳实践

  1. 明确权限:根据需求设置合适的权限,避免意外操作
  2. 使用预设:优先使用预设权限(READ_ONLYINTERACT_ONLY 等)
  3. 分离关注点:GUI 构建和业务逻辑分离
  4. 处理关闭:使用 onClose 清理玩家状态
  5. 避免内存泄漏:及时清理 Map<Player, ...> 中的数据

动态更新

EnderRealmFastGUI 支持在不关闭 GUI 的情况下动态更新内容,适用于商店连续购买、实时数据展示等场景。

更新单个 slot

java
.slot(10, buyButton, click -> {
    InventoryGUI gui = click.getGui();
    Player player = click.getPlayer();
    
    if (buyItem(player, "diamond")) {
        // 购买成功,更新按钮显示
        gui.updateSlot(10, successItem);
        player.sendMessage("购买成功!");
    }
}, SlotPermission.INTERACT_ONLY)

更新单个 slot(带新的处理器)

java
.slot(10, confirmButton, click -> {
    InventoryGUI gui = click.getGui();
    
    // 点击确认后,更新为取消按钮
    gui.updateSlot(10, cancelButton, cancelClick -> {
        // 点击取消,恢复原状
        cancelClick.getGui().updateSlot(10, confirmButton);
    });
}, SlotPermission.INTERACT_ONLY)

批量更新多个 slot

java
.slot(13, refreshButton, click -> {
    InventoryGUI gui = click.getGui();
    
    // 批量更新多个 slot
    Map<Integer, ItemStack> updates = new HashMap<>();
    updates.put(10, newStockItem1);
    updates.put(11, newStockItem2);
    updates.put(12, newStockItem3);
    
    gui.updateSlots(updates);
    click.getPlayer().sendMessage("库存已刷新!");
}, SlotPermission.INTERACT_ONLY)

完整示例:商店连续购买

java
public void openShop(Player player) {
    InventoryGUI shop = InventoryGUI.builder()
        .type(ContainerType.CHEST)
        .title("商店")
        .rows(3)
        .containerPermission(SlotPermission.READ_ONLY)
        .playerInventoryPermission(SlotPermission.READ_ONLY)
        // 商品 1:钻石
        .slot(10, createShopItem(Material.DIAMOND, 100), click -> {
            InventoryGUI gui = click.getGui();
            Player p = click.getPlayer();
            
            if (buyItem(p, "diamond", 100)) {
                // 购买成功,短暂显示成功状态
                gui.updateSlot(10, createSuccessItem());
                
                // 1秒后恢复原状(使用调度器)
                Bukkit.getScheduler().runTaskLater(plugin, () -> {
                    gui.updateSlot(10, createShopItem(Material.DIAMOND, 100));
                }, 20L);
            }
        }, SlotPermission.INTERACT_ONLY)
        // 商品 2:绿宝石
        .slot(11, createShopItem(Material.EMERALD, 50), click -> {
            InventoryGUI gui = click.getGui();
            Player p = click.getPlayer();
            
            if (buyItem(p, "emerald", 50)) {
                gui.updateSlot(11, createSuccessItem());
                Bukkit.getScheduler().runTaskLater(plugin, () -> {
                    gui.updateSlot(11, createShopItem(Material.EMERALD, 50));
                }, 20L);
            }
        }, SlotPermission.INTERACT_ONLY)
        // 刷新按钮
        .slot(14, createRefreshButton(), click -> {
            InventoryGUI gui = click.getGui();
            // 批量更新所有商品
            gui.updateSlots(Map.of(
                10, createShopItem(Material.DIAMOND, 100),
                11, createShopItem(Material.EMERALD, 50)
            ));
            click.getPlayer().sendMessage("商品已刷新!");
        }, SlotPermission.INTERACT_ONLY)
        .build();
    
    shop.open(player);
}

刷新整个 GUI

如果需要完全刷新 GUI(例如重新计算所有内容),可以使用 refresh() 方法:

java
.slot(13, refreshButton, click -> {
    InventoryGUI gui = click.getGui();
    Player player = click.getPlayer();
    
    // 完全刷新 GUI
    gui.refresh(player);
}, SlotPermission.INTERACT_ONLY)

注意refresh() 会关闭并重新打开 GUI,可能会有短暂的视觉闪烁。优先使用 updateSlot()updateSlots() 进行局部更新。