内置 JSX 元素
このコンテンツはまだ日本語訳がありません。
在末语中,所有的 UI 都使用引擎提供的自定义 JSX 元素构建。这些元素不是 HTML 元素——它们直接对应引擎底层的渲染节点。
所有元素都支持以下属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label |
string |
— | 节点标签(调试用,在开发工具中显示) |
x |
number |
0 |
X 坐标 |
y |
number |
0 |
Y 坐标 |
anchor |
[number, number] |
[0, 0] |
锚点位置(0~1 范围,决定坐标对齐点) |
pivot |
[number, number] |
[0, 0] |
旋转/缩放中心(0~1 范围) |
scale |
number |
1 |
整体缩放 |
scaleX |
number |
1 |
水平缩放 |
scaleY |
number |
1 |
垂直缩放 |
rotation |
number |
0 |
旋转角度(角度制) |
skew |
number |
0 |
整体斜切 |
skewX |
number |
0 |
水平斜切 |
skewY |
number |
0 |
垂直斜切 |
visible |
boolean |
true |
是否可见 |
tint |
string |
"#FFFFFF" |
着色(CSS 颜色值) |
opacity |
number |
1 |
透明度(0~1) |
interactive |
boolean |
false |
是否响应交互事件 |
cursor |
MoyuCursor |
— | 鼠标悬停时的光标样式 |
zIndex |
number |
0 |
同一父节点下的绘制和命中顺序,支持负数 |
设置 interactive={true} 后,元素可以响应以下事件:
// 鼠标事件onClick={(e: MouseEvent) => { ... }}onMouseDown={(e: MouseEvent) => { ... }}onMouseUp={(e: MouseEvent) => { ... }}onMouseMove={(e: MouseEvent) => { ... }}onMouseEnter={(e: MouseEvent) => { ... }}onMouseLeave={(e: MouseEvent) => { ... }}
// 键盘事件onKeyDown={(e: KeyboardEvent) => { ... }}onKeyUp={(e: KeyboardEvent) => { ... }}onKeyPress={(e: KeyboardEvent) => { ... }}
// 触摸事件onTouchStart={(e: TouchEvent) => { ... }}onTouchMove={(e: TouchEvent) => { ... }}onTouchEnd={(e: TouchEvent) => { ... }}onTouchCancel={(e: TouchEvent) => { ... }}末语使用左上角为原点的坐标系,X 轴向右,Y 轴向下。坐标以舞台尺寸为基准(通常为 1920×1080),引擎会自动处理到不同窗口大小的缩放。
绘制顺序与 zIndex
Section titled “绘制顺序与 zIndex”同一父节点下的直接子节点按照 zIndex 从小到大绘制。较大的值后绘制,因此显示在较小值的上方;zIndex 相同时保持 JSX 中的原始顺序,靠后的节点显示在上方。
<container> <sprite src="ui/background.png" zIndex={-1} /> <sprite src="ui/panel.png" /> <text text="置顶提示" zIndex={10} /></container>zIndex 只比较同一个父节点的直接子节点。每个子节点会连同整棵子树一起排序,子树内部的节点不能越过父节点的兄弟节点。如果需要让一个弹出层覆盖组件外部的内容,应在两者共同父节点下,为包含弹出层的直接子节点设置 zIndex。
鼠标、触摸和滚轮事件使用相反的顺序进行命中检测,画面上最靠前的节点会优先收到事件。zIndex 不改变 JSX 顺序、布局位置或布局尺寸。
<container> — 容器
Section titled “<container> — 容器”容器是最基础的布局元素,本身不渲染任何内容,用于组织和分组子元素。
<container label="角色层" x={0} y={0}> <sprite src="characters/alice.png" /> <sprite src="characters/bob.png" /></container>容器支持所有通用属性。对容器设置的变换(位移、缩放、旋转等)会影响所有子元素。
<vbox> — 纵向布局
Section titled “<vbox> — 纵向布局”按照从上到下的顺序自动排列直接子元素。适合菜单、设置项和纵向按钮组。
<vbox x={100} y={100} gap={24} padding={20}> <text text="开始游戏" fontSize={32} /> <text text="读取存档" fontSize={32} /> <text text="退出游戏" fontSize={32} /></vbox><hbox> — 横向布局
Section titled “<hbox> — 横向布局”按照从左到右的顺序自动排列直接子元素。适合工具栏、横向按钮组和并排信息。
<hbox x={100} y={100} gap={16} alignItems="center"> <sprite src="ui/icon.png" /> <text text="设置" fontSize={32} /></hbox><vbox> 和 <hbox> 使用相同的布局属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
width |
number |
自动 | 显式宽度;省略时根据子元素和内边距计算 |
height |
number |
自动 | 显式高度;省略时根据子元素和内边距计算 |
gap |
number |
0 |
相邻子元素之间的间距 |
padding |
number |
0 |
四边内边距 |
paddingX |
number |
— | 水平内边距,覆盖 padding 的左右值 |
paddingY |
number |
— | 垂直内边距,覆盖 padding 的上下值 |
justifyContent |
"start" | "center" | "end" | "space-between" |
"start" |
子元素在排列方向上的对齐方式 |
alignItems |
"start" | "center" | "end" |
"start" |
子元素在另一方向上的对齐方式 |
onLayout |
(event: LayoutEvent) => void |
— | 布局尺寸变化时触发,事件包含 width 和 height |
<vbox> 和 <hbox> 可以互相嵌套,也可以放进普通 <container>。完整的尺寸、对齐和嵌套规则见布局系统。
<sprite> — 图片精灵
Section titled “<sprite> — 图片精灵”用于显示图片。这是最常用的元素之一。
<sprite src="bg/classroom.png" /><sprite src="ui/button.png" x={100} y={200} />| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
src |
string |
— | 图片路径(相对于 assets/) |
mode |
"normal" | "nineslice" |
"normal" |
渲染模式 |
area |
[number, number, number, number] |
— | 裁剪区域(归一化坐标 0~1:[x0, y0, x1, y1]) |
九宫格模式用于制作可拉伸的 UI 元素(如按钮、面板背景),保持四个角不变形:
<sprite src="ui/panel_bg.png" mode="nineslice" bounds={[20, 20, 20, 20]} targetWidth={400} targetHeight={300}/>| 属性 | 类型 | 说明 |
|---|---|---|
bounds |
[left, top, right, bottom] |
九宫格边距(像素) |
nineSliceMode |
"stretch" | "repeat" | "mirror" | "blank" |
中间区域的填充方式 |
targetWidth |
number |
目标宽度 |
targetHeight |
number |
目标高度 |
<text> — 文本
Section titled “<text> — 文本”用于渲染文本内容。支持多种打印效果。
<text text="你好,世界!" fontSize={32} fillColor="#FFFFFF" x={100} y={100}/>| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
text |
string |
"" |
文本内容 |
fontSize |
number |
— | 字号 |
fillColor |
string |
— | 文本颜色(CSS 颜色值) |
printMode |
"instant" | "typewriter" | "printer" |
"instant" |
打印模式 |
printSpeed |
number |
— | 打印速度(typewriter: 字/秒,printer: 行/秒) |
boxWidth |
number |
— | 文本框宽度 |
boxHeight |
number |
— | 文本框高度 |
lineHeight |
number |
— | 行高倍数 |
indent |
number |
— | 段首缩进(像素) |
direction |
"horizontal" | "vertical" |
"horizontal" |
排版方向 |
glyphGridSize |
number |
— | 字形网格大小 |
// 描边效果<text text="带描边的文字" stroke={true} strokeColor="#000000" strokeWidth={2}/>
// 阴影效果<text text="带阴影的文字" shadow={true} shadowColor="#000000" shadowOffsetX={2} shadowOffsetY={2} shadowBlur={4}/>| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
stroke |
boolean |
false |
启用描边 |
strokeColor |
string |
"#000000" |
描边颜色 |
strokeWidth |
number |
2 |
描边宽度 |
shadow |
boolean |
false |
启用阴影 |
shadowColor |
string |
"#000000" |
阴影颜色 |
shadowOffsetX |
number |
0 |
阴影 X 偏移 |
shadowOffsetY |
number |
0 |
阴影 Y 偏移 |
shadowBlur |
number |
0 |
阴影模糊半径 |
shadowWidth |
number |
0 |
阴影宽度 |
<text> 元素在打印过程中会触发事件:
<text text="正在打字……" printMode="typewriter" printSpeed={30} onStart={() => console.log('开始打印')} onProgress={(progress) => console.log(`进度: ${progress}`)} onFinish={() => console.log('打印完成')}/>| 事件 | 类型 | 说明 |
|---|---|---|
onStart |
() => void |
开始打印时触发 |
onProgress |
(progress: number) => void |
打印进度(0~1) |
onFinish |
() => void |
打印完成时触发 |
<text> 元素的文本内容支持内联富文本标签:
<text text="这是<fillColor=#E7931C>橙色文字</fillColor>,<bold>加粗</bold>,<underline>下划线</underline>。" /><clip> — 裁剪
Section titled “<clip> — 裁剪”裁剪容器,子元素只在指定范围内可见。
<clip x={100} y={100} width={400} height={300}> <sprite src="large_image.png" /></clip>| 属性 | 类型 | 说明 |
|---|---|---|
width |
number |
裁剪区域宽度 |
height |
number |
裁剪区域高度 |
<filter> — 滤镜
Section titled “<filter> — 滤镜”对子元素应用视觉滤镜效果。
<filter filters={[{ type: 'blur', radius: 4 }]}> <sprite src="bg/photo.png" /></filter>| 属性 | 类型 | 说明 |
|---|---|---|
filters |
FilterKind[] |
滤镜列表(可叠加多个) |
FilterKind 是一个联合类型,每种滤镜的结构如下:
| type | 参数 | 说明 |
|---|---|---|
'blur' |
radius: number, continuous?: boolean |
高斯模糊 |
'blur-perfect' |
radius: number |
精确模糊(质量更高,性能较低) |
'brightness' |
amount: number |
亮度(1 为原始值) |
'contrast' |
amount: number |
对比度(1 为原始值) |
'saturation' |
amount: number |
饱和度(1 为原始值) |
'hue-rotate' |
degrees: number |
色相旋转(角度) |
'grayscale' |
amount: number |
灰度(0~1) |
'sepia' |
amount: number |
复古效果(0~1) |
'invert' |
amount: number |
反色(0~1) |
<backdrop> — 背景滤镜
Section titled “<backdrop> — 背景滤镜”对元素后方的已有画面内容应用滤镜,类似 CSS 的 backdrop-filter。
<backdrop filters={[{ type: 'blur', radius: 8 }]} width={1920} height={1080}/>| 属性 | 类型 | 说明 |
|---|---|---|
filters |
FilterKind[] |
滤镜列表,结构同 <filter> |
width |
number |
区域宽度 |
height |
number |
区域高度 |
<shader> — 着色器容器
Section titled “<shader> — 着色器容器”用于把一个或多个输入通道交给 shader 处理。它既可以承载内建的转场效果,也可以直接运行自定义 WGSL 片元着色器。
<shader shader={{ type: 'builtin', name: 'crossfade' }} timeControl="transition" displayChannel={1}/><shader> 通常与 <shader-slot> 配合使用,由各个 slot 提供输入纹理。完整的 props、命令、事件和 WGSL 约定见着色器 API。
默认情况下,<shader> 的布局尺寸取所有 space="normal"、且非空 slot 内容尺寸的最大值,因此可以作为 <vbox> 或 <hbox> 的普通子节点。也可以用 width / height 显式指定任一轴;未指定的轴仍按内容自动计算。
<shader-slot> — 着色器输入槽位
Section titled “<shader-slot> — 着色器输入槽位”用于为 <shader> 提供输入通道。你可以把子节点渲染进某个 channel,也可以声明一个空 channel 供 shader 按需读取。
<shader> <shader-slot channel={0}> <sprite src="bg/day.png" /> </shader-slot> <shader-slot channel={1}> <sprite src="bg/night.png" /> </shader-slot></shader>常见用法还包括:
- 设置
static={true},把明确不会变化的输入当作静态纹理使用。 - 设置
space="shader",让子节点以 slot 自身局部原点参与采样,适合遮罩规则图这类局部输入。 - 设置
empty={true}并提供width/height,声明一个没有子内容的空通道。
<shader-slot> 自身始终占用 0 × 0 布局空间,不参与父级的普通布局测量。它只负责把子内容提供给 <shader>:直接子节点的 anchor 会以这个 0 × 0 空间为基准;子树更深层的节点仍按各自父节点的实际尺寸布局。
未占用的 channel 会自动使用 dummy texture 填充。详细说明见着色器 API。
<animation> — 动画
Section titled “<animation> — 动画”播放序列帧动画(APNG 或 WebP 动画格式)。
<animation src="effects/fire.apng" />| 属性 | 类型 | 说明 |
|---|---|---|
src |
string |
动画文件路径(APNG 或 WebP) |
area |
[number, number, number, number] |
裁剪区域(归一化坐标) |
format |
"apng" | "webp" |
动画格式 |
<video> — 视频
Section titled “<video> — 视频”播放视频文件,支持 VP9(WebM/MP4)和 AV1(MP4)编码格式,支持音视频同步。视频加载完成后默认自动开始播放。
<video src="video/opening.mp4" />
{/* 循环播放背景视频,静音 */}<video src="video/bg_loop.webm" loop muted />
{/* 控制音量,不自动播放 */}<video src="video/scene.mp4" volume={0.5} autoPlay={false} />| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
src |
string |
— | 视频文件路径(相对于 assets/) |
loop |
boolean |
false |
是否循环播放 |
autoPlay |
boolean |
true |
加载完成后是否自动开始播放 |
volume |
number |
1 |
音量(0~1) |
muted |
boolean |
false |
是否静音 |
| 属性 | 类型 | 说明 |
|---|---|---|
onEnded |
() => void |
视频播放完毕时触发(仅在非循环模式下) |
onStateChange |
(state: string) => void |
播放状态变化时触发,state 为 "idle" "loading" "playing" "paused" "stopped" "ended" "error" |
以下是一个实际的 UI 组合示例,展示如何用这些元素构建界面:
function SimpleDialog() { return ( <container label="对话框"> {/* 模糊背景 */} <backdrop filters={[{ type: 'blur', radius: 6 }]} width={1920} height={1080} />
{/* 对话框面板 */} <sprite src="ui/dialog_bg.png" mode="nineslice" bounds={[24, 24, 24, 24]} targetWidth={800} targetHeight={400} x={560} y={340} > {/* 标题文字 */} <text text="提示" fontSize={36} fillColor="#FFFFFF" x={40} y={30} />
{/* 内容文字 */} <text text="确定要退出游戏吗?" fontSize={28} fillColor="#CCCCCC" x={40} y={120} /> </sprite> </container> );}
