2D 不是铆在旁边的独立框架。精灵、形状和文本与 3D 模型处在同一棵面板树中, 在同一帧的 Overlay 阶段绘制,并按设计分辨率定位——一份写好的布局 在手机上和 4K 显示器上都成立。
五种类型涵盖了引擎在二维中所绘制的一切。它们都派生自
Control,这意味着它们与每个 3D 控件共享摆放字段、
Enable 标志、异步 Load
契约和 OnClick 处理器。
| 控件 | 用途 |
|---|---|
Sprite2D | 屏幕空间图像,包括按纹理名寻址的实时特效输出。 |
Sprite3D | 锚定在世界中的公告板——标记、表情、悬浮图标。 |
Shape | 点、矩形、圆、圆角矩形和用于面板装饰的边框。 |
Texts | MSDF 文本:与分辨率无关,由 Scale 缩放,可选自动翻译。 |
Texture | 引擎侧的纹理句柄,用于自定义上传和共享引用。 |
精灵。 SpriteBase 携带两种精灵
类型都需要的字段:Color 着色、Ext
文件扩展名、OriginWidth 和
OriginHeight 源像素尺寸,以及
SourceX/Y/Width/Height 用于从精灵图集里画出一个单元格。
Clock 推进精灵图集帧,
FlipX 和 FlipY 镜像它,
TextureOverride 接受一次一次性上传——文件路径或
已解码的 RGBA8 像素——下一次更新会消费并清空它。
显示 compute 写出的内容。 compute 特效产出的存储纹理注册在
compute:// 名下,一个
Sprite2D 可以直接指向它。这就是整个
调试视图机制:没有回读,没有拷贝,没有特殊控件类型。
// A normal image.
var badge = new Sprite2D { Name = "Assets/badge", Ext = ".png", PosX = 24, PosY = 24 };
// The ambient-occlusion buffer, on screen, as a sprite.
var aoView = new Sprite2D
{
Name = Season.Rendering.Effects.GtaoEffect.TextureName,
PosX = 20, PosY = 580, Width = 240
};
体积是例外。三维输出使用
compute3d:// 前缀,住在单独的后端
字典里,所以精灵不能直接显示它——必须先由切片内核把它降到 2D,
这正是 Sdf3DViewEffect 的用途。
公告板。 Sprite3D 生活在世界空间中,
与透明通道一起排序。Mode 决定它如何朝向
相机:Spherical 在两个轴上转向、始终面对你,
Cylindrical 只绕世界 Y 轴转,让树或名牌
保持直立,而 None 则把朝向完全交给
四元数 Rotation。
形状。 ShapeType 是
Dot、Square、
Circle、RoundRect、
RectFrame、Gradual 和
GradualCircle。圆和圆角矩形是解析式抗锯齿,
而不是缩放位图,Border 设置边框厚度。
这是一个刻意收窄的集合:足以搭建面板装饰,而不必附带一个矢量图形引擎。
每个后端上只有一条字形管线。字形在固定的 PixelRange = 4
下栅格化为多通道有符号距离场,并打包进引擎自己的图集,所以文本在任何缩放下都保持锐利,
也永远不会为每个尺寸重建图集。Scale 是一个自由参数,
而不是质量设置。
title = new Texts
{
Content = "Settings",
Color = Colors.DarkRed,
Scale = Vector2.One * 1.2f
};
AddControl(title);
| 成员 | 说明 |
|---|---|
Content | 字符串。赋值即触发布局;ContentOrigin 保留未翻译的原文。 |
Translate | 默认开启。在布局前把字符串过一遍 Localization。 |
Scale | Vector2,所以文本可以在单轴上拉伸。锐度零成本。 |
LineHeight | 以设计单位计的行进。默认 40。 |
WidthRequest、HeightRequest | 换行盒。留空则文本是单行。 |
WordsSpace、EmptySpace | 额外字距,以及拉丁文段中一个空格的宽度。 |
TextsType | Immediately 或 FadeIn,用于文本渐显而非弹出。 |
Color、Alpha | 逐控件颜色和不透明度。 |
Append(string) | 追加一个片段并只排版尾部。见下文。 |
内联颜色。 Content 里理解一小套 span 标记,
足以高亮一个词,而不必把一个段落拆成三个控件。
log.Content = "Build <span style='color:Red;font-weight:bold;'>failed</span> in 2.4s";
流式文本。 Append 存在是因为 token 流存在。
它只针对当前行开始处的布局状态检查点排版新尾部,所以增量追加与完整重排
逐像素一致,包括整词前瞻。这就是语言模型的输出能以 token 速率落到
Texts 控件里、而无需每秒重建段落六十次的原因。
一个颜色作用域可能跨过块边界,而 span 索引在整个字符串中是绝对的,
所以尾部无法单独解析。一旦 span 标记出现在
Content 里,追加就会回退为完整重排,
直到下一次整串赋值。流式文本和内联颜色各自都支持;
两者结合时,你要为颜色付出代价。
断行是文字体系感知的:CJK 在字符之间断行,包括住在主统一区块之外、
不能作为行首的那些标点;拉丁文按整词断行。字体通过
Font.CreateAsync(fileName, size) 加载,
多个字体可以同时常驻,字形度量按字体大小和码点缓存。
Panel 是 2D 和 3D 共用的组合单元。它持有一列子
Panels 和一列
Controls,一个屏幕、一个 HUD 或整个场景都在这里组装。
AddControl 和
AddPanel 定义加载顺序;
Layer 和 Order 定义绘制
顺序。OnClose 在面板消失时触发,
SetMode 让面板无需拆解即可重新配置自己。
| 现成面板 | 你能得到什么 |
|---|---|
BoardPanel | 带边框的底板,FrameColor 可设置。大多数浮层的背景。 |
FrameButton | 文本按钮,底色和文字分别有普通态和悬停态颜色。 |
ImageView | 缩略图,带可选的清除入口;同一文件里的 FullView 是展开形态。 |
Input | 文本框,带描述、对齐、缩写和可选清除按钮。 |
MovePanel | 可滚动、可滑动的容器,内边距、尺寸和运动类型可配置。 |
Picker、SimplePicker | 基于 List<EData> 的单选或多选列表,带悬停颜色和 OnSelect。 |
ObjectPicker | 不是小部件——是 3D 拾取与编辑面板。参见 拾取与编辑。 |
每个控件都接受一个 OnClick 和一个
OnTouch 处理器,
MouseOver 也为你维护好了。没有单独的 UI
事件系统,没有冒泡模型,没有需要学习的命令模式:一个按钮就是一个
挂了委托的面板。
绘制顺序是显式的。实现
IRenderOrder 的控件和面板按
Layer、然后 Order、然后
插入索引排序——一个稳定的全序:浮层元素之间没有 z-fighting,
列表重建时也没有意外。
RenderDomain 决定控件在帧中的何时被绘制,
而不是它在树里的位置。
RenderDomain | 含义 |
|---|---|
Inherit | 跟随父级。默认值,几乎总是你要的。 |
Scene | 与 3D 场景一起绘制,在后处理之前。受色调映射、TAA 及链条其余部分影响。 |
Overlay | 在 Overlay 通道中绘制,位于一切之后。不受后处理影响,这正是 UI 保持锐利的原因。 |
把一个 2D 控件设为 Scene 偶尔恰恰是对的
——世界里的叙事屏幕应该和别的东西一起被泛光和调色。
把一个 HUD 设为 Scene 则会意外地让你的文字
在 TAA 下重影。
你针对 DesignResolution 布局,默认 1280×720。
接下来发生什么取决于平台,这个差别值得了解,
因为它是 2D 模型唯一在各处不完全一致的地方。
| 属性 | 桌面与 Web | 手机与平板 |
|---|---|---|
BasicResolution | 等于 DeviceResolution。你在用真实像素布局。 | 等于 DesignResolution。设计盒被保留。 |
Scale | 1。窗口就是画布。 | 两轴比例中较小的那个,使整个设计盒保持可见。 |
ExtendResolution | 等于 DeviceResolution。 | 沿较长轴生长的设计盒,覆盖高手机的额外空间。 |
DeviceResolution | 永远是真实的交换链尺寸,以像素计。 | |
实用规则:把必须保持可达的一切锚定到
BasicResolution,把背景和边缘装饰拉伸到
ExtendResolution。这样你在一台 20:9 的手机上
也能得到无黑边布局,而无需第二份设计。
调整尺寸只重算 Scale 和
ExtendResolution;
BasicResolution 刻意保持不变,
所以拖拽窗口不会重排一份移动端布局。方向变化走单独的事件。
Input 通过
DeviceServices.Dialog.ShowKeyboard 唤起原生键盘。
没有引擎内光标、选区或 IME 组合,所以文本编辑看起来像操作系统,
而不像你的游戏。