跳转到内容

UI

Estella 的 UI 是在 C++ 内核里求解的 CSS 式弹性盒(flexbox)系统。你用 UI 组件组合界面—— 在编辑器里或用代码——并在系统里处理交互。没有 RectTransform:UINode 就是真正的 flexbox,用 px / percent / autoAbsolute 内偏移(inset)来定尺寸。

本页是总览:组件模型、快速上手,以及各 UI 详细指南的地图。每个领域的深入参考在各自的章节里。

组件 职责 指南
UINode 布局盒;持有逐项的 flex 字段(尺寸、grow/shrink、margin、inset)。 UI 布局
FlexContainer 用 flexbox 排布一个节点的子项(方向、justify、align、gap、padding)。 UI 布局
Text 经动态字形图集渲染文本(内容、字体、颜色、对齐、描边、阴影、富文本)。 UI 文本
UIVisual 填充——纯色、纹理、九宫格、平铺或填充。 见下文
UIMask 裁剪它的子项。 见下文
Interactable 标记节点可命中,从而能被悬停/点击。 UI 交互
UIInteraction 引擎每帧写入的指针状态(对你的逻辑只读)。 UI 交互

一个完整的暂停菜单——居中的列面板,带标题和一个按钮——用启动系统构建一次:

import {
defineSystem, addStartupSystem, GetWorld, Res, UIEvents,
spawnUIEntity, createButton,
FlexContainer, FlexDirection, JustifyContent, AlignItems, px,
} from 'esengine';
const buildPauseMenu = defineSystem([GetWorld(), Res(UIEvents)], (world, events) => {
// A panel: 320×220 with a dark translucent background.
const panel = spawnUIEntity({
world,
node: { width: px(320), height: px(220) },
visual: { color: { r: 0.08, g: 0.10, b: 0.16, a: 0.95 } },
});
// Lay its children out as a centered vertical column.
world.insert(panel, FlexContainer, {
direction: FlexDirection.Column,
justifyContent: JustifyContent.Center,
alignItems: AlignItems.Center,
gap: { x: 0, y: 16 },
});
// A title, parented to the panel.
spawnUIEntity({ world, parent: panel, text: { content: 'Paused', fontSize: 28 } });
// A button that resumes the game on click.
createButton({
world, events, parent: panel, text: 'Resume',
node: { width: px(160), height: px(44) },
states: {
normal: { color: { r: 0.20, g: 0.55, b: 1.0, a: 1 } },
hover: { color: { r: 0.30, g: 0.65, b: 1.0, a: 1 } },
pressed: { color: { r: 0.12, g: 0.45, b: 0.9, a: 1 } },
},
onClick: () => { /* unpause, switch scene, hide the menu… */ },
});
});
addStartupSystem(buildPauseMenu);

spawnUIEntity({ world, parent?, node?, visual?, text? }) 返回实体;传 parent 来 嵌套。尺寸用维度助手 pxpercentauto——单位都是设计像素(见 UI 布局)。其余控件工厂用法相同: createTogglecreateSlidercreateProgresscreateDialogcreateDropdown——见 UI 组件

每一层都建立在前一层之上:

  1. 布局——UINode 盒子由 flexbox 引擎求解;FlexContainer 排布子项, Absolute 内偏移放置悬浮层。→ UI 布局
  2. 视觉与文本——UIVisual 填充盒子,UIMask 裁剪,Text 绘制锐利字形。 → UI 文本
  3. 交互——Interactable 让盒子可命中;引擎把指针状态写入 UIInteraction 并发出冒泡的 UIEvents;拖放和键盘焦点建立在其上。 → UI 交互
  4. 控件——工厂(createButtoncreateSlider……)把前三层组合成现成的控件; createListView 提供虚拟化列表。 → UI 组件UI 列表
  5. 控制器——每个 UI 根一份命名“页面”状态加逐页字段绑定(标签页、按钮状态、 显示/隐藏)——零代码。→ UI 控制器
  6. 主题与绑定——设计 token 给所有控件换肤(可实时切换),响应式 signal 让 UI 与游戏状态保持同步。 → UI 主题UI 绑定

UIVisual 绘制节点的背景;visualType 选择模式:

UIVisualType 使用字段 说明
None 不可见(仅参与布局/命中测试)。
SolidColor color 着色四边形。
Image textureuvOffsetuvScale 贴图四边形 / 精灵区域。
NineSlice sliceBorder 角部固定、可缩放的面板。
Tiled tileSize 纹理在盒内平铺重复。
Filled fillAmountfillMethodfillOrigin 裁剪式填充(血条/进度条、冷却环)。

UIVisual.enabled 只隐藏本实体的视觉;UINode.display = None 把该节点连同整棵 子树从布局、渲染和命中测试中移除。给节点加 UIMask 可把子项裁剪到盒内。

在编辑器视口里排好的一个 UI Canvas

编辑器里的一个 UI Canvas——依据设计框排布,带选中节点的 gizmo。同一套控件既能从代码写,也能在编辑器里排。

两者书写的是同一套组件——没有单独的编辑器格式:

  • 编辑器——Create… → UI 放入控件预制件(Button、Toggle、Slider、Dialog……); 拖拽嵌套,在 Details 里编辑每个字段,用逐轴锚点选择器摆放,并按项目的设计分辨率 和设备预设预览。见编辑器
  • 代码——spawnUIEntity + 控件工厂,如上例在启动系统里构建。编辑器能写的, 代码都能写。
你想…… 打开
定尺寸摆盒子——px/percent/auto、flexbox、绝对内偏移、锚点 UI 布局
绘制文本——字体、SDF/位图锐利度、换行、富文本 UI 文本
响应指针与键盘——点击、UIEvents、拖放、焦点 UI 交互
使用现成控件——按钮、开关、滑条、对话框、下拉、输入框 UI 组件
展示滚动数据——虚拟化列表与网格 UI 列表
全局换肤——设计 token、项目主题、实时切换 UI 主题
让 UI 与游戏状态同步——signal、bind、控件双向绑定 UI 绑定
跨元素共享状态——标签页、单选组、页面驱动的视觉 UI 控制器
  • 优先 flex 而非绝对定位——用 FlexContainer + flexGrow 布局;Absolute 内偏移只留给悬浮层和角标。
  • percent / auto 定尺寸以获得分辨率无关性;只有确实需要固定尺寸时才用 硬编码 px
  • 逻辑读 UIInteractionUIEvents 驱动,绝不去改写它们。
  • 复用控件工厂以获得一致的状态视觉,而不是手工接线悬停/按下。
  • 九宫格面板(NineSlice)在任意尺寸下保持边框锐利。
  • 输入——UI 层之下的原始键盘/鼠标/触摸。
  • 本地化——把 Text.content 绑定到翻译键。
  • 动画——补间 UI 颜色/位置增加动感。
  • 编辑器——可视化编排、预制件、锚点选择器。