主题
Tiled TMX 地图
GameGraph.ResourceSystem.TmxFile2D 在客户端读取 Tiled 导出的 XML 地图资源。Load 加载文件、图块集和图片;将结果赋给场景节点上的 TileMap2DComponent.TmxFile 后,组件才会创建地图图层供相机显示。
地图、场景与相机
TMX 文件保存地图数据,本身不包含或创建相机。GameGraph 将瓦片和精灵作为场景对象放在 SceneGraph 中,使用同一套通用 CameraComponent 观察;该相机支持正交和透视投影,不需要另找一个“TMX 专用相机”。
TmxMapSample 把地图放在 XY 平面,使用未旋转、沿 +Z 方向观察的相机,设置 IsOrthographic = true 与 OrthoSize,得到没有透视缩放的二维画面。“使用场景相机”不等于“使用透视三维画面”。
相机的渲染投影与 GameSystemUI 的输入方向补偿是两件事。正交相机也有空间朝向;本例沿 Z 轴观察,正前方向的 XY 投影为零,默认输入补偿会回退到 SCE 游戏镜头。因此样例直接转换屏幕输入,具体规则见移动控件的输入坐标与镜头补偿。
二维渲染与颜色一致性
通用原理、Bloom/ACES 色调映射影响、可复制的二维 RenderPath 和 C# 视口管理样例见二维渲染路径与颜色指南。AI 开发时同时使用 gamegraph-2d-rendering skill。以下说明本 TMX 样例的选择。
SceneGraphMainViewportController.Bind / Renderer.SetupMainViewport 替换场景和相机,保留宿主视口的 RenderPath。独立场景不会自动隔离 Bloom、色调映射等全屏后处理;这些处理还可能读取 Renderer 的默认 Zone。默认 Urho2D 材质不受普通 3D 灯光照亮,但仍可能被后处理改变颜色。像素地图不需要为此创建 LightGroup,应按需要显式选择二维 RenderPath。
TmxMapSample 使用项目内两份路径,仅清屏并绘制 alpha 精灵 pass,不执行 SSAO、Bloom、雾或色彩后处理。颜色处理约定如下:
| 阶段 | PIE 内嵌画布 | 独立客户端窗口 |
|---|---|---|
| PNG 采样 | 同名纹理 XML 设置 srgb=true,解码到线性空间 | 同左 |
| 精灵绘制 | 在线性空间合成 | 在显式 rgba16f 线性缓冲中合成,避免中间缓冲精度损失 |
| 最终显示 | RenderPath 保留线性输出,由编辑器 UI 做 Gamma 转换 | GammaCorrection 从线性缓冲直接输出到屏幕 |
使用 Renderer.IsEditorDebugViewportActive 判断实际是否渲染到编辑器内嵌画布;由编辑器启动的独立客户端仍返回 false。不能按 Game.IsClientOnly 判断颜色出口,也不能仅凭更亮或更鲜艳判断正确性:应以原始 PNG 和目标视觉设计为参照,避免少做或重复做 Gamma 转换。此例沿用正常游戏的后处理启用设置;关闭后处理的诊断模式和压缩纹理的平台支持应另行验证。
接管前用 Viewport.GetRenderPath() 保留原路径,退出时恢复并释放所保留的引用;controller 只恢复场景与相机,不负责恢复项目手动改变的 RenderPath。请分别验收 PIE、独立客户端和先运行 3D 场景再运行二维样例的颜色。窗口宽高比不同导致地图可见范围变化,属于相机取景,与颜色转换是不同问题。
文件、地图与输入坐标
本节针对普通有限正交地图,即 orientation="orthogonal"。需要区分文件中的位置、加载后的位置、瓦片索引和移动方向:
| 数据 | 原点或零值 | X 正方向 | Y 正方向 | 单位 |
|---|---|---|---|---|
| Tiled/TMX 文件中的像素位置 | 地图左上角 (0,0) | 向右 | 向下 | 像素 |
| 加载后的地图局部位置 | 地图左下角 (0,0) | 向右 | 向上 | 引擎坐标,当前 1 像素 = 0.01 单位 |
瓦片索引,例如 GetTile(x,y) | 左上角瓦片 (0,0) | 列号向右增加 | 行号向下增加 | 瓦片索引 |
GameSystemUI MoveInputEventArgs.Direction | 摇杆居中/停止为零向量 | 右推/D 为正 | 下推/S 为正,上推/W 为负 | 方向分量,不是位置 |
因此,不能把“TMX 本身默认左下原点、Y 向上”作为文件格式约定:**文件仍然是左上原点、Y 向下,是加载器进行了转换。**摇杆沿用 UI 屏幕的轴方向,但 Direction 的 (0,0) 表示没有移动,不是 UI 左上角的位置。
地图位置的转换
对于文件中的点 (xPx,yPx),加载器使用:
text
xLocal = xPx * 0.01
yLocal = mapHeightLocal - yPx * 0.01苔湾地图高 32×32 = 1024 像素,局部高度为 10.24。文件点 (64,96) 对应局部点 (0.64,9.28)。矩形对象的文件位置是左上角,而 TileMapObject2D.Position 返回转换后的左下角,因此文件矩形 (x,y,w,h) 的局部位置为 (x*0.01, mapHeightLocal-(y+h)*0.01),Size 为 (w*0.01,h*0.01)。
这些公式用于解释加载器行为。通过 TmxFile2D / TileMap2DComponent 读取的对象位置和尺寸已经转换,项目不能再乘 0.01 或再翻转 Y。瓦片索引则继续采用 Tiled 的上到下行序,不要把索引当成局部位置。
地图局部坐标还不等于任意场景的世界坐标:样例的地图节点没有平移、旋转或缩放,所以两者重合;如果项目改变地图节点的变换,需要再按节点变换转为世界坐标。世界原点不会始终自动落在地图左下角。
移动方向的转换
本例相机使画面右方对应局部/世界 +X,上方对应 +Y。将原始屏幕输入用于移动时,只翻转方向的 Y 分量:
csharp
Vector2 worldDirection = new(e.Direction.X, -e.Direction.Y);W/上推 (0,-1) 转为 (0,+1),S/下推 (0,+1) 转为 (0,-1);A/D 的 X 分量保持不变。方向向量没有地图位置,不需要加减地图高度,也不需要乘像素比例。W/S 反向的直接原因是混用了 Y 正方向不同的输入与地图坐标,原点从左上移到左下本身并不决定移动方向。
上述做法使用原始 Direction。WorldDirection 已经经过 GameSystemUI 的默认镜头补偿,不能把它当作同一个原始向量再套用固定翻转;固定正交视图和旋转相机的选择见输入坐标与镜头补偿。
客户端能力
TMX 1.x 普通有限地图(包括 1.11)、Base64 zlib/gzip/zstd 图层数据和外部 TSX 相对图片路径需要客户端能力 TmxFile2DModernLoading,最低能力版本为 7。API 签名保持不变,但旧客户端没有这些格式读取能力。项目在初始化时判断一次,再选择是否加载此类地图或提示更新客户端:
csharp
#if CLIENT
using GameCore.ClientEnvironment;
bool canLoadModernTmx = ClientEnvironment.Runtime.Supports(
ClientRuntimeFeature.TmxFile2DModernLoading);
#endif旧客户端与未知客户端会返回 false。能力判断描述客户端的读取能力,不保证具体文件正确或依赖齐全。详见客户端二进制兼容与能力版本。
资源路径与显示
把地图及依赖放在项目 res/ 中,代码路径从 res/ 下一级开始:
text
MyProject/res/tmx/
maps/level.tmx
tilesets/terrain.tsx
images/terrain.pnglevel.tmx 的图块集引用可以是 ../tilesets/terrain.tsx;terrain.tsx 的图片引用可以是 ../images/terrain.png。内嵌图块集和图片图层的图片路径相对于 TMX,外部图块集的图片路径相对于 TSX。项目资源随客户端包封装,无需额外生成云资源引用清单。
在已准备好相机和视口的客户端 SceneGraph 中:
csharp
#if CLIENT
using GameCore.ClientEnvironment;
using GameGraph.NodeSystem.Component.Graphics;
using GameGraph.ResourceSystem;
if (ClientEnvironment.Runtime.Supports(ClientRuntimeFeature.TmxFile2DModernLoading))
{
var tmx = TmxFile2D.Load("tmx/maps/level.tmx");
if (tmx is not null)
{
var mapNode = sceneGraph.CreateChild("TileMap");
var tileMap = mapNode.CreateComponent<TileMap2DComponent>();
if (tileMap is not null)
tileMap.TmxFile = tmx;
}
}
#endifLoad 失败时返回 null,原生日志会记录版本、图层数据或资源依赖错误。成功加载不代表地图已显示;还需把组件挂到节点上并由正确的相机和视口观察。
图层数据格式
| 编码 | 压缩 | 支持情况 |
|---|---|---|
| XML 瓦片元素 | 无 | 支持旧文件 |
| CSV | 无 | 支持 |
| Base64 | 无、zlib、gzip、zstd | 支持;解码后每个瓦片是 4 字节小端 GID |
这里的压缩是瓦片编号数据压缩,与 PNG、ASTC 等图片格式无关。加载器会检查瓦片数量、Base64 数据和解压长度,并保留 GID 中的翻转标记。未知编码或压缩类型会报错。
当前边界
本次支持普通有限地图和既有瓦片、对象、图片图层,不代表实现了 TMX 1.11 的全部特性。无限地图与 chunk、分组图层、动画瓦片、对象模板、文本/点/胶囊对象目前会被明确拒绝。图层混合、视差、透明度等完整 Tiled 渲染语义尚未纳入本次能力保证。
地图仅用于客户端表现;它不会自动成为星火地形,也不会自动生成服务端导航、碰撞或多人同步。
使用 GameSystemUI 操控固定 XY 正交视图中的角色时,输入和地图坐标要分别处理:MoveInputEventArgs.Direction 的屏幕 Y 向下,应映射为 (X, -Y);TileMapObject2D.Position 等元数据已经转换为地图局部坐标,不再翻转。默认 WorldDirection 在此类相机下可能回退到 SCE 游戏镜头方位,详见移动控件的输入坐标与镜头补偿。
完整样例
code_sample 模板中的 src/TmxMapSample/ 提供苔湾小镇探索小游戏。它使用 Maker 生成的 TMX、TSX 与精灵,从对象层读取出生点、阻挡矩形、区域介绍和六枚水晶,用 TileMap2DComponent 渲染地图、StaticSprite2D 渲染角色和拾取物,并复用 GameSystemUI 键盘/摇杆输入。启动时检查 TmxFile2DModernLoading:能力版本 7 及以上加载 Mossbay.tmx(TMX 1.11 + Base64 zlib);未提供该能力的客户端加载 MossbayLegacy.tmx(TMX 1.0 + 未压缩 Base64)。两份地图的图块与玩法数据一致,共用同目录的 TSX 和图片。这是文件格式兼容分支,其它 API 与渲染路径仍需按目标客户端验证。详细运行方法和生命周期见该目录的 README.md;这是客户端本地玩法示例。