MIT 许可证 NuGet

首页 / 功能 / 2D、文本与 UI

2D、文本与 UI

2D 不是铆在旁边的独立框架。精灵、形状和文本与 3D 模型处在同一棵面板树中, 在同一帧的 Overlay 阶段绘制,并按设计分辨率定位——一份写好的布局 在手机上和 4K 显示器上都成立。

控件集

五种类型涵盖了引擎在二维中所绘制的一切。它们都派生自 Control,这意味着它们与每个 3D 控件共享摆放字段、 Enable 标志、异步 Load 契约和 OnClick 处理器。

控件用途
Sprite2D屏幕空间图像,包括按纹理名寻址的实时特效输出。
Sprite3D锚定在世界中的公告板——标记、表情、悬浮图标。
Shape点、矩形、圆、圆角矩形和用于面板装饰的边框。
TextsMSDF 文本:与分辨率无关,由 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 设置边框厚度。 这是一个刻意收窄的集合:足以搭建面板装饰,而不必附带一个矢量图形引擎。

文本只有 MSDF

每个后端上只有一条字形管线。字形在固定的 PixelRange = 4 下栅格化为多通道有符号距离场,并打包进引擎自己的图集,所以文本在任何缩放下都保持锐利, 也永远不会为每个尺寸重建图集。Scale 是一个自由参数, 而不是质量设置。

title = new Texts
{
    Content = "Settings",
    Color = Colors.DarkRed,
    Scale = Vector2.One * 1.2f
};
AddControl(title);
成员说明
Content字符串。赋值即触发布局;ContentOrigin 保留未翻译的原文。
Translate默认开启。在布局前把字符串过一遍 Localization。
ScaleVector2,所以文本可以在单轴上拉伸。锐度零成本。
LineHeight以设计单位计的行进。默认 40。
WidthRequest、HeightRequest换行盒。留空则文本是单行。
WordsSpace、EmptySpace额外字距,以及拉丁文段中一个空格的宽度。
TextsTypeImmediately 或 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 索引在整个字符串中是绝对的, 所以尾部无法单独解析。一旦 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。设计盒被保留。
Scale1。窗口就是画布。两轴比例中较小的那个,使整个设计盒保持可见。
ExtendResolution等于 DeviceResolution。沿较长轴生长的设计盒,覆盖高手机的额外空间。
DeviceResolution永远是真实的交换链尺寸,以像素计。

实用规则:把必须保持可达的一切锚定到 BasicResolution,把背景和边缘装饰拉伸到 ExtendResolution。这样你在一台 20:9 的手机上 也能得到无黑边布局,而无需第二份设计。

调整尺寸只重算 Scale 和 ExtendResolution; BasicResolution 刻意保持不变, 所以拖拽窗口不会重排一份移动端布局。方向变化走单独的事件。

已知缺口

  • 缺失 没有布局系统。 没有 flexbox,没有约束,没有锚点,没有停靠。 位置是你算出来的数字——对 HUD 没问题,对四十行的设置界面则很繁琐。
  • 部分 文本输入委托给平台。 Input 通过 DeviceServices.Dialog.ShowKeyboard 唤起原生键盘。 没有引擎内光标、选区或 IME 组合,所以文本编辑看起来像操作系统, 而不像你的游戏。
  • 缺失 没有无障碍表面。 没有屏幕阅读器树,没有焦点环, 没有控件之间的键盘导航。
  • 缺失 没有标记或样式语言。 span 颜色就是全部。没有富文本 文档模型,没有样式表,没有数据绑定。