Skip to content

内置 JSX 元素

This content is not available in your language yet.

在末语中,所有的 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 从小到大绘制。较大的值后绘制,因此显示在较小值的上方;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 label="角色层" x={0} y={0}>
<sprite src="characters/alice.png" />
<sprite src="characters/bob.png" />
</container>

容器支持所有通用属性。对容器设置的变换(位移、缩放、旋转等)会影响所有子元素。


按照从上到下的顺序自动排列直接子元素。适合菜单、设置项和纵向按钮组。

<vbox x={100} y={100} gap={24} padding={20}>
<text text="开始游戏" fontSize={32} />
<text text="读取存档" fontSize={32} />
<text text="退出游戏" fontSize={32} />
</vbox>

按照从左到右的顺序自动排列直接子元素。适合工具栏、横向按钮组和并排信息。

<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 布局尺寸变化时触发,事件包含 widthheight

<vbox><hbox> 可以互相嵌套,也可以放进普通 <container>。完整的尺寸、对齐和嵌套规则见布局系统


用于显示图片。这是最常用的元素之一。

<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
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 x={100} y={100} width={400} height={300}>
<sprite src="large_image.png" />
</clip>
属性 类型 说明
width number 裁剪区域宽度
height number 裁剪区域高度

对子元素应用视觉滤镜效果。

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

对元素后方的已有画面内容应用滤镜,类似 CSS 的 backdrop-filter

<backdrop
filters={[{ type: 'blur', radius: 8 }]}
width={1920}
height={1080}
/>
属性 类型 说明
filters FilterKind[] 滤镜列表,结构同 <filter>
width number 区域宽度
height number 区域高度

用于把一个或多个输入通道交给 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> 提供输入通道。你可以把子节点渲染进某个 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


播放序列帧动画(APNG 或 WebP 动画格式)。

<animation src="effects/fire.apng" />
属性 类型 说明
src string 动画文件路径(APNG 或 WebP)
area [number, number, number, number] 裁剪区域(归一化坐标)
format "apng" | "webp" 动画格式

播放视频文件,支持 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>
);
}