主题
HNCore 现在提供了一套可复用的 GUI Kit,目标不是把所有业务逻辑都塞进配置,而是把 布局、外观、占位符、分页骨架、导航按钮 这些重复劳动统一抽出来,让业务插件只保留真正有差异的点击逻辑和数据查询。
补充说明:GUI Kit 与共享物品库是两套不同能力;如果你的 GUI 里需要展示或发放 Baikiruto / Mythic / Neige 物品,仍然推荐通过
HNCoreAPI.resolveLibraryItem(...)先统一解析,再交给业务 GUI 使用。
设计边界
这套 GUI Kit 的核心原则很简单:
- 模板负责外观与布局
GuiContext负责占位符数据- 业务代码负责点击行为与数据来源
因此它更适合这样的问题:
- 同一类 GUI 到处都要写边框、标题、分页按钮、空列表提示
- 同一个模块里很多 slot / 材质 / lore 写死在 Java 里,不方便后期调样式
- 多个业务插件都想共用一套分页、导航与模板渲染逻辑
但它目前不打算做成一个“纯配置驱动业务系统”。
核心类
GuiContext
统一承载占位符数据,例如:
java
GuiContext context = HNCoreAPI.createGuiContext()
.with("player", player.getName())
.with("page", page)
.with("pages", totalPages)
.with("count", total);常见用途:
{player}{page}/{pages}{count}{status}{title}
GuiTemplateLoader
用于从业务插件自己的资源目录读取模板:
java
GuiTemplate template = HNCoreAPI.createGuiTemplateLoader(this)
.load("gui/mail-inbox.yml");推荐把模板放在:
text
src/main/resources/gui/而不是放进 HNCore 自己的数据目录统一管理。
GuiTemplateRenderer
把模板和上下文渲染成:
Gui- 模板
ItemStack
java
Gui gui = HNCoreAPI.createGuiTemplateRenderer().render(this, template, context);
ItemStack icon = HNCoreAPI.createGuiTemplateRenderer().renderItem(template, "entry", context);AbstractPagedGui<T>
适合列表型 GUI,例如:
- 邮箱列表
- 黑名单列表
- 商店商品页
- 任务列表
- 强化记录页
它统一处理:
- 页码边界
content-slots分发- 空列表渲染
- 上一页 / 下一页 / 返回 / 刷新按钮
- 条目点击绑定
GuiNavItems
统一渲染常见导航项,内置约定 key 包括:
back-menuprev-pagenext-pagerefreshclose
模板文件结构
一个典型模板如下:
yml
gui:
title: "&8邮箱菜单"
size: 27
cancel-click: true
layout:
content-slots: [10, 11, 12, 13, 14, 15, 16]
items:
background:
fill-slots: [0, 1, 2, 3, 4, 5, 6, 7, 8]
material: LIGHT_BLUE_STAINED_GLASS_PANE
name: " "
inbox:
slot: 10
material: CHEST
name: "&b收件箱"
lore:
- "&7总邮件数: &f{total}"
- "&7未读邮件: &e{unread}"顶层字段说明
gui.title
GUI 标题,支持占位符。
gui.size
必须是 9 的倍数,范围 9 ~ 54。
gui.cancel-click
是否默认取消点击事件。大多数菜单 GUI 推荐保持 true。
gui.layout.content-slots
分页内容槽位定义,主要用于 AbstractPagedGui<T>。
- 顺序决定分页条目的摆放顺序
- 不要求连续
- 可用于跳过边框、标题栏、底栏等固定区域
items.<key> 支持字段
每个模板物品目前支持:
slotfill-slotsmaterialnameloreamountcustom-model-dataenchantsflagsunbreakable
slot
单槽位物品。
fill-slots
多槽位物品,适合背景板、装饰条、批量填充区域。
material
Bukkit Material 名称。
name / lore
支持占位符替换,例如:
yml
name: "&a第 {page} 页"
lore:
- "&7总数: &f{count}"amount
物品数量,默认 1。
custom-model-data
用于资源包模型切换。
enchants
例如:
yml
enchants:
DURABILITY: 1flags
例如:
yml
flags:
- HIDE_ATTRIBUTES
- HIDE_ENCHANTSunbreakable
布尔值。
slot 与 fill-slots 的使用约定
固定按钮
推荐用 slot:
- 返回
- 关闭
- 设置
- 刷新
- 详情页操作按钮
批量装饰项
推荐用 fill-slots:
- 边框
- 背景板
- 分隔条
列表条目模板
如果某个物品只是作为“列表条目样式模板”,可以不写 slot / fill-slots。
例如:
yml
entry:
material: PLAYER_HEAD
name: "&f{player}"
lore:
- "&7时间: &f{time}"这种写法适合:
- 黑名单条目
- 邮件条目
- 商品条目
- 日志条目
实际放到哪个槽位,由分页基类或业务代码决定。
推荐命名约定
为了便于跨模块复用,推荐尽量遵守下面这些 key:
通用导航
back-menuprev-pagenext-pagerefreshcloseempty
列表模板
entrysummarybackground
业务按钮
按业务语义命名,例如:
composeclaimreplydeletesettingsfilter-allfilter-unread
不要只写成 item1、item2、btn3 这类没有语义的 key。
占位符建议
推荐统一使用简单、稳定的占位符名:
{player}{page}{pages}{count}{title}{status}{time}{type}
如果是同一页内多个区域,建议加前缀,例如:
{summary-title}{summary-ready}{filter-name}
这样模板更不容易互相污染。
一个最小接入示例
普通模板 GUI
java
GuiTemplate template = plugin.getGuiTemplateLoader().load("gui/example.yml");
GuiContext context = HNCoreAPI.createGuiContext()
.with("player", player.getName())
.with("count", 12);
Gui gui = plugin.getGuiTemplateRenderer().render(plugin, template, context);
gui.setItem(template.requireSlot("open"),
plugin.getGuiTemplateRenderer().renderItem(template, "open", context),
viewer -> openNext(viewer));
gui.open(player);分页 GUI
java
public final class DemoPagedGui extends AbstractPagedGui<DemoEntry> {
private final MyPlugin plugin;
private final GuiTemplate template;
public DemoPagedGui(MyPlugin plugin) {
super(plugin);
this.plugin = plugin;
this.template = plugin.getGuiTemplateLoader().load("gui/demo-list.yml");
}
@Override
protected GuiTemplate getTemplate() {
return template;
}
@Override
protected int countEntries(Player player) {
return plugin.getDemoService().count(player.getUniqueId());
}
@Override
protected List<DemoEntry> loadEntries(Player player, int page, int pageSize) {
return plugin.getDemoService().list(player.getUniqueId(), page, pageSize);
}
@Override
protected ItemStack renderEntryItem(Player player, DemoEntry entry, GuiContext context, int indexOnPage) {
return getTemplateRenderer().renderItem(template, "entry", context.copy()
.with("title", entry.title())
.with("time", entry.timeText()));
}
@Override
protected void onEntryClick(Player player, DemoEntry entry, int currentPage, int indexOnPage) {
plugin.getDemoService().open(player, entry.id());
}
}当前适合做什么,不适合做什么
适合
- 把 GUI 外观从 Java 中抽出来
- 统一分页与导航逻辑
- 让多个模块共用同一套 GUI 基础设施
- 让服主或开发者更方便调整材质、标题、描述与布局
暂时不适合
- 纯配置定义复杂业务流程
- 在 yml 中直接写点击动作 DSL
- 把全部业务判断都塞进模板层
如果以后确实需要更强的“配置驱动动作系统”,可以在这套骨架之上继续扩展;但当前版本优先保证的是:
- 清晰
- 稳定
- 可维护
- 易被业务代码接入
当前验证情况
这套 GUI Kit 目前已经在 HNMail 中完成了一轮真实接入,覆盖:
- 主菜单
- 收件箱分页列表
- 黑名单分页列表
- 设置页
- 邮件详情页
- 写信页
因此它已经不只是概念 API,而是一套经过业务验证的通用基础设施。
