跳转到内容

UI 与文本

TextUIRect 组件配合 Sprite 实现游戏中的文本渲染。TextPlugin 会在每帧自动将文本内容转换为纹理。

设置步骤

在场景编辑器中:创建实体 → 添加 LocalTransformSpriteTextUIRect 组件。

  • Text — 定义文本内容和样式(字体、大小、颜色、对齐)
  • UIRect — 定义布局矩形(尺寸、锚点、旋转中心)
  • Sprite — 文本系统内部使用,用于显示渲染后的纹理

Text 属性

属性类型默认值说明
contentstring''显示的文本内容
fontFamilystring'Arial'字体名称
fontSizenumber24字号(像素)
colorColor{r:1, g:1, b:1, a:1}文本颜色 RGBA(0–1)
alignTextAlignLeft水平对齐方式
verticalAlignTextVerticalAlignTop垂直对齐方式
wordWrapbooleantrue启用自动换行
overflowTextOverflowVisible溢出处理模式
lineHeightnumber1.2行高倍率

UIRect 属性

属性类型默认值说明
anchorMinVec2{0.5, 0.5}相对于父级的左下角锚点(0–1 范围)
anchorMaxVec2{0.5, 0.5}相对于父级的右上角锚点(0–1 范围)
offsetMinVec2{0, 0}从 anchorMin 的像素偏移
offsetMaxVec2{0, 0}从 anchorMax 的像素偏移
sizeVec2{100, 100}宽高(anchorMin 等于 anchorMax 时使用)
pivotVec2{0.5, 0.5}旋转和缩放的中心点

anchorMin 等于 anchorMax 时,元素具有固定 size,定位在锚点位置。当两者不同时,元素在两个锚点之间拉伸,sizeUILayoutPlugin 自动计算。

枚举类型

TextAlign

名称说明
0Left左对齐
1Center居中对齐
2Right右对齐

TextVerticalAlign

名称说明
0Top顶部对齐
1Middle垂直居中
2Bottom底部对齐

TextOverflow

名称说明
0Visible文本溢出矩形边界
1Clip在边界处裁剪
2Ellipsis截断处显示省略号

运行时修改文本

在系统中通过 Mut() 查询 Text 组件来更新文本内容或样式:

import { defineSystem, addSystem, Query, Mut } from 'esengine';
import { Text } from 'esengine';
addSystem(defineSystem(
[Query(Mut(Text))],
(query) => {
for (const [entity, text] of query) {
text.content = '已更新!';
text.fontSize = 32;
}
}
));

示例:动态计分板

一种常见模式是在数值变化时更新分数显示:

import { defineComponent, defineTag, defineSystem, addSystem, Query, Mut } from 'esengine';
import { Text } from 'esengine';
const ScoreDisplay = defineTag('ScoreDisplay');
const GameState = defineComponent('GameState', {
score: 0,
});
addSystem(defineSystem(
[Query(Mut(Text), ScoreDisplay), Query(GameState)],
(displayQuery, stateQuery) => {
let score = 0;
for (const [, state] of stateQuery) {
score = state.score;
}
for (const [, text] of displayQuery) {
text.content = `分数:${score}`;
}
}
));

在编辑器中:创建一个文本实体,添加 TextUIRectSpriteLocalTransformScoreDisplay 标签。

UIMask

UIMask 将子实体裁剪到父实体的 UIRect 边界内。任何超出矩形的子实体部分会被视觉裁切。

设置: 在父实体上添加 UIMaskUIRectLocalTransform。所有子实体(及其后代)都会被裁剪到该矩形范围内。

UIMask 属性

属性类型默认值说明
enabledbooleantrue是否启用裁剪

嵌套遮罩

当子实体也拥有 UIMask 时,其裁剪矩形为自身 UIRect 与父级裁剪矩形的交集。这允许嵌套滚动区域或分层裁剪。

示例:可滚动列表

创建遮罩容器,然后移动子实体来实现滚动:

import { defineSystem, addSystem, Res, Input, Query, Mut, LocalTransform, Children } from 'esengine';
import { UIMask } from 'esengine';
import { ScrollList } from './components';
addSystem(defineSystem(
[Res(Input), Query(Children, UIMask, ScrollList), Query(Mut(LocalTransform))],
(input, maskQuery, childQuery) => {
for (const [entity, children, mask, scroll] of maskQuery) {
const scrollDelta = input.getScrollDelta();
for (const child of children.entities) {
const [, transform] = childQuery.get(child);
transform.position.y += scrollDelta * 20;
}
}
}
));

UI 交互

ESEngine 提供了内置的 UI 交互系统,用于处理 UI 元素的悬停、按压和点击事件。

设置

在任何拥有 UIRectLocalTransform 的 UI 实体上添加 Interactable 组件。UIInteractionPlugin(由 createWebApp() 自动注册)每帧执行命中检测并管理交互状态。

Interactable

标记实体为可交互。

属性类型默认值说明
enabledbooleantrue是否启用交互

Button

Interactable 基础上添加状态机,支持可选的颜色过渡。

属性类型默认值说明
stateButtonStateNormal当前按钮状态(运行时只读)
transitionButtonTransition | nullnull颜色过渡配置

ButtonState: Normal (0)、Hovered (1)、Pressed (2)、Disabled (3)

设置 transition 后,状态变化时 Sprite 颜色会自动更新:

import { Button, Interactable } from 'esengine';
world.insert(entity, Button, {
state: 0,
transition: {
normalColor: { r: 1, g: 1, b: 1, a: 1 },
hoveredColor: { r: 0.9, g: 0.9, b: 0.9, a: 1 },
pressedColor: { r: 0.7, g: 0.7, b: 0.7, a: 1 },
disabledColor: { r: 0.5, g: 0.5, b: 0.5, a: 0.5 },
},
});

UIInteraction

由插件自动添加到拥有 Interactable 的实体上,提供逐实体状态:

属性类型说明
hoveredboolean指针悬停在该实体上
pressedboolean实体正在被按压
justPressedboolean本帧开始按压
justReleasedboolean本帧释放按压

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);
}
}
));

事件类型: clickpressreleasehover_enterhover_exit

ScreenSpace

用于根 UI 实体的标签组件。拥有 ScreenSpaceUIRectLocalTransform 的实体会由 UILayoutPlugin 相对于相机边界进行布局。

import { ScreenSpace, UIRect, LocalTransform } from 'esengine';
world.insert(entity, ScreenSpace);
world.insert(entity, UIRect, {
anchorMin: { x: 0, y: 0 },
anchorMax: { x: 1, y: 1 },
offsetMin: { x: 0, y: 0 },
offsetMax: { x: 0, y: 0 },
size: { x: 100, y: 100 },
pivot: { x: 0.5, y: 0.5 },
});

下一步