做2D游戏,最让人头疼的往往不是玩法逻辑,而是关卡里那一堆需要摆放和管理的Sprite。如果你试过在场景里手动摆几十上百张地面图块,再去调整位置、处理遮挡、加碰撞体,你一定会有一种冲动:能不能像画画一样把地图涂出来?Unity TileMap系统就是为这件事准备的。它把地图拆成网格上的瓦片单元,用专门的编辑器面板和组件来管理绘制、渲染、碰撞与运行时更新,是做平台跳跃、地牢探索、模拟经营、塔防等2D游戏时绕不开的核心工具。这篇文章我从架构、绘制、规则瓦片、碰撞和性能几个角度完整拆一遍TileMap,适合刚上手的新人,也适合已经用了一阵子但没深究过细节的开发者。1. 铁三角架构:Grid、Tilemap 与 Tile 到底谁管什么很多新手第一次点菜单里的 Tilemap 创建,看到场景中多出一个叫 Grid 的根物体、下面挂一个 Child Tilemap,心里就开始打鼓:这俩是干啥的?Tile 我又该从哪里拖?先别急着画,把这三个角色的分工弄清楚,后面所有操作都会顺畅很多。1.1 Grid:地图的骨架与坐标换算器Grid 本身不渲染任何东西,它也不存储瓦片数据。它是一套规则的骨架,负责把世界坐标转换成网格坐标,再把它转回世界坐标。你可以把它理解成一张透明的方格纸,瓦片只是被涂在纸上的颜料,而纸的格子间距、形状、方向全由 Grid 决定。创建 Tilemap 时,Unity 会自动在根节点挂一个 Grid。它有四个参数直接影响你的关卡设计和运行时逻辑:Cell Layout(网格布局):决定格子的形状。最常用的 Rectangle(矩形)适合横版、俯视角地图;Isometric(等距)适合 45 度斜视角的模拟经营类玩法;Hexagon 分 Flat Top 和 Pointed Top 两种,做 hex 网格战棋必须先用对;Isometric Z as Y 则是把 Z 轴当作 Y 轴的等距变体,常在 2.5D 混合场景里出现。Cell Size(格子尺寸):默认是 1x1,单位是世界单位。这个值要和你的 Sprite PPU(Pixels Per Unit)对得上。假设素材是 32x32 像素、PPU 设成 32,那一个格子正好 1 个世界单位,Cell Size 保持 1 即可;如果素材是 64 像素但 PPU 改成 16,素材在场景里会显示成 4 个单位大小,Cell Size 就得改成 4。很多人画地图出现瓦片叠加、缝隙,十有八九是 Cell Size 和 PPU 没对齐。Cell Swizzle(坐标修正):只在等距和六边形布局下会出现,影响瓦片贴图在坐标轴上的映射顺序。创建 Grid 时选定后一般不要再动,运行时代码里用 Grid.WorldToCell 换算时,如果坐标方向总是偏一点,优先检查是不是这里设错了。Transform 旋转:Grid 根物体上的 Rotation 会影响整个坐标系的方向。有些美术同学为了场景“好看”会随手转个 45 度,结果运行时代码全乱。2D 场景里请保持 Rotation 为 0,美观的部分交给相机和 Sorting Layer 去处理。1.2 Tilemap:瓦片数据的容器与渲染入口Tilemap 组件挂在 Grid 的子物体上,它才是真正持有瓦片数据的角色。每个网格坐标对应哪块 Tile、颜色有没有染色、做没做翻转,这些信息都存在 Tilemap 里。Tilemap 组件上有几个高频使用的参数:Animation Frame Rate:控制动画瓦片的播放帧率,单位是帧/秒。素材如果没有特殊处理,默认值 1 每秒只播一帧,看起来像幻灯片。做水面、传送门动画时我会习惯调到 10 到 12,具体看序列帧张数和节奏需求。Color:把整张 Tilemap 染色。可以用它做全地图淡入淡出、夜晚氛围压暗、冷暖色调切换,不用逐格改素材。Collider Type:控制当前 Tilemap 的瓦片碰撞体生成方式,None、Sprite、Grid 三个选项,后面第四节我会详细展开。真正把数据画到屏幕上的,是挂在同一个物体上的 TilemapRenderer。它读取 Tilemap 数据,合成网格并参与渲染。日常开发里你只需要知道:默认的 Chunk 渲染模式会把相邻瓦片合并成大网格,这是 Tilemap 性能好的关键。1.3 Tile:最小单元资源,再也不怕改一处全图跟着乱Tile 是一个 ScriptableObject 资源,可以理解成“一个格子该长什么样”的说明书。最常见的 Tile 配置项包括:Sprite:要显示的那张图。Color:染色叠加,可以给同一张图做出不同色调的变体。Collider Type:这个瓦片自身参与碰撞的方式,优先级高于 Tilemap 组件上的全局设置。Transform 矩阵:左右翻转、上下翻转、旋转 90 度。同一个 Sprite 配合不同翻转可以拆成多个 Tile,画地图时会更灵活。这里有个非常实用的特性:画完地图后想调整某类瓦片的表现,只需要双击对应的 Tile 资源修改属性,所有使用这个 Tile 的格子会同步更新。对比一下以前手动摆 Sprite 的做法——你摆了一面墙,突然想给墙根加一条阴影边,得挨个选中再换图,TileMap 直接改资源就行,这就是它效率高的底层原因。2. Tile Palette 与笔刷实操:把画地图变成“涂色”搞清楚架构之后,下一步就是动手把地图画出来。TileMap 的编辑器体验核心在 Tile Palette 窗口,如果你还没找到它,菜单路径是 Window > 2D > Tile Palette。2.1 准备工作:图集切分、Palette 创建与素材命名在创建 Paletter 之前,先把瓦片素材整理好。建议把同一类素材(比如地面、墙体)放进一张图集,用 Sprite Editor 的切片功能切成多个 Sprite。如果项目是 3D 模板,还需要在 Package Manager 里确认 2D Tilemap Editor 相关包已经装上,否则可能找不到 Tile Palette 相关菜单。接着在 Tile Palette 窗口左上角点 Create New Palette,弹出的面板里有几个值得注意的地方:Name:调色板名称,建议按地图风格或场景命名,比如 Forest_Floor、Dungeon_Wall。Cell Size:默认 Automatic,Unity 会根据你素材的 PPU 自动换算单格尺寸。手动指定也行,但确保和 Grid 上的 Cell Size 一致。Grid 预设:矩形、等距、六边形,必须和你的地图布局匹配。这一步选错了,后面画出来的地图坐标全是乱的。保存路径:我习惯放在 Assets/Art/Palettes 下面,资源分类清晰一些。创建完成后,把切好的 Sprite 直接拖进 Palette 窗口,Unity 会自动生成对应的 Tile 资源。注意 Tile 资源的默认名字直接复用 Sprite 名,所以 Sprite 命名越规范,后面越好找。我的习惯是 Floors_01、Walls_Left_01 这样带类型前缀加编号的命名,调 RuleTile 时能省很多眼力。2.2 常用笔刷与高效绘制习惯Tile Palette 的笔刷不少,但真正高频使用的就那么几个:Pencil(铅笔):单格逐点画,适合补细节、画零散装饰。Marquee(框选):框选一片已有瓦片,然后可以整体复制、涂抹到别的位置。做重复房间、重复地块结构时效率极高。Eraser(橡皮):清空格子。Fill(油漆桶):填充连通的同色区域。注意它的连通规则和图像软件一致——如果目标区域已经被别的瓦片隔断,油漆桶不会穿过去。Move(移动):整体移动选中的瓦片区域,会改变这些格子的坐标。用之前记得确认碰撞体和周围瓦片的边界,不然容易挪出问题。画地图最容易翻车的不是笔刷本身,而是图层意识。我强烈建议同一个 Grid 下挂多个 Tilemap 子物体,按用途分层:背景层(Background):远景、天空盒效果的瓦片。地面层(Ground):玩家能走的实心地面。墙壁层(Wall):阻挡和遮挡角色的大体积墙体。装饰层(Props):花草、桌椅、箱子这类不参与核心碰撞的物件。这些 Tilemap 共享同一个 Grid,坐标系统完全一致。绘制时在 Tile Palette 上方切换 Active Tilemap 就能在同一个格子里分层画。渲染顺序通过 Sorting Layer 控制,背景层最低、建筑层中间、角色层最高,遮挡关系一目了然。这里有一个必须要养成的习惯:不要为了排序去改 Tilemap 的 Z 轴坐标。2D 场景用 Z 轴控制遮挡会和 Shader 深度、相机裁剪纠缠在一起,容易产生莫名其妙的闪烁和排序 bug。老老实实把 Sorting Layer 规划好,比什么都管用。2.3 批量创建 Tile 与资源管理小技巧如果素材切了几十上百张 Sprite,挨个拖进 Palette 会崩溃。可以直接在 Project 窗口里选中多个 Sprite,一起拖进 Tile Palette 窗口,Unity 会批量生成对应的 Tile 资源。生成后建议把它们统一放到一个 Tile 资源文件夹里,不要和 Sprite 混在一起。还有一个很省事的操作:批量修改多个 Tile 资源。你可以在 Project 窗口里选中所有墙体 Tile,然后在 Inspector 里直接把 Collider Type 统一改成 Grid。如果一个个点,点个几十次,再好的脾气也磨没了。3. RuleTile 与高级瓦片:让地形自己长出连接边先想一个问题:画一面石墙时,墙体中间是完整砖块纹理,到了墙的边缘,你应该手动挑“边缘过渡”素材来贴合。如果墙拐了一个角,还要找对应的内角、外角素材。地形越复杂,手动挑选素材的工作量越离谱。RuleTile 解决的正是这件事——让瓦片根据周围邻居的状态自动选择显示哪张图。3.1 理解 RuleTile 的规则图RuleTile 是 Unity 2D Tilemap 生态里最实用的一块,官方也把它做成了内置能力。它的核心是一个 3x3 的九宫格规则图:中间那一格是当前瓦片自身,不需要配置;周围八格用来表达邻居状态。每一种状态,你可以指定一张或一组 Sprite:如果上下都是同类地面,当前格子显示中段纹理。如果上面是空,显示顶部边缘或墙体收口纹理。如果左上角有缺口,显示内角过渡纹理。斜对角方向缺角时,显示角块。配置里有几种匹配模式:要求邻居是某类瓦片、要求邻居不是某类瓦片、或者邻居是什么都可以。规则组合起来之后,你在地图上画一个多边形区域,RuleTile 会自动把边界、转角、对角线全部补齐,画出来的地形自然到像手工精修过一样。还有一个隐藏功能:TPipeline 里经常用到的随机输出和动画输出。RuleTile 的 Output 可以设置为 Random,同一规则下从多张 Sprite 里随机挑一张。做草地、碎石地面时,同样的规则多配几块不同纹理,画出来就不会有整片“复制粘贴”的廉价感。3.2 创建与配置 RuleTile 的实操流程创建 RuleTile 很简单:Project 窗口右键 → Create → 2D → Tiles → Rule Tile。但配置才是重头戏。把最基础的地形 Sprite 拖进 RuleTile 的 Sprite 字段,然后双击打开 RuleTile 编辑器。编辑器的界面就是一个 3x3 格子,你需要把素材按方向对应的纹理拖进对应格子。比如:中间空格表示当前自身。上方格子拖入“顶部边缘”纹理。右上方格子拖入“右上角”纹理。左边格子拖入“左边边缘”纹理,依此类推。关键是保持素材命名和方位的一致性。大多数地形素材包都会按九宫格顺序提供中间、右上、上、左上、左、右、右下、下、左下的切片,但不同素材包排序可能不一样。我建议先用一张小草图记录素材与方位的对应关系,配置时对照着拖,比盯着平铺图瞎猜靠谱得多。配置完成后把 RuleTile 拖进 Tile Palette,就能像普通瓦片一样画地图了。一旦某个地形区域发生变化,RuleTile 会自动刷新邻居关系,这个过程叫 RefreshTile。3.3 AnimatedTile、RandomTile 与混用的注意点除了 RuleTile,日常地面绘制还会用到几个官方瓦片类型:AnimatedTile:把多帧 Sprite 拖进去做成动画瓦片,适合水面、传送门、发光矿脉。RandomTile:从一组 Sprite 里随机选一张显示,没有邻居检测能力,但胜在简单,适合不需要地形逻辑的碎石地、草堆。PipelineTile:冷门但实用,它只检测左右连接状态,自动选直管、弯管、端盖素材,做管道类横版游戏非常合适。混用这些瓦片时有一个性能隐患:动画瓦片每帧切换 Sprite,会导致所在渲染块持续重建。如果一张地图上挂了上百个动画瓦片,低端移动设备上帧率会掉得非常明显。我实测过的经验是:动画瓦片数量尽量控制在几十个以内,再多就把动画改成 Shader UV 偏移或顶点色动画,让 GPU 去处理,而不是每帧换 Sprite。如果遇到 RuleTile 不自动变化的玄学问题,最常见的两个原因就是刷新没触发和规则方向理解反了。排查方法很简单:运行时调用 Tilemap.RefreshAllTiles() 强制刷新一遍,看瓦片有没有变;如果变了,说明只是刷新时机不对,如果没变,回去检查规则图里“要求匹配的是上方的格子还是下方的格子”,很多素材包的方向约定和你想的正好相反。4. 碰撞体系统与运行时交互:让地图真正可玩地图画得再漂亮,没有碰撞体,角色走过去还是会直接掉穿。TileMap 的碰撞体配置逻辑和普通 Sprite 不太一样,理解对了才能少走弯路。4.1 Tilemap Collider 2D 与 Tile 的 Collider Type给挂有 TilemapRenderer 的那个 GameObject 添加 Tilemap Collider 2D 组件后,系统会为每个有 Tile 的格子生成碰撞形状。但这个形状长什么样,得由 Tile 资源里的 Collider Type 决定:None:不参与碰撞,适合纯装饰层。Sprite:按 Sprite 的实际图形轮廓生成碰撞。适合不规则障碍物、镂空边缘,最灵活但网格多边形也更复杂。Grid:按整个格子生成矩形碰撞。性能好、行为直观,绝大多数地面和墙体我都用 Grid。批量调整 Collider Type 的技巧上一节已经提过:在 Project 里选中多个 Tile,统一在 Inspector 改,效率极高。4.2 Composite Collider 2D:把碎片焊成整体如果一张墙由 300 个格子组成,每个格子一个独立碰撞体,那物理引擎要同时处理 300 个多边形。玩家斜向移动时,经常会卡在格子之间的接缝处,听起来像“呲溜”一下被什么东西绊住了。这个问题在斜边、弧线地形上尤其糟糕。解决办法是 Composite Collider 2D。操作流程如下:在 Tilemap 所在 GameObject 上添加 Tilemap Collider 2D。再添加 Composite Collider 2D 组件。回到 Tilemap Collider 2D,把 Used By Composite 勾上。勾选之后,物理引擎会把所有瓦片的碰撞形状合并成一个大的复合碰撞体。格子之间的缝隙被焊接掉,斜向移动也顺滑了。Unity 会自动在这个 GameObject 上添加一个 Rigidbody 2D 并设为 Static,作为复合碰撞体的载体。这个刚体不参与物理模拟,只是一个必要的挂载条件,不用管它。这里有一个容易踩的坑:Composite Collider 2D 的 Geometry Type 有 Outlines 和 Polygons 两种。Outlines 生成的是外部轮廓,适合装饰性边界;Polygons 生成的是内部填满的多边形,适合不可穿过的实心地形。做关卡地形我推荐 Polygons,做山体轮廓或视觉边界可以用 Outlines,反正两者切换很方便,哪边物理表现不对就换另一边试。4.3 运行时改瓦片:从单纯绘制到动态世界TileMap 的另一半价值在运行时动态修改。采集矿石、铺路、拆墙、种田这类玩法,全部建立在运行时改瓦片的能力上。核心 API 其实就那么几个,先记下来:tilemap.SetTile(pos, tile):在指定坐标放一块瓦片。tilemap.SetTiles(posArray, tileArray):批量放置,效率远高于循环单个调用。tilemap.GetTile(pos):获取指定坐标当前的瓦片。tilemap.HasTile(pos):判断坐标是否有瓦片。tilemap.GetTilesBlock(bounds):按矩形区域批量获取瓦片数组,适合做范围遍历。tilemap.ClearAllTiles():清空整个 Tilemap。tilemap.RefreshTile(pos) / RefreshAllTiles():刷新规则瓦片状态,强制邻居联动。最容易被坑的是坐标系换算。鼠标点击拿到的是世界坐标,不能直接往 SetTile 里塞,必须先通过 Grid 组件转一下:Vector3Int cellPos = grid.WorldToCell(worldPoint);然后才能拿这个 cellPos 去读写瓦片。这个转换逻辑和 Grid 的 Cell Layout 强相关,等距网格和矩形网格的换算结果完全不同。我建议开发时先写一个 Gizmos 脚本,把鼠标所在的格坐标显示出来,验证换算结果的正确性,再来写玩法逻辑,比直接盲写稳太多。还有一个经验必须分享:运行时批量更新瓦片,尤其是一帧内改几百上千个坐标,请务必用数组版本 API。我在某个模拟项目里做过对比,同样改 1000 个格子,把数据整理成数组一次性 SetTiles 比循环 SetTile 快好几倍。低端移动设备上这个差距会直接变成肉眼可见的卡顿和血条下降。5. 性能优化与常见问题排查:从能用到好用TileMap 用多了你就会发现,它的性能上限和学习门槛都不在“画地图”这一步,而是看你能不能理解它的渲染机制和优化边界。5.1 渲染模式与图集优化TilemapRenderer 默认是 Chunk 模式,这是我前文反复强调的重点:相邻瓦片会自动合并成一个大网格,DrawCall 被压得非常低。这是 TileMap 系统相比裸摆 Sprite 的最大优势。但 Chunk 模式也有几个禁忌:同一 Tilemap 上不要混太多不同 Material 或不同 Sorting Layer 的瓦片,每一次状态切换都会打断合批。瓦片素材要尽量放入同一个 Sprite Atlas。Chunk 模式虽然会合并网格,但如果瓦片引用的 Sprite 来自散图,纹理切换依然会带来额外开销。我在项目里统一用 Sprite Atlas 打包地图素材,Padding 设成至少 2 到 4 像素,边缘黑边问题基本没再出现过。超大地图不要只挂一个 Tilemap。哪怕它们共享同一个 Grid,按区域拆分成多个 Tilemap,也能大幅减少单次网格更新和裁剪计算。类似 200x200 的开放地图,我会拆成几个区块,每块独立 Tilemap,按需加载,运行时的负担分摊开。另外一个模式是 Individual,它让每个瓦片单独渲染,方便调试单块瓦片纹理问题,但性能比 Chunk 差一截,调试完记得切回来。5.2 瓦片缝隙、黑边与 PPU 对齐瓦片之间出现黑边、细缝,几乎是每个 TileMap 新人的第一个视觉灾难。根因通常出在三个位置:Sprite 的 PPU 与 Cell Size 不一致,素材被拉伸后出现采样误差。图集内 Sprite 之间的 Padding 不足,相邻瓦片互相渗透像素。Filter Mode 是 Bilinear,像素风瓦片被模糊处理后边缘出现半透明过渡。解决思路也很直接:素材 PPU 与 Grid Cell Size 匹配;图集 Padding 不低于 2 像素;纯像素风素材的 Filter Mode 统一设成 Point。这一套流程走完,黑边问题基本根治。还有一个小坑:同一张素材如果被用于多个 Tile,不要在某个 Tile 里单独改 Scale 或 Rotation,否则看起来像偏移的格子。想要不同方向的显示,正确做法是复制 TILE 资源,在 Transform 矩阵里设置翻转,而不是在场景里旋转瓦片。5.3 现实项目中高频踩坑实录我把这几年在项目里反复遇到的问题整理成一张速查表,几乎每个 2D 项目都能用到。现象可能原因处理办法瓦片之间有黑边或缝隙PPU 与 Cell Size 不匹配、图集 Padding 太小、Filter Mode 为 Bilinear统一 PPU,Padding 不低于 2,瓦片纹理设 Point地图能画但角色直接穿墙Tile 的 Collider Type 是 None,或 Tilemap Collider 组件没加把 Tile 的 Collider Type 改为 Grid,添加 Tilemap Collider 2DRuleTile 不按预期匹配规则图方向理解反了,或刷新未触发检查九宫格方向约定,运行时调用 RefreshAllTiles 验证DrawCall 高、运行卡顿TilemapRenderer 被设成 Individual,或瓦片散图未打包改回 Chunk,统一放入 Sprite Atlas多个 Tilemap 之间排序忽上忽下Sorting Layer 或 Order 未明确区分使用 Sorting Layer + Order 统一管理,不要依赖创建顺序程序改瓦片后画面有残影直接覆盖旧瓦片,网格边界没刷新先 ClearAllTiles 再 SetTiles,或调用 RefreshTile 刷新局部动画瓦片多了掉帧AnimatedTile 每帧切换 Sprite 导致网格重建控制动画瓦片数量,改用 Shader UV 动画Isometric 地图点击位置对不上Grid 坐标换算没考虑布局类型用 Grid.WorldToCell,并写 Gizmos 高亮调试坐标转换表格里最后一条值得多说一句:等距地图的瓦片绘制和点击判定一直是社区高频问题。大多数人踩坑的原因是直接从鼠标屏幕坐标换算到世界坐标,却没有意识到 Grid 的 Cell Layout 已经把坐标系做了偏移和错切。遇到这种问题,不要盯着计算式蒙答案,直接在 OnDrawGizmos 里把鼠标所在格画出来,眼睛看到的效果远比脑子里的推演可靠。6. 数据持久化与模块化设计:TileMap 的进阶用法如果你只把 TileMap 当成编辑器里的画图工具,那其实还用不到它一半的价值。真正的进阶用法是把 Tilemap 当作战场数据源,让关卡生成、存档读取、运行时交互统一走一条数据通路。6.1 持久化:TileMap 不会替你存进度Tilemap 里的数据只在场景运行时存在,退出场景一切恢复原状。如果玩家在场景里挖了矿、拆了墙、种了地,你需要把这些变化记录到自己的存档系统里。我的做法是建一个二维或三维数组,以网格坐标为主键,记录两个值:坐标位置和瓦片资源引用。保存时遍历所有受影响的 Tilemap,把瓦片数据序列化进存档;读取时创建新的 Tilemap,遍历存档数据批量 SetTiles。这里要注意:存档数据结构不要绑定 Unity 的 Tile 资源实例,因为在不同平台、不同资源版本里,Tile 资源的 GUID 才是稳定标识。你存 GUID,读取时再根据 GUID 找到对应资源,这样切换平台、打补丁时不会丢瓦片。还要考虑一种常见情况:同一块区域可能同时存在多个 Tilemap 图层。存档时不要只记坐标和 Tile,还要记这个坐标属于哪一层。否则读档时地面层的数据写进了装饰层,画面立刻乱给你看。6.2 用 Tilemap 做关卡数据的读取与验证既然 Tilemap 本身就是数据,那么它可以同时承担“关卡编辑工具”和“关卡数据源”两个角色。你可以在编辑器中通过 Tile Palette 快速搭一关,然后运行时代码遍历检查玩家出生点、终点、障碍物分布,生成小地图或者自动验证关卡合理性。这个思路在自动生成关卡、每日挑战这类玩法里尤其顺手。我强烈建议把地图数据访问稳定封装成一个方法,比如 GetCellAt(pos, layer) / SetCellAt(pos, layer, tile)。这样玩法逻辑里不会散落一堆 Grid 换算和 Tilemap API 调用,出问题的时候排查范围也小。之前在某项目里因为直接在多个脚本里直接调用 SetTile,结果存档和瓦片状态偶尔不一致,最后把读写全部收拢到一个管理类里,问题才算根治。6.3 分区、多 Tilemap 与按需加载的配合前文提过把大地图拆成多个 Tilemap 区块,这里展开说下配合按需加载的设计。假设你的地图很大,玩家视角只覆盖其中一小块。一次性把所有区块的 Tilemap 挂进场景,内存和网格更新压力都会堆积。更合理的做法是:场景里只保留当前视野附近的两个到三个区块,玩家移动过界时,把远处的区块卸载或隐藏,同时把新进来的区块加载出来。配合 Tilemap 的 Chunk 渲染模式,每个区块的网格只在初始化时生成一次,运行时的动态变更只影响局部,整体帧数会稳定很多。这个方案的前提是区块划分和网格坐标系一致。我习惯直接以 Grid 的像素坐标边界来划分区块,比如每 32 格一个区块,区块名字就叫 Region_X_Y,这样程序里计算当前玩家位于哪个区块、该加载哪几个邻居区块,只是简单的坐标除法,连查表都不用。结尾做了这么久 2D 项目,我的体会是 TileMap 系统真正厉害的地方不在于“能画地图”,而在于它把关卡数据、渲染、碰撞、编辑器工具和运行时逻辑整合在同一条数据管线上。编辑器里涂几笔,运行时改几个坐标,存档系统再同步一次,一个动态关卡就立住了。反过来,如果一开始不理解 Grid、Tilemap、Tile 三者的分工,不明白 Chunk 渲染和 RuleTile 的边界,遇到性能瓶颈时真的会非常痛苦。以上这些经验都是我一个个坑踩出来的,希望你能直接抄走,少走几轮弯路。如果你在项目里也有关于 TileMap 的骚操作或邪门问题,欢迎一起交流。
做2D游戏,最让人头疼的往往不是玩法逻辑,而是关卡里那一堆需要摆放和管理的Sprite。如果你试过在场景里手动摆几十上百张地面图块,再去调整位置、处理遮挡、加碰撞体,你一定会有一种冲动:能不能像画画一样把地图涂出来?Unity TileMap系统就是为这件事准备的。它把地图拆成网格上的瓦片单元,用专门的编辑器面板和组件来管理绘制、渲染、碰撞与运行时更新,是做平台跳跃、地牢探索、模拟经营、塔防等2D游戏时绕不开的核心工具。这篇文章我从架构、绘制、规则瓦片、碰撞和性能几个角度完整拆一遍TileMap,适合刚上手的新人,也适合已经用了一阵子但没深究过细节的开发者。1. 铁三角架构:Grid、Tilemap 与 Tile 到底谁管什么很多新手第一次点菜单里的 Tilemap 创建,看到场景中多出一个叫 Grid 的根物体、下面挂一个 Child Tilemap,心里就开始打鼓:这俩是干啥的?Tile 我又该从哪里拖?先别急着画,把这三个角色的分工弄清楚,后面所有操作都会顺畅很多。1.1 Grid:地图的骨架与坐标换算器Grid 本身不渲染任何东西,它也不存储瓦片数据。它是一套规则的骨架,负责把世界坐标转换成网格坐标,再把它转回世界坐标。你可以把它理解成一张透明的方格纸,瓦片只是被涂在纸上的颜料,而纸的格子间距、形状、方向全由 Grid 决定。创建 Tilemap 时,Unity 会自动在根节点挂一个 Grid。它有四个参数直接影响你的关卡设计和运行时逻辑:Cell Layout(网格布局):决定格子的形状。最常用的 Rectangle(矩形)适合横版、俯视角地图;Isometric(等距)适合 45 度斜视角的模拟经营类玩法;Hexagon 分 Flat Top 和 Pointed Top 两种,做 hex 网格战棋必须先用对;Isometric Z as Y 则是把 Z 轴当作 Y 轴的等距变体,常在 2.5D 混合场景里出现。Cell Size(格子尺寸):默认是 1x1,单位是世界单位。这个值要和你的 Sprite PPU(Pixels Per Unit)对得上。假设素材是 32x32 像素、PPU 设成 32,那一个格子正好 1 个世界单位,Cell Size 保持 1 即可;如果素材是 64 像素但 PPU 改成 16,素材在场景里会显示成 4 个单位大小,Cell Size 就得改成 4。很多人画地图出现瓦片叠加、缝隙,十有八九是 Cell Size 和 PPU 没对齐。Cell Swizzle(坐标修正):只在等距和六边形布局下会出现,影响瓦片贴图在坐标轴上的映射顺序。创建 Grid 时选定后一般不要再动,运行时代码里用 Grid.WorldToCell 换算时,如果坐标方向总是偏一点,优先检查是不是这里设错了。Transform 旋转:Grid 根物体上的 Rotation 会影响整个坐标系的方向。有些美术同学为了场景“好看”会随手转个 45 度,结果运行时代码全乱。2D 场景里请保持 Rotation 为 0,美观的部分交给相机和 Sorting Layer 去处理。1.2 Tilemap:瓦片数据的容器与渲染入口Tilemap 组件挂在 Grid 的子物体上,它才是真正持有瓦片数据的角色。每个网格坐标对应哪块 Tile、颜色有没有染色、做没做翻转,这些信息都存在 Tilemap 里。Tilemap 组件上有几个高频使用的参数:Animation Frame Rate:控制动画瓦片的播放帧率,单位是帧/秒。素材如果没有特殊处理,默认值 1 每秒只播一帧,看起来像幻灯片。做水面、传送门动画时我会习惯调到 10 到 12,具体看序列帧张数和节奏需求。Color:把整张 Tilemap 染色。可以用它做全地图淡入淡出、夜晚氛围压暗、冷暖色调切换,不用逐格改素材。Collider Type:控制当前 Tilemap 的瓦片碰撞体生成方式,None、Sprite、Grid 三个选项,后面第四节我会详细展开。真正把数据画到屏幕上的,是挂在同一个物体上的 TilemapRenderer。它读取 Tilemap 数据,合成网格并参与渲染。日常开发里你只需要知道:默认的 Chunk 渲染模式会把相邻瓦片合并成大网格,这是 Tilemap 性能好的关键。1.3 Tile:最小单元资源,再也不怕改一处全图跟着乱Tile 是一个 ScriptableObject 资源,可以理解成“一个格子该长什么样”的说明书。最常见的 Tile 配置项包括:Sprite:要显示的那张图。Color:染色叠加,可以给同一张图做出不同色调的变体。Collider Type:这个瓦片自身参与碰撞的方式,优先级高于 Tilemap 组件上的全局设置。Transform 矩阵:左右翻转、上下翻转、旋转 90 度。同一个 Sprite 配合不同翻转可以拆成多个 Tile,画地图时会更灵活。这里有个非常实用的特性:画完地图后想调整某类瓦片的表现,只需要双击对应的 Tile 资源修改属性,所有使用这个 Tile 的格子会同步更新。对比一下以前手动摆 Sprite 的做法——你摆了一面墙,突然想给墙根加一条阴影边,得挨个选中再换图,TileMap 直接改资源就行,这就是它效率高的底层原因。2. Tile Palette 与笔刷实操:把画地图变成“涂色”搞清楚架构之后,下一步就是动手把地图画出来。TileMap 的编辑器体验核心在 Tile Palette 窗口,如果你还没找到它,菜单路径是 Window > 2D > Tile Palette。2.1 准备工作:图集切分、Palette 创建与素材命名在创建 Paletter 之前,先把瓦片素材整理好。建议把同一类素材(比如地面、墙体)放进一张图集,用 Sprite Editor 的切片功能切成多个 Sprite。如果项目是 3D 模板,还需要在 Package Manager 里确认 2D Tilemap Editor 相关包已经装上,否则可能找不到 Tile Palette 相关菜单。接着在 Tile Palette 窗口左上角点 Create New Palette,弹出的面板里有几个值得注意的地方:Name:调色板名称,建议按地图风格或场景命名,比如 Forest_Floor、Dungeon_Wall。Cell Size:默认 Automatic,Unity 会根据你素材的 PPU 自动换算单格尺寸。手动指定也行,但确保和 Grid 上的 Cell Size 一致。Grid 预设:矩形、等距、六边形,必须和你的地图布局匹配。这一步选错了,后面画出来的地图坐标全是乱的。保存路径:我习惯放在 Assets/Art/Palettes 下面,资源分类清晰一些。创建完成后,把切好的 Sprite 直接拖进 Palette 窗口,Unity 会自动生成对应的 Tile 资源。注意 Tile 资源的默认名字直接复用 Sprite 名,所以 Sprite 命名越规范,后面越好找。我的习惯是 Floors_01、Walls_Left_01 这样带类型前缀加编号的命名,调 RuleTile 时能省很多眼力。2.2 常用笔刷与高效绘制习惯Tile Palette 的笔刷不少,但真正高频使用的就那么几个:Pencil(铅笔):单格逐点画,适合补细节、画零散装饰。Marquee(框选):框选一片已有瓦片,然后可以整体复制、涂抹到别的位置。做重复房间、重复地块结构时效率极高。Eraser(橡皮):清空格子。Fill(油漆桶):填充连通的同色区域。注意它的连通规则和图像软件一致——如果目标区域已经被别的瓦片隔断,油漆桶不会穿过去。Move(移动):整体移动选中的瓦片区域,会改变这些格子的坐标。用之前记得确认碰撞体和周围瓦片的边界,不然容易挪出问题。画地图最容易翻车的不是笔刷本身,而是图层意识。我强烈建议同一个 Grid 下挂多个 Tilemap 子物体,按用途分层:背景层(Background):远景、天空盒效果的瓦片。地面层(Ground):玩家能走的实心地面。墙壁层(Wall):阻挡和遮挡角色的大体积墙体。装饰层(Props):花草、桌椅、箱子这类不参与核心碰撞的物件。这些 Tilemap 共享同一个 Grid,坐标系统完全一致。绘制时在 Tile Palette 上方切换 Active Tilemap 就能在同一个格子里分层画。渲染顺序通过 Sorting Layer 控制,背景层最低、建筑层中间、角色层最高,遮挡关系一目了然。这里有一个必须要养成的习惯:不要为了排序去改 Tilemap 的 Z 轴坐标。2D 场景用 Z 轴控制遮挡会和 Shader 深度、相机裁剪纠缠在一起,容易产生莫名其妙的闪烁和排序 bug。老老实实把 Sorting Layer 规划好,比什么都管用。2.3 批量创建 Tile 与资源管理小技巧如果素材切了几十上百张 Sprite,挨个拖进 Palette 会崩溃。可以直接在 Project 窗口里选中多个 Sprite,一起拖进 Tile Palette 窗口,Unity 会批量生成对应的 Tile 资源。生成后建议把它们统一放到一个 Tile 资源文件夹里,不要和 Sprite 混在一起。还有一个很省事的操作:批量修改多个 Tile 资源。你可以在 Project 窗口里选中所有墙体 Tile,然后在 Inspector 里直接把 Collider Type 统一改成 Grid。如果一个个点,点个几十次,再好的脾气也磨没了。3. RuleTile 与高级瓦片:让地形自己长出连接边先想一个问题:画一面石墙时,墙体中间是完整砖块纹理,到了墙的边缘,你应该手动挑“边缘过渡”素材来贴合。如果墙拐了一个角,还要找对应的内角、外角素材。地形越复杂,手动挑选素材的工作量越离谱。RuleTile 解决的正是这件事——让瓦片根据周围邻居的状态自动选择显示哪张图。3.1 理解 RuleTile 的规则图RuleTile 是 Unity 2D Tilemap 生态里最实用的一块,官方也把它做成了内置能力。它的核心是一个 3x3 的九宫格规则图:中间那一格是当前瓦片自身,不需要配置;周围八格用来表达邻居状态。每一种状态,你可以指定一张或一组 Sprite:如果上下都是同类地面,当前格子显示中段纹理。如果上面是空,显示顶部边缘或墙体收口纹理。如果左上角有缺口,显示内角过渡纹理。斜对角方向缺角时,显示角块。配置里有几种匹配模式:要求邻居是某类瓦片、要求邻居不是某类瓦片、或者邻居是什么都可以。规则组合起来之后,你在地图上画一个多边形区域,RuleTile 会自动把边界、转角、对角线全部补齐,画出来的地形自然到像手工精修过一样。还有一个隐藏功能:TPipeline 里经常用到的随机输出和动画输出。RuleTile 的 Output 可以设置为 Random,同一规则下从多张 Sprite 里随机挑一张。做草地、碎石地面时,同样的规则多配几块不同纹理,画出来就不会有整片“复制粘贴”的廉价感。3.2 创建与配置 RuleTile 的实操流程创建 RuleTile 很简单:Project 窗口右键 → Create → 2D → Tiles → Rule Tile。但配置才是重头戏。把最基础的地形 Sprite 拖进 RuleTile 的 Sprite 字段,然后双击打开 RuleTile 编辑器。编辑器的界面就是一个 3x3 格子,你需要把素材按方向对应的纹理拖进对应格子。比如:中间空格表示当前自身。上方格子拖入“顶部边缘”纹理。右上方格子拖入“右上角”纹理。左边格子拖入“左边边缘”纹理,依此类推。关键是保持素材命名和方位的一致性。大多数地形素材包都会按九宫格顺序提供中间、右上、上、左上、左、右、右下、下、左下的切片,但不同素材包排序可能不一样。我建议先用一张小草图记录素材与方位的对应关系,配置时对照着拖,比盯着平铺图瞎猜靠谱得多。配置完成后把 RuleTile 拖进 Tile Palette,就能像普通瓦片一样画地图了。一旦某个地形区域发生变化,RuleTile 会自动刷新邻居关系,这个过程叫 RefreshTile。3.3 AnimatedTile、RandomTile 与混用的注意点除了 RuleTile,日常地面绘制还会用到几个官方瓦片类型:AnimatedTile:把多帧 Sprite 拖进去做成动画瓦片,适合水面、传送门、发光矿脉。RandomTile:从一组 Sprite 里随机选一张显示,没有邻居检测能力,但胜在简单,适合不需要地形逻辑的碎石地、草堆。PipelineTile:冷门但实用,它只检测左右连接状态,自动选直管、弯管、端盖素材,做管道类横版游戏非常合适。混用这些瓦片时有一个性能隐患:动画瓦片每帧切换 Sprite,会导致所在渲染块持续重建。如果一张地图上挂了上百个动画瓦片,低端移动设备上帧率会掉得非常明显。我实测过的经验是:动画瓦片数量尽量控制在几十个以内,再多就把动画改成 Shader UV 偏移或顶点色动画,让 GPU 去处理,而不是每帧换 Sprite。如果遇到 RuleTile 不自动变化的玄学问题,最常见的两个原因就是刷新没触发和规则方向理解反了。排查方法很简单:运行时调用 Tilemap.RefreshAllTiles() 强制刷新一遍,看瓦片有没有变;如果变了,说明只是刷新时机不对,如果没变,回去检查规则图里“要求匹配的是上方的格子还是下方的格子”,很多素材包的方向约定和你想的正好相反。4. 碰撞体系统与运行时交互:让地图真正可玩地图画得再漂亮,没有碰撞体,角色走过去还是会直接掉穿。TileMap 的碰撞体配置逻辑和普通 Sprite 不太一样,理解对了才能少走弯路。4.1 Tilemap Collider 2D 与 Tile 的 Collider Type给挂有 TilemapRenderer 的那个 GameObject 添加 Tilemap Collider 2D 组件后,系统会为每个有 Tile 的格子生成碰撞形状。但这个形状长什么样,得由 Tile 资源里的 Collider Type 决定:None:不参与碰撞,适合纯装饰层。Sprite:按 Sprite 的实际图形轮廓生成碰撞。适合不规则障碍物、镂空边缘,最灵活但网格多边形也更复杂。Grid:按整个格子生成矩形碰撞。性能好、行为直观,绝大多数地面和墙体我都用 Grid。批量调整 Collider Type 的技巧上一节已经提过:在 Project 里选中多个 Tile,统一在 Inspector 改,效率极高。4.2 Composite Collider 2D:把碎片焊成整体如果一张墙由 300 个格子组成,每个格子一个独立碰撞体,那物理引擎要同时处理 300 个多边形。玩家斜向移动时,经常会卡在格子之间的接缝处,听起来像“呲溜”一下被什么东西绊住了。这个问题在斜边、弧线地形上尤其糟糕。解决办法是 Composite Collider 2D。操作流程如下:在 Tilemap 所在 GameObject 上添加 Tilemap Collider 2D。再添加 Composite Collider 2D 组件。回到 Tilemap Collider 2D,把 Used By Composite 勾上。勾选之后,物理引擎会把所有瓦片的碰撞形状合并成一个大的复合碰撞体。格子之间的缝隙被焊接掉,斜向移动也顺滑了。Unity 会自动在这个 GameObject 上添加一个 Rigidbody 2D 并设为 Static,作为复合碰撞体的载体。这个刚体不参与物理模拟,只是一个必要的挂载条件,不用管它。这里有一个容易踩的坑:Composite Collider 2D 的 Geometry Type 有 Outlines 和 Polygons 两种。Outlines 生成的是外部轮廓,适合装饰性边界;Polygons 生成的是内部填满的多边形,适合不可穿过的实心地形。做关卡地形我推荐 Polygons,做山体轮廓或视觉边界可以用 Outlines,反正两者切换很方便,哪边物理表现不对就换另一边试。4.3 运行时改瓦片:从单纯绘制到动态世界TileMap 的另一半价值在运行时动态修改。采集矿石、铺路、拆墙、种田这类玩法,全部建立在运行时改瓦片的能力上。核心 API 其实就那么几个,先记下来:tilemap.SetTile(pos, tile):在指定坐标放一块瓦片。tilemap.SetTiles(posArray, tileArray):批量放置,效率远高于循环单个调用。tilemap.GetTile(pos):获取指定坐标当前的瓦片。tilemap.HasTile(pos):判断坐标是否有瓦片。tilemap.GetTilesBlock(bounds):按矩形区域批量获取瓦片数组,适合做范围遍历。tilemap.ClearAllTiles():清空整个 Tilemap。tilemap.RefreshTile(pos) / RefreshAllTiles():刷新规则瓦片状态,强制邻居联动。最容易被坑的是坐标系换算。鼠标点击拿到的是世界坐标,不能直接往 SetTile 里塞,必须先通过 Grid 组件转一下:Vector3Int cellPos = grid.WorldToCell(worldPoint);然后才能拿这个 cellPos 去读写瓦片。这个转换逻辑和 Grid 的 Cell Layout 强相关,等距网格和矩形网格的换算结果完全不同。我建议开发时先写一个 Gizmos 脚本,把鼠标所在的格坐标显示出来,验证换算结果的正确性,再来写玩法逻辑,比直接盲写稳太多。还有一个经验必须分享:运行时批量更新瓦片,尤其是一帧内改几百上千个坐标,请务必用数组版本 API。我在某个模拟项目里做过对比,同样改 1000 个格子,把数据整理成数组一次性 SetTiles 比循环 SetTile 快好几倍。低端移动设备上这个差距会直接变成肉眼可见的卡顿和血条下降。5. 性能优化与常见问题排查:从能用到好用TileMap 用多了你就会发现,它的性能上限和学习门槛都不在“画地图”这一步,而是看你能不能理解它的渲染机制和优化边界。5.1 渲染模式与图集优化TilemapRenderer 默认是 Chunk 模式,这是我前文反复强调的重点:相邻瓦片会自动合并成一个大网格,DrawCall 被压得非常低。这是 TileMap 系统相比裸摆 Sprite 的最大优势。但 Chunk 模式也有几个禁忌:同一 Tilemap 上不要混太多不同 Material 或不同 Sorting Layer 的瓦片,每一次状态切换都会打断合批。瓦片素材要尽量放入同一个 Sprite Atlas。Chunk 模式虽然会合并网格,但如果瓦片引用的 Sprite 来自散图,纹理切换依然会带来额外开销。我在项目里统一用 Sprite Atlas 打包地图素材,Padding 设成至少 2 到 4 像素,边缘黑边问题基本没再出现过。超大地图不要只挂一个 Tilemap。哪怕它们共享同一个 Grid,按区域拆分成多个 Tilemap,也能大幅减少单次网格更新和裁剪计算。类似 200x200 的开放地图,我会拆成几个区块,每块独立 Tilemap,按需加载,运行时的负担分摊开。另外一个模式是 Individual,它让每个瓦片单独渲染,方便调试单块瓦片纹理问题,但性能比 Chunk 差一截,调试完记得切回来。5.2 瓦片缝隙、黑边与 PPU 对齐瓦片之间出现黑边、细缝,几乎是每个 TileMap 新人的第一个视觉灾难。根因通常出在三个位置:Sprite 的 PPU 与 Cell Size 不一致,素材被拉伸后出现采样误差。图集内 Sprite 之间的 Padding 不足,相邻瓦片互相渗透像素。Filter Mode 是 Bilinear,像素风瓦片被模糊处理后边缘出现半透明过渡。解决思路也很直接:素材 PPU 与 Grid Cell Size 匹配;图集 Padding 不低于 2 像素;纯像素风素材的 Filter Mode 统一设成 Point。这一套流程走完,黑边问题基本根治。还有一个小坑:同一张素材如果被用于多个 Tile,不要在某个 Tile 里单独改 Scale 或 Rotation,否则看起来像偏移的格子。想要不同方向的显示,正确做法是复制 TILE 资源,在 Transform 矩阵里设置翻转,而不是在场景里旋转瓦片。5.3 现实项目中高频踩坑实录我把这几年在项目里反复遇到的问题整理成一张速查表,几乎每个 2D 项目都能用到。现象可能原因处理办法瓦片之间有黑边或缝隙PPU 与 Cell Size 不匹配、图集 Padding 太小、Filter Mode 为 Bilinear统一 PPU,Padding 不低于 2,瓦片纹理设 Point地图能画但角色直接穿墙Tile 的 Collider Type 是 None,或 Tilemap Collider 组件没加把 Tile 的 Collider Type 改为 Grid,添加 Tilemap Collider 2DRuleTile 不按预期匹配规则图方向理解反了,或刷新未触发检查九宫格方向约定,运行时调用 RefreshAllTiles 验证DrawCall 高、运行卡顿TilemapRenderer 被设成 Individual,或瓦片散图未打包改回 Chunk,统一放入 Sprite Atlas多个 Tilemap 之间排序忽上忽下Sorting Layer 或 Order 未明确区分使用 Sorting Layer + Order 统一管理,不要依赖创建顺序程序改瓦片后画面有残影直接覆盖旧瓦片,网格边界没刷新先 ClearAllTiles 再 SetTiles,或调用 RefreshTile 刷新局部动画瓦片多了掉帧AnimatedTile 每帧切换 Sprite 导致网格重建控制动画瓦片数量,改用 Shader UV 动画Isometric 地图点击位置对不上Grid 坐标换算没考虑布局类型用 Grid.WorldToCell,并写 Gizmos 高亮调试坐标转换表格里最后一条值得多说一句:等距地图的瓦片绘制和点击判定一直是社区高频问题。大多数人踩坑的原因是直接从鼠标屏幕坐标换算到世界坐标,却没有意识到 Grid 的 Cell Layout 已经把坐标系做了偏移和错切。遇到这种问题,不要盯着计算式蒙答案,直接在 OnDrawGizmos 里把鼠标所在格画出来,眼睛看到的效果远比脑子里的推演可靠。6. 数据持久化与模块化设计:TileMap 的进阶用法如果你只把 TileMap 当成编辑器里的画图工具,那其实还用不到它一半的价值。真正的进阶用法是把 Tilemap 当作战场数据源,让关卡生成、存档读取、运行时交互统一走一条数据通路。6.1 持久化:TileMap 不会替你存进度Tilemap 里的数据只在场景运行时存在,退出场景一切恢复原状。如果玩家在场景里挖了矿、拆了墙、种了地,你需要把这些变化记录到自己的存档系统里。我的做法是建一个二维或三维数组,以网格坐标为主键,记录两个值:坐标位置和瓦片资源引用。保存时遍历所有受影响的 Tilemap,把瓦片数据序列化进存档;读取时创建新的 Tilemap,遍历存档数据批量 SetTiles。这里要注意:存档数据结构不要绑定 Unity 的 Tile 资源实例,因为在不同平台、不同资源版本里,Tile 资源的 GUID 才是稳定标识。你存 GUID,读取时再根据 GUID 找到对应资源,这样切换平台、打补丁时不会丢瓦片。还要考虑一种常见情况:同一块区域可能同时存在多个 Tilemap 图层。存档时不要只记坐标和 Tile,还要记这个坐标属于哪一层。否则读档时地面层的数据写进了装饰层,画面立刻乱给你看。6.2 用 Tilemap 做关卡数据的读取与验证既然 Tilemap 本身就是数据,那么它可以同时承担“关卡编辑工具”和“关卡数据源”两个角色。你可以在编辑器中通过 Tile Palette 快速搭一关,然后运行时代码遍历检查玩家出生点、终点、障碍物分布,生成小地图或者自动验证关卡合理性。这个思路在自动生成关卡、每日挑战这类玩法里尤其顺手。我强烈建议把地图数据访问稳定封装成一个方法,比如 GetCellAt(pos, layer) / SetCellAt(pos, layer, tile)。这样玩法逻辑里不会散落一堆 Grid 换算和 Tilemap API 调用,出问题的时候排查范围也小。之前在某项目里因为直接在多个脚本里直接调用 SetTile,结果存档和瓦片状态偶尔不一致,最后把读写全部收拢到一个管理类里,问题才算根治。6.3 分区、多 Tilemap 与按需加载的配合前文提过把大地图拆成多个 Tilemap 区块,这里展开说下配合按需加载的设计。假设你的地图很大,玩家视角只覆盖其中一小块。一次性把所有区块的 Tilemap 挂进场景,内存和网格更新压力都会堆积。更合理的做法是:场景里只保留当前视野附近的两个到三个区块,玩家移动过界时,把远处的区块卸载或隐藏,同时把新进来的区块加载出来。配合 Tilemap 的 Chunk 渲染模式,每个区块的网格只在初始化时生成一次,运行时的动态变更只影响局部,整体帧数会稳定很多。这个方案的前提是区块划分和网格坐标系一致。我习惯直接以 Grid 的像素坐标边界来划分区块,比如每 32 格一个区块,区块名字就叫 Region_X_Y,这样程序里计算当前玩家位于哪个区块、该加载哪几个邻居区块,只是简单的坐标除法,连查表都不用。结尾做了这么久 2D 项目,我的体会是 TileMap 系统真正厉害的地方不在于“能画地图”,而在于它把关卡数据、渲染、碰撞、编辑器工具和运行时逻辑整合在同一条数据管线上。编辑器里涂几笔,运行时改几个坐标,存档系统再同步一次,一个动态关卡就立住了。反过来,如果一开始不理解 Grid、Tilemap、Tile 三者的分工,不明白 Chunk 渲染和 RuleTile 的边界,遇到性能瓶颈时真的会非常痛苦。以上这些经验都是我一个个坑踩出来的,希望你能直接抄走,少走几轮弯路。如果你在项目里也有关于 TileMap 的骚操作或邪门问题,欢迎一起交流。