资讯动态

Unity TileMap 从原理到实操:关卡数据化、渲染合批与碰撞管理全解

发布时间:2026/10/20 5:02:59 来源:尧图企业网站定制
做关卡或者说做场景搭建绕不开一个东西Unity TileMap。2D 游戏里从模拟经营到平台跳跃从塔防到 Roguelike几乎都能看到它的影子。很多人最初接触 TileMap 时都停留在“把图块拖到场景里摆一摆”的阶段觉得这就是个拼图工具。但真把它吃透之后会发现它解决的不只是“拼图”这一件事而是关卡数据化、渲染合批、碰撞管理、运行时修改这一整套工程问题。这篇内容把 TileMap 从原理到实操完整过一遍适合刚接触 2D 关卡设计的新人也适合那些已经被拼缝、碰撞、排序问题折磨很久的开发者。1. 为什么铺地图离不开 TileMap核心需求与选型思路1.1 它解决的不是“画图”而是“数据管理”先搞清楚一个底层逻辑TileMap 本质是一张二维表每个格子位置对应一个图块数据。这个数据结构带来的最大好处就是关卡不再是一堆散落在场景里的 Sprite而是可以被代码按坐标读写的地图数据。对比一个很直观的场景如果你要做一个 50×50 的地图用传统的“一个一个 Sprite 拖进场景”就是 2500 个 GameObject。每一个都有 Transform、SpriteRenderer光是初始化开销和 Draw Call 就够喝一壶的。而用 TileMap场景里只有一个 Tilemap 对象加一个 Grid2500 个格子只是它的内部数据。渲染时 TilemapRenderer 会把相邻的同类图块合并成一块网格Draw Call 从 2500 降到几个。所以它最核心的价值有三个编辑效率Tile Palette 配合画笔几分钟铺完一大片地形手动摆要摆半天。运行性能合批减少 Draw Call内存占用也远低于同规模的 GameObject 集合。数据化能力运行时可以用坐标读格子、改格子、查格子天然适配随机生成、建筑破坏、存档序列化这些玩法。1.2 什么时候该用什么时候别硬上TileMap 不是万能药。它适合规则网格化的场景尤其是俯视角、横版平台跳跃、斜 45 度视角和六边形格子地图。这些类型的关卡本质上就是“格子集合”用 TileMap 是最自然的。但有几类情况我不建议硬用 TileMap地形边缘极其自由灵活的场景比如大量不规则洞穴、手绘风格浓重的背景这种更适合直接摆 Sprite 或用程序化 Mesh强行格子化反而限制美术发挥。单一场景、图块数量极少的小关卡一两个屏的地图手动摆 Sprite 也很快引入整套 TileMap 管线有点杀鸡用牛刀。需要复杂物理形状的中型地形块单个地形块如果有很多凹凸的碰撞细节用 TilemapCollider 的逐格碰撞反而浪费不如直接拼若干带 Polygon Collider 的地形组件。另外还要做一个很常见的选型对比Unity 自带 TileMap 和外部工具 Tiled 加第三方导入插件。我的经验是纯 Unity 项目、需求标准、团队不大直接用自带的就好如果美术习惯用 Tiled 做关卡导出或者需要复用已经用 Tiled 搭好的大批关卡数据用导入方案更省事。自带的方案胜在配置链路短、踩坑少去网上搜资料也容易。2. 系统架构拆解Grid、Tilemap、Tile 到底谁管谁2.1 Grid 是看不见的坐标系骨架很多人第一次创建 TileMap 时会看到层级里多了一个 Grid 物体下面挂着 Tilemap。这个 Grid 不渲染任何东西它只做一件事定义格子坐标和世界坐标之间的换算关系。Grid 上有两个关键配置一个是Cell Layout格子布局一个是Cell Size格子尺寸。Cell Layout 有四种选择Rectangle矩形、Hexagon-PointTop、Hexagon-FlatTop、Isometric等距斜视角。在创建 Grid 的时候就得想清楚之后想改布局类型整个地图的数据坐标都会变基本等于重建。Cell Size 则直接决定一个格子在世界空间占多大。这个值和你的美术资源规格、Sprite 的 Pixels Per UnitPPU必须对齐。举个例子如果你的图块 Sprite 是 32×32 像素PPU 设成 32那么这个图块在场景里的尺寸就是 1×1 单位Cell Size 就应该保持 1。如果你 PPU 设 100图块就变成 0.32 单位Cell Size 就得改成 0.32。很多新手铺完图发现图块之间出现黑缝、对不齐、渲染闪烁八成就是 Cell Size 和 PPU 没对齐。这个问题后面我会单独再讲。Grid 的另一层作用是为代码提供坐标换算接口// 屏幕鼠标位置转格子坐标 Vector3 worldPos Camera.main.ScreenToWorldPoint(Input.mousePosition); Vector3Int cellPos grid.WorldToCell(worldPos); // 格子坐标转世界坐标取格子中心 Vector3 cellCenter grid.GetCellCenterWorld(cellPos);这套换算是一切交互的基础点击格子种树、角色站到哪个格子、炸弹炸哪几个格子全都靠它。2.2 Tile 资产一图块一配置Tile 是一个资产文件它把一张 Sprite 和它对应的碰撞、元数据绑定在一起。创建方式很简单在 Project 窗口右键Create → 2D → Tiles 里选需要的类型然后把 Sprite 拖进 Tile 资产的 Sprite 槽里。这里要提醒一点Tile 资产本身是不带坐标的它只是一块“贴纸”可以被无数个 Tilemap 引用。你在 Tile Palette 里刷图本质上是在 Tilemap 的记录里写入“这个坐标用了哪个 Tile 资产”。Tilemap 内部的数据结构可以简单理解成一个DictionaryVector3Int, TileBase坐标是键Tile 资产的引用是值。用GetTile和SetTile就可以读写// 读取某个格子的 Tile TileBase tile tilemap.GetTile(new Vector3Int(0, 0, 0)); // 写入某个格子的 Tile tilemap.SetTile(new Vector3Int(0, 0, 0), myTile);因为数据是这种结构所以存档也就顺理成章了把每个格子的坐标和 Tile 资产名称序列化到文件里读档时按坐标重建一个无缝地图副本就回来了。我做模拟经营类项目时农场所有地块的种植状态就是这么处理并扩展的。2.3 TilemapRenderer 的合批逻辑性能好的真正原因TilemapRenderer 有两种渲染模式Individual和Chunk。默认是 Chunk它在内部把地图切成一格一格的区块每个区块里把所有图块合并成一张大网格Mesh一次性提交渲染。这意味着一个 100×100 的地图最终可能只产生几十个 Draw Call而不是一万个。Chunk 模式唯一的代价是当某个格子发生变化时它所在的整块区块网格都需要重建。这个重建有开销但如果不频繁改格子完全感觉不到。反过来如果玩法里每秒有大量格子动态变化频繁触发区块重建就得考量一下是继续用 TileMap 还是改用个体 Sprite 管理。Individual 模式则每个格子单独渲染适合格子数量极少或者需要特殊渲染控制的场合。绝大多数 2D 游戏我都不建议切 Individual因为合批优势全没了。再补充一个很少有人提的点TilemapRenderer 默认会做 Cell Culling也就是视口外的区块不参与渲染。所以大场景里同屏远处的图块并不会拖累性能这也是它能撑起大型关卡的原因之一。3. 实操环节从搭第一块地图到完整关卡3.1 创建 Tile Palette 与常用画笔搭建流程的第一步是准备图块。选中你的图块 Sprite在 Inspector 里把 Texture Type 设为 Sprite (2D and UI)如果你把所有图块放在一张图集里Sprite Mode 选 Multiple然后用 Sprite Editor 把每一块切出来。接着打开 Tile Palette 窗口菜单 Window → 2D → Tile Palette。创建并命名一个 Palette 后把切好的 Sprite 直接拖进去Unity 会自动帮你生成对应的 Tile 资产并纳入这个调色板。之后你在 Scene 视图就能像用画图工具一样刷地图了。这里把常用的 Brush 工具整理一下Paint画笔点哪刷哪儿按住拖动可连续画。Box矩形填充拖一个矩形区域整体填充铺地基、铺地面非常快。Select选择选中已有的一小片图块。Move移动把选中的图块整体搬走。Eraser橡皮擦擦掉格子。Line直线快速画横线竖线。实操里我建议一个动作先用 Box 刷大面积底色再用画笔处理边缘细节。一格格画不仅慢而且容易把整体节奏做乱。刷地形时把 Palette 窗口固定到编辑器侧边配合快捷键 Q/W/E/R 切换工具效率能翻一倍。3.2 Rule Tile让墙壁和边缘自动拼接手动画边缘、画转角是一件极其枯燥且容易出错的事。比如横版平台游戏里一面墙需要区分“左侧露出、右侧露出、上下边缘、内外转角”等几十种情况手动判断该放哪张图铺一关能铺到怀疑人生。Rule Tile 就是干这个的。它让你配置几条规则比如“如果上格子也是同类图块就显示完整墙面如果上格子不是就显示顶部边缘”然后它会在编辑器里根据周围格子自动选择正确的 Sprite 拼上去。创建方法Project → Create → 2D → Tiles → Rule Tile。在 Rule Tile 资产里你会看到三种铺设模式Grid 模式是用 3×3 的小格子来定义周围 8 格的关系Fixed 模式适合一些固定组合但最常用的是 Grid 环形模式。配置规则时选中相应方位的小图标然后拖入对应的 Sprite。比如平台边缘的规则是只有上方没有同类图块的格子用顶部边缘那几张图左转角、右转角各配一张。配上之后回到 Palette 把它当成普通图块刷转角处会自动切图。这条经验非常重要先配规则再铺图。如果你把成片地图都画完了再改规则改完还得重刷而先把规则配好之后无论在哪个位置涂边缘都是对的。3.3 Animated Tile 与 Prefab Brush让地图活起来静态图块铺完地图还不够“活”。水面要流动、火把要闪、传送门要转这些动画需求可以用 Animated Tile 解决。它需要你提供多帧 Sprite然后设置帧间隔Tile 就会在运行循环播放。用法很简单Create → 2D → Tiles → Animated Tile把帧 Sprite 拖进去Speed 字段控制播放速度。注意Animated Tile 播放的是 Sprite 切换不是真正的动画帧数不多、节奏固定没问题但别拿它做复杂动画。另外还有一个 Prefab Brush 很实用。有些图块不只是一个 Sprite比如地图上的宝箱、可交互机关、带物理的石头它们是完整 Prefab。直接用 TileMap 刷 Prefab 需要在放置时同时创建实体对象Prefab Brush 就是用来做这个的。用 Prefab Brush 把 Prefab 刷进地图本质上是往场景里实例化了物体不是写进 Tilemap 的 Tile 数据。这也意味着想用 GetTile 去查询“这格有没有宝箱”是查不到的。处理这种“图块数据与实体对象并存”的混合关卡时我的做法是维护一层额外的“对象层”数据表用同样的网格坐标记录哪个格子放了哪个 Prefab运行时生成实体逻辑保持一致。3.4 碰撞与交互让 TileMap 真正“硬”起来地图光好看没用角色得踩上去、撞墙得停下。给 TileMap 加碰撞最标准的配置是挂一个TilemapCollider2D再挂一个CompositeCollider2D。这里有个重要的设置步骤新手经常漏TilemapCollider2D 上要勾选Used By Composite然后让 Tilemap 物体挂一个Rigidbody2D把 Body Type 设为 Static这样 CompositeCollider2D 才能把所有格子的碰撞轮廓合成一个整体碰撞体。为什么不直接用每个格子的独立 Box Collider因为相邻格子各自生成小碰撞体边缘处会出现大量冗余的接触面物理计算浪费严重而且角色在拼缝处容易产生轻微抖动。Composite 合并后一整面墙就是一个大碰撞体边缘干净、物理稳定。还有一点容易被忽视每个 Tile 资产的 Collider Type 和 Sprite 的Physics Shape共同决定碰撞轮廓。默认 BoxCollider 是矩形但如果你希望桥洞、斜坡这种格子碰撞更贴合图形就要在 Sprite 的 Physics Shape 编辑器里设置 Alpha 阈值让 Unity 自动根据图像透明区域生成轮廓。门槛不高但做复杂地形特别值得花这几分钟。4. 进阶玩法运行时改图、层级排序与性能优化4.1 用代码动态修改地图破坏、建造与随机生成TileMap 一个让很多人津津乐道的点是运行时改图方便。像素风格的沙盒、塔防的建造和拆塔、Roguelike 的地道生成都能靠代码完成。核心 API 其实就几个// 单个格子写入 tilemap.SetTile(new Vector3Int(x, y, 0), tileAsset); // 单个格子清除 tilemap.SetTile(new Vector3Int(x, y, 0), null); // 读取当前图块做逻辑判断 TileBase current tilemap.GetTile(new Vector3Int(x, y, 0)); if (current groundTile) { // 是地面允许挖 } // 批量写入比逐个 SetTile 快得多 TileBase[] blocks new TileBase[width * height]; tilemap.SetTilesBlock(blockArea, blocks);批量写入这里特别想强调能 SetTilesBlock 就别用循环 SetTile。因为每次 SetTile 都可能触发所在区块网格重建一循环几十上百次每帧重建好几次卡顿立刻就能感觉到。批量接口一次更新一整块区域内部能合并重建操作性能差距非常明显。做随机生成地图时我的通用套路是先生成一张二维的“地形类型矩阵”草地、水、山、沙再根据这个矩阵批量构建 Tilemap而不是生成时一格一格 SetTile。这样逻辑计算和 Tilemap 数据写入解耦调试时打印矩阵比一点一点查格子舒服多了。4.2 多图层让角色在墙后、桥上、树下都有正确遮挡2D 游戏里遮挡关系处理得好不好直接决定画面的立体感。TileMap 默认是一个 Tilemap 对应一个图层但一个关卡通常需要多层 Tilemap 叠加地面层、地物层、装饰层、遮挡层。在层级里多创建几个 Tilemap全部挂在同一个 Grid 下。这样它们共用一套坐标系铺图时对齐毫无压力。然后在每个 TilemapRenderer 上配置Sorting Layer和Order in Layer。比如地面层 Order 设 0装饰层设 5角色默认在同一 Sorting Layer 上靠 Y 轴位置排序。要做出“角色走到树后面被树挡住”的效果就把树的遮挡块放到一个更高层级的图层里同时根据角色 Y 坐标动态调整它的 Order in Layer// 根据 Y 坐标动态调整渲染顺序实现伪 Y-Sort spriteRenderer.sortingOrder Mathf.RoundToInt(-transform.position.y * 100f);注意这里有一个经典误区TileMapRenderer 是整层一个排序值它不能像单个 SpriteRenderer 那样每个图块独立参与 Y-Sort。如果你需要某个 Tilemap 里的图块也按 Y 坐标和角色穿插排序就得拆分成多个小 Tilemap或者改用个体 Sprite 管理这些特殊物体。我处理角色和树冠遮挡时树冠部分不用 TileMap用独立 SpriteRenderer逻辑就简单很多。4.3 性能优化实操清单把 TileMap 用起来之后性能问题通常集中在几个点。这里给一份实践过的优化清单控制单个 Tilemap 的 Tile 类型数量。同一张地图里图块 Sprite 尽量合并进同一张图集不同图集之间切换也需要额外 Draw Call 开销。避免频繁改格子。前面提过改成 Chunk 渲染时改动一个格子会导致所在区块重建如果玩法要求高频挖地、填地可以接受但能合并改动就合并改动。给大关卡做碰撞自适应。整张地图挂一个巨大的 CompositeCollider在格子极多的超大关卡里初始合成会卡一下。解决办法是拆分多个相邻 Tilemap 控制合成区域或者仅对需要碰撞的层开碰撞装饰层干脆不挂 Collider。善用 TilemapRenderer 的 Batch 和 Culling。网格合批默认开启视口剔除默认开启不建议为了“保险”把渲染模式改成 Individual。编辑器里大图块显示卡顿可以把 Tile Palette 的视图缩得小一点或者把不用的 Tilemap 的 Renderer 先关掉。我这几个项目测下来一张 200×200 的地图配合图集和默认 Chunk 渲染Draw Call 一般能控制在 30 以内物理也稳定完全够多数 2D 游戏使用。5. 常见问题排查实录与经验速查5.1 拼缝黑线最经典的老大难铺完图镜头拉近发现图块之间一条条暗缝这是 TileMap 使用频率最高的求助问题。常见的原因有三个按排查顺序列一下PPU 和 Cell Size 没对齐。图块实际显示尺寸和格子尺寸差那么零点几像素拼接时边缘就露馅。回去检查 PPU 和 Grid 的 Cell Size。Sprite 的 Filter Mode 不是 Point。默认的 Bilinear 会在拉伸时做像素插值低分辨率像素风下会把边缘混出半透明色黑色缝隙就出来了。在 Sprite 图集导入设置里把 Filter Mode 改成 Point无过滤同时把 Compression 设为 None避免压缩带来的边缘色块污染。相机有抗锯齿。镜头渲染的 MSAA 或者后期处理里的抗锯齿偶尔也会让像素边缘变灰。像素风格游戏通常关掉或保持 Point 采样。实际操作中我还会顺手检查一下图集边缘有没有留透明边距以及在 Tile asset 上不要把 Sprite 的 Pixel Perfect 选项和 PPU 调成矛盾的值。总之这是一整套配置不是单改一处就能完美。5.2 碰撞边缘抖动与穿透角色走过两格地砖的交界处偶尔会上下抖一下高速移动时甚至能穿过墙壁。这通常是网格边缘的微小缝隙导致的物理接触不连续也跟图块拼缝问题有关。用 CompositeCollider 合并后抖动基本会消失。如果还抖检查Rigidbody2D 的 Interpolate是否开启没开启的话高速移动下物理渲染会一卡一卡看起来就像抖动。穿透问题则多半是运动速度太快、单帧位移超过碰撞体厚度把 Rigidbody2D 的 Collision Detection 改成 Continuous就不会直接穿墙了。5.3 多图层排序错乱地面和墙壁互相穿插、角色一会跑到草上面一会跑到草后面通常是 Sorting Layer 和 Order in Layer 没区分清楚。处理原则是先统一规划图层顺序地面在最低层可通行地物在中间遮挡物在最高层所有 Tilemap 保持同一个 Grid避免各自偏移导致坐标漂移。角色和遮挡物的穿插关系用动态 sortingOrder 而不是图层硬排。5.4 编辑器卡顿与热重载问题大 Palette 里图块多编辑器里用画笔连续刷大区域时可能会卡。这不是运行时的性能问题是编辑时 Tilemap 数据频繁变化网格反复重建导致的。解决办法是在铺大面积时缩小 Palette 视口、减少一次拖动覆盖的格子数或者干脆先把底色 Tile 用脚本批量写入再进编辑器微调。还有一个看不见的坑在编辑器下修改了 Tile 资产运行时如果角色站在那块地图上有时会发现刷新没有立即生效。这种情况先从 Asset Database 的刷新开始排查不行就直接重启编辑器。我碰到过一次 Rule Tile 改了规则但场景里的图没跟着变把该图块重新刷一遍就好了不是 Bug是规则缓存的问题。5.5 问题速查表表现主要原因快速处理图块间黑线PPU / Cell Size 不对齐、Filter 非 Point、压缩统一 PPU 与 Cell SizeFilter 改 Point压缩关 None角色抖动、穿墙碰撞未合并、高速检测不足用 CompositeColliderCollision Detection 调 Continuous图层前后错乱Sorting Layer 未规划统一规划图层顺序动态排序用 sortingOrderTileMap 运行时报错坐标错位多个 Tilemap 没共用同一个 Grid所有 Tilemap 挂在同一 Grid 下编辑器大范围刷图卡网格频繁重建分批刷、缩小 Palette 视口、脚本批量写入运行时 SetTile 后画面闪烁区块网格重建成本高优先用 SetTilesBlock 批量写入收尾的几点心里话TileMap 这套东西单独看每个功能都不难难的是把它放进真实项目里跟美术资源、物理系统、玩法逻辑捏合在一起。我踩过最深的坑是在做第一个平台跳跃 Demo 时为了赶进度把碰撞挂在每个 Tile 上结果角色在墙边滑不动、还时不时被拼缝卡住后来老老实实把 Collider 合成做了问题立刻少了一大半。所以在实际开工前哪怕只是一个小关卡也建议先把 Grid 的尺寸、Sprite 的导入设置、Collider 的三件套TilemapCollider2D、CompositeCollider2D、静态 Rigidbody2D完整配一遍这些基础不打好后面改起来成本翻倍。如果你后续想往随机地牢、程序化生成这类方向走那更要现在就把“坐标读写数据”这套思路练熟因为地图对你来说就不再是美术素材而是真正的游戏数据。这也是我建议每个做 2D 的人都该花时间把 TileMap 吃透的原因。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑