1. 从一块空白画布开始:Tiled到底在解决什么问题
第一次打开Tiled的人,十有八九会愣住:左边一堆图层,右边一个巨大灰白格子,中间工具栏图标看着都眼熟但不知道点哪个。很多人第一反应是"我是不是该先学个教程",然后去搜视频,看博主飞快地点了一通,回来自己还是不知道从哪下手。我当年也是这样,折腾了大半天,最后做出来的地图导进引擎只有一半能显示——原因后面会讲。所以这篇不打算做那种"功能大全"式的罗列,而是老老实实按一条能走通的路,从空画布一路做到一张导出后能直接拿去做碰撞、能挂对象、能分层渲染的地图。
先把Tiled是什么说清楚。Tiled是一款开源的2D关卡/地图编辑器,核心定位是"只管编辑、不管运行"——它不负责渲染逻辑、不负责物理、不负责游戏循环,只负责把地图数据整理成结构化的文件交给你。它支持正交(Orthogonal)、等距(Isometric)、六边形(Hexagonal)三种主流瓦片排布,支持瓦片层(Tile Layer)、对象层(Object Layer)、图像层(Image Layer)混合叠加,导出格式涵盖 TMX/TSX(XML)、JSON、CSV、Lua 等,几乎所有主流 2D 引擎都有现成加载器。
它解决的核心痛点是:把"摆场景"这件事从代码里解放出来。没有它,你要么在代码里手写几百行tiles[23][45] = 7,要么做一个简陋的内部编辑器;有了它,美术出的图集直接切瓦片,策划能自己拖场景,程序只需要解析文件。这个分工对独立开发者尤其重要——你一个人干三个人的活时,工具链的顺手程度直接决定项目能不能做下去。
适合谁看:有一点引擎基础(知道 Sprite、图集、坐标系)、但没正经用过Tiled的人;或者用过但只会画不能分层、不会挂对象、导出后总要手动修数据的人。零引擎基础也能跟,因为我会把每一步在干什么、为什么这么干都说清楚。全文用一张虚构的"某横版探险Demo"地图作为主线,尺寸定在40×25 瓦片、单瓦片 32×32 像素,也就是 1280×800 像素的地图区,这是最典型的桌面端 2D 场景规格,各种参数设置都以它为准。
提示:Tiled 的版本更新比较快,界面细节会有出入,本文所有操作用的是 1.10 之后的通用布局。如果你手里的版本菜单位置不一样,按名字找即可,逻辑没变。
2. 开工前的三件事:图集规格、网格对齐与工程目录
很多人跳过这一步直接开画,画到一半发现瓦片边缘有条缝、或者吸附怎么都对不齐,然后回过头返工。这一步花十分钟,能省掉后面两小时。
2.1 图集不是随便一张图:瓦片尺寸与留边规则
Tiled 的瓦片来源有两种:基于图集的瓦片集(Tileset from Image)和单张图片瓦片集。前者是把一张大图按固定网格切开,这是绝大多数项目的做法,因为省 Draw Call、方便统一管理。
关键在三个参数:
| 参数 | 含义 | 本例取值 | 设错的后果 |
|---|---|---|---|
| Tile width / height | 单个瓦片像素尺寸 | 32 × 32 | 切片错位,图案斜切 |
| Margin | 图集最外圈留白 | 0 或 2 | 有留白却填0,全图偏移 |
| Spacing | 瓦片之间的间隙 | 0 或 2 | 有间隙却填0,瓦片串边 |
我见过最常见的翻车是:美术导出的图集每块瓦片之间留了 2px 透明间隙(为了防止相邻瓦片采样时颜色渗透),但建瓦片集时 Margin 和 Spacing 全填了 0,结果每切一格就偏一点,画到第 20 列已经完全错位。判断方法很简单:在图像软件里量一下"第一块瓦片的左上角坐标"就是 Margin,"两块瓦片左上角的距离减去瓦片宽"就是 Spacing。
还有一个隐性坑:瓦片尺寸最好与游戏内渲染尺寸一致,或者是整数倍关系。如果你的瓦片是 32×32,但游戏里以 1.5 倍缩放渲染,又没开纹理的最近邻采样,就会出现瓦片接缝处的细线。这不是 Tiled 的问题,是采样问题,但在 Tiled 阶段就该定死规格,别等到渲染阶段才发现。
2.2 网格与吸附:先让画布"对得上"
新建地图(File → New → New Map)时,这几个选项必须一次设对,因为地图的瓦片尺寸事后改起来非常麻烦:
- Orientation:选 Orthogonal(正交)。俯视角、横版、平台跳跃都用它。Isometric 是斜 45° 的伪 3D 视角,Hexagonal 是六边形战略图,初学者别碰。
- Tile size:32 × 32,与图集一致。
- Map size:40 × 25(单位是瓦片数,不是像素)。
- Infinite:不要勾。无限地图适合超大地图或程序化拼接,但它导出的是分块(chunk)结构,很多引擎的加载器不支持,或者需要额外处理。做第一张图,固定尺寸最稳。
- Tile render order:Right Down(默认)。它决定瓦片绘制顺序,除非你在做特殊视角,否则不改。
建完后打开 View → Show Grid 和 Snapping → Snap to Grid。吸附一定要开,否则手一抖瓦片就落在非整数格上,导出后引擎里会出现"半个像素"的诡异偏移,这种 bug 排查起来极其痛苦,因为肉眼几乎看不出来,但角色走过去会突然卡一下。
2.3 目录结构:现在乱,后面必乱
Tiled 的工程文件(.tmx)会以相对路径引用瓦片集文件和图片。这意味着如果你把 tmx 挪到别的文件夹,图片就全丢了,打开是一片红叉。
我用的结构是这样的,从项目第一天就固定:
assets/ maps/ demo-level-01.tmx tilesets/ terrain.tsx props.tsx textures/ terrain-32.png props-32.png.tsx是独立的瓦片集文件,把瓦片集从地图里拆出来单独存的好处是:多张地图可以共用同一个瓦片集,改一次全生效。如果瓦片集内嵌在 tmx 里,每张地图都得重复配置一遍,纯属自找麻烦。
注意:瓦片集引用的是图片路径,图片路径又是相对于 tsx 的。所以 tsx 和图片的相对位置也要固定。我习惯把 tsx 和对应图片放在同一层目录,路径最短,最不容易出错。
3. 第一层地面怎么铺:用图层语义而不是"想到哪画到哪"
新手最典型的画法是:所有东西全塞在一个图层里,地面、草丛、石头、装饰全在 Tile Layer 1。图能看,但一进引擎就抓瞎——你想让角色走到草丛后面,做不到;想给石头单独加碰撞,得靠坐标硬编码。
正确的做法是按"渲染顺序 + 交互属性"分层,而不是按"我画到哪了"分层。
3.1 推荐的四层骨架与它们的职责
对一张横版或俯视角地图,我基本固定用这套结构(图层从上到下即渲染从后到前,Tiled 里列表越靠上越先画):
| 图层名 | 类型 | 职责 | 是否参与碰撞 |
|---|---|---|---|
| background | Tile Layer | 天空、远山、纯背景色块 | 否 |
| ground | Tile Layer | 地面、地板、可站立面 | 通常否(用对象层标) |
| decoration | Tile Layer | 草丛、石堆、贴地装饰 | 否 |
| collision | Object Layer | 墙、障碍、触发区、出生点 | 是 |
为什么把 decoration 放 ground 之上?因为角色需要能"走到草丛前面被遮住一半"的效果时,你得再拆一层 foreground。这里先留个接口意识:图层是可以随时增删和调序的,别一开始就想设计完美,先跑通,再按实际遮挡需求拆。
另一个常被忽略的点:图层的偏移(Offset)和视差(Parallax)。background 层如果做远景,可以在图层属性里设parallaxx = 0.5,这样镜头移动时它只移动一半距离,立刻就有纵深感。这个属性是 Tiled 原生支持的,导出后引擎读取即可,不需要写代码。做横版游戏时这一条能极大提升观感,成本却几乎是零。
3.2 用"地形刷"而不是一个个点:Terrain 与 Wang 瓦片
手工一格一格点地面,画一张 40×25 的图要点上千次,还容易漏格。Tiled 内置了地形工具(Terrains),本质是基于"Wang 瓦片"(也叫边缘匹配瓦片)的自动拼接:你告诉它"这块瓦片是草地中心、这块是左边缘、这块是左上角",然后在地图上刷,它会自动选择正确的边缘瓦片。
配置路径:选中瓦片集 → 点瓦片集面板下方的"Terrains"标签 → 新建一个 Terrain(比如叫 Grass)→ 然后逐块给瓦片打上"属于哪个地形 + 处于什么位置"的标记。
这一步的真正门槛不在操作,而在图集本身是否规整。如果你的草地瓦片是"随意拼的 16 格",没有标准的 9 宫格或 47 格布局,Terrain 配置会非常痛苦。业界常用的是47 瓦片布局(包含所有内外角组合),也有简化版16 瓦片布局(只处理边缘不处理内角)。做第一张图,建议用 16 格简化版,够用且好配。
如果图集不规整、或者你不想配 Terrain,还有一条路:用矩形选择 + 填充。快捷键R切矩形选择,B用图章刷,或者选中一片区域后按Ctrl+F做区域填充。画大面积纯色地面时,区域填充比刷子快十倍。
3.3 一个让我返工两次的细节:地面不是"一层"
假设你的地面只有一层瓦片,那"地面上的坑"就没法表现——坑需要露出下面的背景,也就是地面层在那几格必须是空的。这没问题。但"地面上有一块略高的台地"呢?台地的侧面需要额外瓦片,而且它要盖住下面地面的边缘。
解决办法是把地面拆成ground-base(最底层填充)和ground-top(表面与侧面装饰)两层。base 层永远填满,保证没有空洞;top 层只在需要表现高差的地方画。这样导出后,引擎里做"踩地判定"只需要读 base 层的碰撞数据,视觉和逻辑解耦。
实测下来,这个拆法让我后面接物理时省了大量事——碰撞数据永远来自一个语义明确的层,而不是"从视觉层里猜哪里是地板"。
4. 让地图"活"起来:对象层、碰撞体与自定义属性
瓦片层解决"看起来是什么",对象层解决"它是什么"。这是 Tiled 从画图工具变成关卡数据工具的分水岭,也是绝大多数新手卡住的地方。
4.1 对象层里的四种东西:矩形、多边形、点、瓦片对象
在对象层里,你可以放四种对象:
- Rectangle(矩形):最常用,做墙体、碰撞箱、触发区域。按住
Ctrl拖拽可以以中心为基准画。 - Polygon / Polyline(多边形/折线):做斜坡、不规则地形轮廓。斜坡用多边形而不是矩形,是因为很多物理系统支持"多边形碰撞体",斜边能直接走上坡。
- Point(点):做出生点、路径点、事件锚点。一个点就是一个坐标,轻量。
- Tile Object(瓦片对象):把某个瓦片作为"对象"放置,可以单独设翻转、缩放。适合放独立的装饰物或需要单独碰撞的物件。
所有对象都必须有名字(Name)和类型(Type/Class),这是引擎侧识别的唯一依据。我以前偷懒不填名字,导出后一堆Rectangle 1、Rectangle 2,在代码里只能靠索引判断哪个是墙、哪个是门——改一次地图就要改一遍代码,血泪教训。
命名规范建议:type用英文小写加连字符(wall、trigger-door、spawn-player),name用于同类区分(wall-01、wall-02)。别用中文做 type,某些引擎的解析器对编码不友好,会出玄学问题。
4.2 碰撞体怎么画才不影响手感
这是最体现经验的地方。同一个墙,碰撞体画得大一点小一点,手感天差地别:
- 不要贴着瓦片边缘画满。角色通常有"宽度",如果墙面碰撞体和视觉墙完全重合,角色贴墙时会显得"陷进去"一点点。推荐内缩 1~2 像素(对 32px 瓦片),让视觉上留一点呼吸感。
- 地面碰撞体用"一条线"而不是"一个块"。如果你给地面每格画一个方块碰撞,角色在格与格交界处可能会卡住(经典的"接缝卡顿")。正确做法是在整个地面顶部画一条长的、连续的矩形条,跨越多个瓦片,物理引擎处理连续平面比处理多个相邻方块稳得多。
- 斜坡必须有明确方向。画斜坡多边形时,顶点顺序要一致(建议统一从左上开始逆时针),否则某些引擎会判定法线朝反方向,角色会从坡上掉下去。
- 触发区域宁大勿小。门、存档点、对话触发区,画大一点不会难受;画小了玩家擦边走过没触发,那是 bug。
我踩过最深的坑是:用瓦片对象做碰撞。看着直观,但每种瓦片对象的碰撞箱要么得手调,要么和瓦片本身对齐得很死,最后地图里几百个对象,想整体调整一次墙的厚度,得逐个改。结论:瓦片层负责视觉,矩形对象负责碰撞,这条边界不要模糊。
4.3 自定义属性:把"策划参数"从代码里搬出来
Tiled 最被低估的功能是属性(Properties),它能在任何层级挂键值对:地图、图层、对象、瓦片集、单块瓦片都行。
举几个我这套 Demo 里实际用到的:
| 挂载位置 | 属性名 | 值 | 用途 |
|---|---|---|---|
| 地图 | bgm | level-01-theme | 进关卡播放哪首音乐 |
| 地图 | gravity | 980 | 该关卡重力倍率 |
| 图层 | parallaxx | 0.5 | 该层视差系数 |
| 对象(门) | target-map | demo-level-02 | 进门跳到哪张图 |
| 对象(门) | spawn-id | entry-from-01 | 出生点匹配 |
| 瓦片 | is-water | true | 该瓦片是否是水(用于音效、物理) |
好处是改配置不用改代码。策划想调重力、想让某个门通向另一张图,在 Tiled 里改属性、存盘、重开游戏就行。这个工作流一旦跑通,你会发现自己再也懒得回去写if (levelId == 1) gravity = 980;这种代码。
注意:属性值有类型(字符串、整数、浮点、布尔、颜色、文件、对象)。类型选错会导致引擎侧解析失败——比如你把
gravity设成字符串"980",代码里as number可能拿到 0。养成"设值时先看类型下拉框"的习惯。
5. 从编辑器到引擎:导出配置与"导完不能直接用"的排查链路
画完、挂完对象,点 File → Export,这步看起来最简单,实际上是我见过问题最集中的环节。下面把我自己踩过的完整排查过程写出来,遇到问题可以顺着走。
5.1 TMX 还是 JSON:先看你的加载器吃什么
| 格式 | 优点 | 缺点 | 适用 |
|---|---|---|---|
| TMX (XML) | 支持最全,所有加载器都懂 | 文件大,解析慢,人眼难读 | 通用、老项目 |
| JSON | 轻量、易调试、脚本语言友好 | 部分加载器只支持子集 | Web / 自研引擎 |
| CSV | 数据极简,便于手改 | 只含瓦片索引,丢图层和对象 | 极简场景 |
| Lua | 部分引擎原生 | 通用性差 | 特定框架 |
关键动作:导出前先确认你的加载器支持哪些特性。比如某些轻量加载器不支持"多瓦片集"、不支持"无限地图"、不支持"瓦片翻转位",这些限制在导出后才暴露,会让人误以为是 Tiled 的问题。我的做法是:先导一张4×4 的测试图,只放一个瓦片层和一个对象层,跑通加载流程,再回去画正式图。这个小测试图我保留到现在,换引擎时还会拿出来验一遍。
5.2 三条最常见的"导出来不对"
第一条:图片全红叉 / 路径丢失。99% 是相对路径问题。tmx 里存的是相对路径,一旦你把 tmx 从maps/挪走,或者把 tsx 单独移动,引用就断了。解决办法:File → 用 "Export As" 时注意输出位置,或者干脆在项目里固定maps/和tilesets/的相对层级,永不打乱。
第二条:瓦片显示错乱、图案错位。优先怀疑两个:一是瓦片集的firstgid冲突——当一张地图用了多个瓦片集,每个瓦片集的全局起始 ID 必须不同且连续,Tiled 会自动算,但如果你手动改过或者合并过文件,就可能撞车。二是尺寸参数不一致——地图 tile size 和图集 tile size 不同,渲染时会拉伸。
第三条:对象位置偏移。这个最隐蔽。先看你的对象坐标基准:Tiled 里对象的 x/y 默认是左上角,但有些引擎的碰撞体定义是中心点。如果你没做半宽半高的偏移修正,所有碰撞体都会整体偏移 1/2 尺寸。我见过有人以为自己的角色"脚陷进地里",其实是所有碰撞箱都偏了半个身位。修正方式:要么在 Tiled 里把对象的对齐锚点改成中心(对象属性里有),要么在代码里统一减去宽高的一半。两处只能挑一处改,改两遍会翻倍偏移。
第四条(补充):透明边缘出现黑边或白边。这是图像采样/预乘 Alpha 的问题,不属于 Tiled 导出错误,但在 Tiled 阶段能预防:图集导出时确保瓦片有 1~2px 的外扩像素(padding),并在引擎里关闭纹理压缩或改用最近邻采样。
5.3 版本管理与协作:tmx 是文本,但要当二进制对待
tmx 和 tsx 都是纯文本(XML/JSON),理论上可以 diff、可以合并。但实际上,两个人同时改同一张地图几乎必然冲突——因为瓦片数据的行是超长字符串,一冲突就是整行对不上。
我的处理方式:
- 地图文件按人分文件,不要两人共编一张图。需要拼大场景,用"切片地图 + 运行时拼接",各自负责一个区域。
- 把
.tmx加入版本管理,但配置.gitattributes对 tmx 用-diff或binary标记,避免无意义的行级 diff 冲突。 - 每次导出后立刻提交一次,让"可运行状态"永远有存档点。地图这种数据文件,回滚成本比重新画低得多。
6. 跑通第一张图之后:性能、复用与扩展的几条实操建议
地图能加载、角色能走,说明主线通了。但距离"能用"还有一段,说几个我从实际项目里攒下来的东西。
6.1 瓦片层数量与 Draw Call 的关系
每一层 Tile Layer 在多数引擎里对应一个渲染批次。四层结构本身没问题,但如果你为了遮挡效果拆出七八层,draw call 就会上去,在低端设备或大分辨率下会掉帧。
实用原则:视觉上能合并的层就合并。比如 background 里的天空、云、远山,它们是不同视差系数才需要拆;如果视差一样,合成一层。另外,空图层记得删。Tiled 不会自动清理你新建但没用过的层,而很多加载器会为每个层创建空的渲染对象,白白消耗。
还有一个细节:瓦片集拆得太碎也会增加批次。一张地图用 5 个瓦片集,渲染时可能被切成 5 批。合理的做法是把"同屏高频出现的瓦片"放在同一个图集里(地形、常用装饰),把"低频、独立"的(BOSS 专属装饰、一次性道具)分开。这就是所谓"图集分组策略",在 Tiled 阶段规划好,比后期改省事。
6.2 一张图不够用:地图模板与复用
做第二张图的时候,不要从 File → New 重新配一遍参数——用"另存为 + 清空瓦片"。流程是:打开已经配好瓦片集、图层、属性的第一张图 → Save As 成demo-level-02.tmx→ 选中瓦片层用Ctrl+A全选后删除 → 保留图层结构和属性 → 开始画新内容。
这样能保证所有关卡的结构、属性键名、图层顺序完全一致,加载器代码一份就够。我甚至会做一个_template.tmx,里面所有图层都空着、属性都填好默认值、对象层里放好标准的出生点和出口对象,新关卡全部从它复制。这个习惯让我后面做十几个关卡时,代码侧一次都没改过。
6.3 三个我反复遇到的细节坑
坑一:翻转瓦片在部分引擎里失效。Tiled 支持对瓦片做水平/垂直翻转(快捷键X/Y),它通过给瓦片 ID 加高位标记实现。但一些简易加载器只取低位 ID,翻转信息直接丢掉,于是你在编辑器里看到的镜像瓦片,进游戏全变回原样。验证方法:放一块翻转瓦片,导出后看文件里的数值是否明显大于正常 ID(通常是加了0x80000000这种高位)。如果加载器不处理,就在图集里另存一份镜像瓦片,别依赖翻转。
坑二:透明度与混合模式。Tiled 图层支持自定义透明度和混合模式。用的时候要知道,不是所有引擎都支持"叠加/正片叠底"这类混合模式,很多时候只支持普通 Alpha。做发光、雾效时,优先在图集里画好半透明像素,而不是指望图层混合模式,这样跨引擎最稳。
坑三:单元格坐标和像素坐标的换算。Tiled 里所有对象坐标单位是像素,而瓦片层数据是格子索引。写代码时两套坐标混用是 bug 温床。我的习惯是在加载器里统一转成像素坐标(格子 × 瓦片尺寸),游戏逻辑全部用像素,只有绘制瓦片时才反算格子。写死一个转换函数,全项目只调它。
6.4 有些事不要在 Tiled 里做
最后说个反向经验:Tiled 不是万能的,有些东西硬塞进去只会让工作流变重。
- 动画瓦片:Tiled 支持瓦片动画(给瓦片配多帧),但很多加载器不支持,或者支持得不完整。做水面、火焰这类循环动画,我更倾向在引擎里用 shader 或精灵动画做,Tiled 里只放一帧占位。
- 大量逻辑:不要试图用对象属性表达复杂逻辑(比如"这个门需要三把钥匙才能开")。属性适合放静态配置,逻辑判断放代码。属性里塞 JSON 字符串那种做法,短期爽,长期是灾难。
- 超大无缝地图:一张 500×500 的图在 Tiled 里编辑会很卡,导出文件也大。这种规模应该用"分块 + 运行时拼接",Tiled 只负责单块。无限地图模式虽然能做,但如前所述,加载器兼容性是硬门槛。
回到开头那句话:Tiled 只管编辑,不管运行。它的价值不在于功能多,而在于把地图数据整理成一套干净、可配置、跨引擎通用的结构。你把图层语义分对、把对象类型命名规范、把可变参数挂到属性上,这套数据就能在你换引擎、加功能、交给别人接手时都不用重做。我自己那张 40×25 的 Demo 图,从最初只能看,到后来接上物理、加视差、挂事件,中间改的全是配置和分层,瓦片一块没重画——这种"改起来不心疼"的状态,才是工具真正帮到你的地方。