Skip to content

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-menu
  • prev-page
  • next-page
  • refresh
  • close

模板文件结构

一个典型模板如下:

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> 支持字段

每个模板物品目前支持:

  • slot
  • fill-slots
  • material
  • name
  • lore
  • amount
  • custom-model-data
  • enchants
  • flags
  • unbreakable

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: 1

flags

例如:

yml
flags:
  - HIDE_ATTRIBUTES
  - HIDE_ENCHANTS

unbreakable

布尔值。

slot 与 fill-slots 的使用约定

固定按钮

推荐用 slot

  • 返回
  • 关闭
  • 设置
  • 刷新
  • 详情页操作按钮

批量装饰项

推荐用 fill-slots

  • 边框
  • 背景板
  • 分隔条

列表条目模板

如果某个物品只是作为“列表条目样式模板”,可以不写 slot / fill-slots

例如:

yml
entry:
  material: PLAYER_HEAD
  name: "&f{player}"
  lore:
    - "&7时间: &f{time}"

这种写法适合:

  • 黑名单条目
  • 邮件条目
  • 商品条目
  • 日志条目

实际放到哪个槽位,由分页基类或业务代码决定。

推荐命名约定

为了便于跨模块复用,推荐尽量遵守下面这些 key:

通用导航

  • back-menu
  • prev-page
  • next-page
  • refresh
  • close
  • empty

列表模板

  • entry
  • summary
  • background

业务按钮

按业务语义命名,例如:

  • compose
  • claim
  • reply
  • delete
  • settings
  • filter-all
  • filter-unread

不要只写成 item1item2btn3 这类没有语义的 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,而是一套经过业务验证的通用基础设施。

HN 系列插件文档