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();权限优先级
playerSlot()单独设置的权限(最高)playerHotbarPermission/playerMainPermission区域权限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);
}最佳实践
- 明确权限:根据需求设置合适的权限,避免意外操作
- 使用预设:优先使用预设权限(
READ_ONLY、INTERACT_ONLY等) - 分离关注点:GUI 构建和业务逻辑分离
- 处理关闭:使用
onClose清理玩家状态 - 避免内存泄漏:及时清理
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()进行局部更新。