UI 交互
Interactable
将 UI 实体标记为可交互(接收指针事件)。所有交互控件都需要此组件。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | boolean | true | 是否启用交互 |
blockRaycast | boolean | true | 阻止事件传递到后面的元素 |
raycastTarget | boolean | true | 是否参与射线检测 |
UIInteraction
由插件自动添加到拥有 Interactable 的实体上,提供逐实体状态:
| 属性 | 类型 | 说明 |
|---|---|---|
hovered | boolean | 指针悬停在此实体上 |
pressed | boolean | 实体正被按下 |
justPressed | boolean | 本帧开始按下 |
justReleased | boolean | 本帧释放按下 |
UIEvents
UIEvents 资源收集每帧的交互事件:
import { defineSystem, addSystem, Res } from 'esengine';import { UIEvents } from 'esengine';
addSystem(defineSystem( [Res(UIEvents)], (events) => { for (const e of events.query('click')) { console.log('点击了:', e.entity); } }));事件类型
| 类型 | 说明 | 冒泡 | 触发者 |
|---|---|---|---|
click | 指针在同一实体上按下后释放 | 是 | UIInteraction |
press | 指针按钮按下 | 是 | UIInteraction |
release | 指针按钮释放 | 是 | UIInteraction |
hover_enter | 指针进入实体边界 | 否 | UIInteraction |
hover_exit | 指针离开实体边界 | 否 | UIInteraction |
focus | 元素获得键盘焦点(点击或 Tab) | 否 | Focusable |
blur | 元素失去键盘焦点 | 否 | Focusable |
submit | 单行 TextInput 中按下 Enter | 否 | TextInput |
change | Slider、Toggle、TextInput 或 Dropdown 的值改变 | 否 | 各控件 |
drag_start | 指针移动超过拖拽阈值 | 否 | Draggable |
drag_move | 拖拽过程中指针移动 | 否 | Draggable |
drag_end | 拖拽后释放指针 | 否 | Draggable |
scroll | ScrollView 内容滚动 | 否 | ScrollView |
UIEvent 结构
每个事件对象包含:
| 属性 | 类型 | 说明 |
|---|---|---|
entity | Entity | 接收事件的实体 |
type | string | 事件类型(见上表) |
target | Entity | 触发事件的原始实体 |
currentTarget | Entity | 当前正在处理事件的实体(冒泡时可能不同于 target) |
查询事件
// 查询特定类型的所有事件for (const e of events.query('click')) { console.log('点击了:', e.entity);}
// 检查特定实体是否有特定事件if (events.hasEvent(buttonEntity, 'click')) { handleButton();}
// 消费所有事件(返回并清空队列)const all = events.drain();回调订阅
除了每帧轮询事件,还可以使用 .on() 通过回调方式订阅事件。适合在启动系统中进行一次性设置:
import { defineSystem, addStartupSystem, Res, GetWorld } from 'esengine';import { UIEvents, makeInteractable } from 'esengine';
addStartupSystem(defineSystem( [Res(UIEvents), GetWorld()], (events, world) => { const button = findEntityByName(world, 'StartButton');
// 实体级:仅当该实体触发事件时回调 const unsub = events.on(button, 'click', (e) => { console.log('开始按钮被点击!'); });
// 全局:任何实体触发该类型事件时回调 events.on('change', (e) => { console.log('值变更:', e.entity); });
// 调用 unsub() 移除监听器 }));| 重载形式 | 说明 |
|---|---|
events.on(entity, type, handler) | 订阅特定实体的事件,返回 Unsubscribe 函数 |
events.on(type, handler) | 全局订阅某类型的所有事件,返回 Unsubscribe 函数 |
makeInteractable
确保实体拥有 Interactable 组件的工具函数,使用合理的默认值:
import { makeInteractable } from 'esengine';
makeInteractable(world, entity);// 等价于添加 Interactable { enabled: true, blockRaycast: true, raycastTarget: true }在代码中创建交互实体时使用此函数,避免手动导入和插入 Interactable 组件。
事件冒泡
press、release 和 click 事件沿实体层级向上冒泡。如果父级的 Interactable 设置了 blockRaycast: true,冒泡在此停止。hover_enter 和 hover_exit 不会冒泡。
Focusable
启用键盘焦点和 Tab 导航。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
tabIndex | number | 0 | 焦点顺序(越小越早) |
isFocused | boolean | false | 当前焦点状态 |
Draggable
启用 UI 元素的拖拽移动。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | boolean | true | 启用拖拽 |
dragThreshold | number | 5 | 开始拖拽前的像素移动距离 |
lockX | boolean | false | 锁定水平移动 |
lockY | boolean | false | 锁定垂直移动 |
constraintMin | {x, y} | null | null | 拖拽范围最小值(世界坐标) |
constraintMax | {x, y} | null | null | 拖拽范围最大值(世界坐标) |
Draggable 同时支持基于 Transform 和基于 UIRect(锚点布局)的元素。当实体拥有 UIRect 时,拖拽会调整布局偏移量;否则直接移动 Transform 位置。
拖拽激活时,引擎会自动为实体添加 DragState 组件:
| 属性 | 类型 | 说明 |
|---|---|---|
isDragging | boolean | 正在拖拽 |
startWorldPos | {x, y} | 拖拽开始时的实体位置 |
currentWorldPos | {x, y} | 当前世界位置 |
deltaWorld | {x, y} | 本帧移动量 |
totalDeltaWorld | {x, y} | 从拖拽开始的总移动量 |
pointerStartWorld | {x, y} | 拖拽开始时的指针世界位置 |
默认着色
当 Button 或 Toggle 没有显式 transition 时,UI 系统会自动为实体的 Sprite 或 Image 颜色应用基于状态的着色:
| 状态 | 效果 |
|---|---|
| 正常 | 无变化 |
| 悬停 | 1.15 倍亮度 |
| 按下 | 0.75 倍亮度 |
| 禁用 | 0.5 倍亮度,0.6 透明度 |
无需显式定义 ColorTransition 即可获得视觉反馈。
处理管线
UI 交互系统每帧按固定顺序执行:
| 调度阶段 | 系统 | 作用 |
|---|---|---|
| Last(上一帧) | InputPlugin | 清除单帧输入状态 |
| PreUpdate | UIInteractionSystem | 命中测试、悬停、按下、释放、点击 |
| PreUpdate | DragPlugin | 拖拽状态追踪、拖拽事件 |
| Update | ButtonSystem、TogglePlugin、SliderPlugin 等 | 控件状态更新 |