GUI自定义组件
自定义组件用于把一组 GUI 部件封装成可复用结构。它适合商品条目、任务卡片、技能按钮、排行榜行、地图卡片这类重复界面。
自定义组件本身是一个真实 GUI 部件:在普通 GUI 位置写 type: component 后,会创建一个 component 容器,并在它内部加载组件定义里的 root。组件内部仍然可以放 canvas、texture、label、button、slot、scroll 等普通部件。
适合场景
- 纯配置 GUI:重复结构写成组件,每个实例只传不同
props。 - 脚本前后端分离 GUI:YAML 负责界面结构,
clickScript、clickDos或服务端接口负责业务逻辑。 - 商品栏、任务列表、技能网格:多个同类组件放进带
layout的canvas或scroll,由父容器负责排布。
不推荐把自定义组件作为传统命令式 GUI 的主要开发方式。比如大量使用 GermGuiScreen.getGuiPart("xxx").setLocationX("100") 逐个移动部件时,直接操作普通部件会更直观。组件内部部件仍然可以通过 商品_1$购买按钮 这类路径访问,但整体设计更适合“配置描述界面,脚本处理交互”的写法。
放置位置
公共组件库可以写在 plugins/GermPlugin/gui/ 目录下任意 yml 文件中,只要文件 里有顶级 components 节点就会被加载。
components:
简单标题:
props:
text: "标题"
root:
type: canvas
width: "%fontWidth_${props.text}%"
height: "12"
relativeParts:
文本:
type: label
texts:
- "${props.text}"
一个 yml 可以只写 components,也可以同时写 components 和普通 GUI。components 下可以定义多个组件,不是一个文件只能放一个组件。
某个 GUI 自己使用的小组件可以写在当前 GUI 的 options.components 下。它只对当前 GUI 生效,同名时优先于公共组件。
商店界面:
options:
components:
私有标题:
props:
text: "商店"
root:
type: canvas
width: "%fontWidth_${props.text}%"
height: "12"
relativeParts:
文本:
type: label
texts:
- "&e${props.text}"
配置项
组件定义字段:
| 配置项 | 位置 | 必填 | 说明 |
|---|---|---|---|
components | yml 顶级或某个 GUI 的 options 下 | 否 | 组件库入口。顶级为公共组件库,options.components 为当前 GUI 私有组件库。 |
props | 某个组件定义下 | 否 | 组件参数默认值。引用组件时可以覆盖。 |
root | 某个组件定义下 | 是 | 组件内部根容器配置。当前按 canvas 容器加载,实际内容写到 relativeParts。 |
组件引用字段:
| 配置项 | 位置 | 必填 | 说明 |
|---|---|---|---|
type | 普通部件位置 | 是 | 固定写 component。 |
component | 组件实例节点 | 是 | 要引用的组件名。 |
props | 组件实例节点 | 否 | 传给组件的参数,会覆盖组件定义里的默认 props。 |
| 普通部件字段 | 组件实例节点 | 否 | 如 locationX、locationY、width、height、enable、relativeParts 等,会覆盖或追加到组件 root 上。 |
模板变量只使用 ${props.xxx}:
texts:
- "&f${props.name}"
clickDos:
- "opcmd<->shop buy %player_name% ${props.id}"
${props.xxx} 是组件模板变量,%player_name%、%calculate_100+20%、%thisGui_part_width% 这类萌芽原有全局变量照常保留。
商品条目组件
商品条目通常是一组部件:背景、槽位、图标、名称、价格、购买按钮。推荐把它封装成一个 canvas 根组件。
components:
商品条目:
props:
id: "unknown"
identity: "shop_slot_unknown"
icon: "local<->textures/shop/default.png"
name: "商品"
price: "0"
root:
type: canvas
width: "180"
height: "64"
relativeParts:
背景:
type: texture
path: "local<->textures/gui/shop/item_bg.png"
width: "w"
height: "h"
槽位:
type: slot
identity: "${props.identity}"
sync: false
interact: false
invalid: true
locationX: "8"
locationY: "14"
size: "36"
图标:
type: texture
path: "${props.icon}"
locationX: "10"
locationY: "16"
width: "32"
height: "32"
名称:
type: label
locationX: "52"
locationY: "12"
texts:
- "&f${props.name}"
价格:
type: label
locationX: "52"
locationY: "34"
texts:
- "&e${props.price}金币"
购买按钮:
type: button
locationX: "132"
locationY: "18"
width: "40"
height: "26"
defaultPath: "local<->textures/gui/shop/buy.png"
hoverPath: "local<->textures/gui/shop/buy_hover.png"
clickDos:
- "opcmd<->shop buy %player_name% ${props.id}"
clickScript: |
var itemId = '${props.id}';
Log.chat('准备购买商品: ' + itemId);
组件中的 ${props.id} 会替换成当前实例传入的商品 id。脚本和 DOS 都可以使用这些变量。
配合 layout 使用
当前推荐的批量界面写法,是把多个 type: component 实例放进带 layout 的父容器,而不是在组件自身写 count、columns 或 items。
商店界面:
options:
startX: "w*0.5-230"
startY: "h*0.5-140"
商品滚动框:
type: scroll
locationX: "20"
locationY: "28"
width: "460"
height: "240"
relative: true
scrollableV: "auto"
invalidH: true
invalidV: false
layout:
type: FLOW
gapX: "10"
gapY: "8"
fillX: "0"
fillY: "0"
scrollableParts:
商品_1:
type: component
component: 商品条目
props:
id: "diamond_sword"
identity: "shop_slot_diamond_sword"
icon: "local<->textures/shop/diamond_sword.png"
name: "钻石剑"
price: "100"
商品_2:
type: component
component: 商品条目
props:
id: "heal_potion"
identity: "shop_slot_heal_potion"
icon: "local<->textures/shop/heal_potion.png"
name: "治疗药水"
price: "80"
商品_3:
type: component
component: 商品条目
props:
id: "magic_scroll"
identity: "shop_slot_magic_scroll"
icon: "local<->textures/shop/magic_scroll.png"
name: "魔法卷轴"
price: "160"
父容器 layout 会根据每个商品条目的宽高自动排布。需要六个商品就写六个组件实例;每个实例只改 props,按钮脚本、槽位结构、文本结构都保留在组件定义中。
访问内部部件时使用组件实例名加 $:
商品_1$购买按钮
商品_2$名称
商品_3$槽位
查看组件
使用命令查看当前已加载的公共组件:
/gp gui components
输出会显示组件名和组件 root.type,方便确认组件库是否已经被读取。
兼容说明
canvas.relativeParts、scroll.scrollableParts内都可以引用type: component。- 组件可以嵌套组件,内部组件同样会按
component字段继续加载。 options.components只服务当前 GUI;跨多个 GUI 复用时应写在顶级components公共库。- 动态插入部件时,插入片段里也可以写
type: component,会按普通部件一样创建。 component和props用于创建组件时展开模板。界面打开后如果要改显示内容,推荐更新组件内部具体部件,或删除后重新插入整个 component。