# **Tilemaps and Tilesets in Unity 6 (6000.5+)** > **Target:** Unity 6000.5 and above. Packages: **2D Tilemap Editor** (`com.unity.2d.tilemap`, built in to the 2D templates) and **2D Tilemap Extras** (`com.unity.2d.tilemap.extras`, 6.x line for Unity 6). Input uses the **Input System** package (the default active input handler in new Unity 6 projects). What we'll build: a small side-on level painted from a sliced sprite sheet, with rule tiles for terrain that auto-picks edges/corners, an animated water tile, a custom C# "ore" tile that randomizes its sprite, a single merged collider for the whole ground, and a runtime digger that removes and places tiles with the mouse. --- ## 1. Project setup 1. Unity Hub > **New Project** > **Universal 2D** template (URP 2D renderer) > name it `TilemapLab`. 2. **Window > Package Management > Package Manager** > Unity Registry. Confirm **2D Tilemap Editor** is installed and install **2D Tilemap Extras**. - *Why:* the editor package provides the Tilemap, Tile Palette and `Tile` classes; Extras adds `RuleTile`, `AnimatedTile`, `RuleOverrideTile`, Random brushes, GameObject brush, etc. 3. Make sure **Edit > Project Settings > Player > Active Input Handling** is *Input System Package (New)* or *Both*. --- ## 2. Importing and slicing a sprite sheet (the tileset) 1. Drop your sheet (e.g. `terrain.png`, 16×16 px tiles) into `Assets/Art/Tiles/`. 2. Select it; in the Inspector set: - **Texture Type:** Sprite (2D and UI) - **Sprite Mode:** Multiple - **Pixels Per Unit:** `16` (one tile = one world unit = one grid cell) - **Filter Mode:** Point (no filter); **Compression:** None (pixel art) - Apply. 3. Click **Open Sprite Editor** > **Slice** dropdown > Type **Grid By Cell Size**, Pixel Size `16 × 16`, add **Padding/Offset** if the sheet has gutters > **Slice** > **Apply**. 4. *Tip — seams:* if you see hairline gaps between tiles at runtime, add a **Sprite Atlas** (Assets > Create > 2D > Sprite Atlas) containing the sheet with padding 2–4 px, or use the **Pixel Perfect Camera** component on the camera. **Why PPU matters:** the Grid's cell size defaults to 1×1 unit. If PPU ≠ tile pixel size, tiles render too big/small relative to cells. ### What to draw in `terrain.png` The rest of this tutorial expects these pieces, so plan the sheet before you open your pixel editor. Every cell is 16x16 px on a transparent background, pixel-aligned with no gutters. A **128 x 64 px sheet (8 columns x 4 rows)** holds everything: ``` col: 0 1 2 3 4 5 6 7 row 0 [TL ] [T ] [TR ] [ICtl] [ICtr] [PL ] [PM ] [PR ] row 1 [L ] [C ] [R ] [ICbl] [ICbr] [SOLO] [F2 ] [F3 ] row 2 [BL ] [B ] [BR ] [ORE1] [ORE2] [ORE3] [GEM ] [ -- ] row 3 [W1 ] [W2 ] [W3 ] [W4 ] [GR1 ] [GR2 ] [FLW1] [FLW2] ``` | Group | Cells | What to draw | Used by | |---|---|---|---| | **Ground 3x3 block** | `TL T TR / L C R / BL B BR` | Outer edges and corners of a solid dirt mass. The top row carries the grass lip. `C` is the plain center fill. | `GroundRule` (section 5) | | **Inner corners** | `ICtl ICtr ICbl ICbr` | Small notches of grass or edge in one corner of an otherwise filled tile, for concave shapes. | `GroundRule`, so holes from the digger re-tile cleanly | | **Thin platforms** | `PL PM PR`, `SOLO` | Left cap, middle and right cap for 1-tile-high ledges, plus a lone single block. Without these, 1-wide shapes get the wrong sprite. | `GroundRule` | | **Fill variants** | `F2 F3` | Two more versions of `C` with small differences (pebbles, roots). They must tile seamlessly on all four sides. | Random sprite output on the center rule | | **Ore** | `ORE1-3`, `GEM` | Ore specks drawn on top of the `C` dirt so they blend. `GEM` is the rare version that `OreTile` tints. | `OreTile` (section 7) | | **Water** | `W1-W4` | A 4-frame loop. Make frame 4 lead smoothly back into frame 1. | `Water` Animated Tile (section 6) | | **Decor** | `GR1 GR2 FLW1 FLW2` | Grass tufts and flowers that sit on top of the ground, with mostly transparent pixels. | `Decor` tilemap and Random Brush, no collider | **Drawing tips** - Draw the center fill `C` first and test it tiled 4x4 before you draw any edges. Every other ground tile borrows its texture. - Keep the edge pixels of `T` identical across `TL`, `T` and `TR` where they meet, so a long row reads as one surface. - Add 1 px of shading under the grass lip for depth. It reads well at 16 px. - You don't need gutters between cells. If you add them, set the matching **Padding** when you slice. - Leave unused cells (`--`) fully transparent. Delete their empty sprites after slicing, or just ignore them. **Example sheets to study** - [Pixel Adventure 1 by Pixel Frog](https://pixelfrog-assets.itch.io/pixel-adventure-1) is free, 16x16, and its terrain blocks follow almost exactly this layout. - [Sunny Land by ansimuz](https://ansimuz.itch.io/sunny-land-pixel-game-art) is a free 16x16 side-view set with grass-topped terrain, platforms and decor. - [Kenney Pixel Platformer](https://kenney.nl/assets/pixel-platformer) is CC0 and good for class handouts. Its tiles are **18x18**, so use PPU 18 and an 18 x 18 slice. - [Unity 2D Tilemap Extras samples (GitHub)](https://github.com/Unity-Technologies/2d-extras) include rule tile examples with matching sprites. - The [Rule Tile manual page](https://docs.unity3d.com/Packages/[email protected]/manual/RuleTile.html) shows how neighbor rules map onto these edge and corner tiles. --- ## 3. Grid, Tilemaps and layers 1. Hierarchy > **+ > 2D Object > Tilemap > Rectangular**. This creates `Grid` (with a `Grid` component) and a child `Tilemap` (with `Tilemap` + `TilemapRenderer`). 2. Rename the child `Ground`. Add two more tilemaps under the same Grid: `Background` and `Decor`. 3. Sort them: on each `TilemapRenderer` set **Order in Layer** (Background = -10, Ground = 0, Decor = 10) or use Sorting Layers. 4. **TilemapRenderer Mode:** - **Chunk** (default) — batches tiles per chunk; fastest, but all sprites in a tilemap should share a texture/atlas. - **Individual** — sorts each tile separately (needed for isometric/Y-sorted tiles that overlap sprites). - **SRP Batch** — available in Unity 6 for URP; lets tiles batch through the SRP batcher. Other grid layouts: Hexagonal (Point/Flat Top) and Isometric / Isometric Z as Y — same API, different `Grid.cellLayout`. --- ## 4. Tile Palette: painting 1. **Window > 2D > Tile Palette**. Click **Create New Palette**, name `TerrainPalette`, Grid = Rectangle, save in `Assets/Tiles/Palettes/`. 2. Drag the sliced sheet (or selected sprites) into the palette window and choose a folder (`Assets/Tiles/Generated/`). Unity creates one `Tile` asset per sprite. 3. Choose the **Active Tilemap** dropdown at the top of the palette (e.g. `Ground`) and paint. 4. Tools and shortcuts: Select (S), Move (M), Paint (B), Box Fill (U), Pick (I), Erase (D), Flood Fill (G). `[` / `]` rotate the brush, Shift+`[` flips. Hold Ctrl/Alt for pick/erase shortcuts depending on tool. 5. Use the **Tile Palette's Brush** dropdown for Extras brushes: **Random Brush** (scatter grass), **Line Brush**, **GameObject Brush** (paint prefabs onto the grid, e.g. coins). *Explanation:* the palette is itself a tilemap prefab; tiles are assets referenced by your scene tilemaps, so editing a Tile asset (e.g. changing its collider type) updates every placed instance. --- ## 5. Rule Tiles (auto-tiling) 1. Project window > **Create > 2D > Tiles > Rule Tile**. Name `GroundRule`. 2. Set **Default Sprite** to the center-fill sprite and **Default Collider** to *Sprite* (or *Grid*). 3. Add rules (+). Each rule has a 3×3 neighbor grid: click a cell to cycle **green arrow = This** (same tile must be there), **red X = Not This**, empty = don't care. - Rule for "top edge": up = X, left/right/down = arrow → top-surface sprite. - Rules for left/right edges, outer corners, inner corners (diagonal X with orthogonal arrows). 4. **Order matters** — rules are evaluated top to bottom; first match wins. Put specific rules (corners) above general ones (edges). 5. Per-rule options: **Output** = Single / Random (with Perlin noise scale) / Animation; **Transform** = Fixed / Rotated / MirrorX / MirrorY (lets one corner rule cover all four orientations). 6. Drag `GroundRule` into the palette and paint — edges resolve automatically. 7. **Rule Override Tile** (Create > 2D > Tiles > Rule Override Tile): reuse `GroundRule`'s rules but swap sprites for a snow/desert variant. --- ## 6. Animated Tiles 1. **Create > 2D > Tiles > Animated Tile** → `Water`. 2. Set **Number of Animated Sprites** and drag frames in order. 3. **Minimum/Maximum Speed** (frames per second multiplier) — a random value in range per tile desyncs neighbours; **Start Time** and **Start Frame** offset the cycle. 4. **Tile Collider**: None for decorative water. Paint it on `Background`. Animated tiles are driven by the TilemapRenderer — zero scripts, and they work in Edit Mode preview from the palette. --- ## 7. Custom scriptable tile in C# Any class deriving from `TileBase` can be a tile. Override `GetTileData` to supply sprite/color/transform/collider per cell, and `RefreshTile` if neighbours must update when this tile changes. This `OreTile` picks a deterministic random sprite from its position (stable across reloads), tints rare variants, and refreshes neighbours so adjacent ore tiles can react. ```csharp // Assets/Scripts/Tiles/OreTile.cs using UnityEngine; using UnityEngine.Tilemaps; [CreateAssetMenu(fileName = "OreTile", menuName = "2D/Tiles/Ore Tile")] public class OreTile : TileBase { [Tooltip("Possible sprites; one is chosen per cell from a position hash.")] public Sprite[] sprites; [Range(0f, 1f)] public float rareChance = 0.1f; public Color rareTint = new Color(1f, 0.85f, 0.3f); public Tile.ColliderType colliderType = Tile.ColliderType.Grid; // How much "ore" a cell yields when mined (read by TileDigger). public int yield = 1; public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData) { if (sprites == null || sprites.Length == 0) return; uint h = Hash(position); tileData.sprite = sprites[h % (uint)sprites.Length]; bool rare = (h % 1000u) / 1000f < rareChance; tileData.color = rare ? rareTint : Color.white; tileData.transform = Matrix4x4.identity; tileData.flags = TileFlags.LockTransform; // allow runtime color changes (no LockColor) tileData.colliderType = colliderType; } public override void RefreshTile(Vector3Int position, ITilemap tilemap) { // Refresh this cell and the 8 neighbours (useful if visuals depend on neighbours). for (int y = -1; y <= 1; y++) for (int x = -1; x <= 1; x++) { var p = new Vector3Int(position.x + x, position.y + y, position.z); if (tilemap.GetTile(p) == this) tilemap.RefreshTile(p); } } static uint Hash(Vector3Int p) { unchecked { uint h = 2166136261u; h = (h ^ (uint)p.x) * 16777619u; h = (h ^ (uint)p.y) * 16777619u; h = (h ^ (uint)p.z) * 16777619u; h ^= h >> 13; h *= 0x5bd1e995; h ^= h >> 15; return h; } } } ``` **Use it:** Create > 2D > Tiles > Ore Tile, assign 3–4 rock sprites, drag the asset into the palette, paint. *Explanation:* `TileBase` assets are shared — never store per-cell state in fields. Derive per-cell variety from `position` (as above) or keep state in a separate component/dictionary keyed by cell. You can also subclass the concrete `Tile` class when you just need to tweak `GetTileData` while keeping its sprite/color/collider fields, or override `GetTileAnimationData` for code-driven animation and `StartUp` to spawn a GameObject per tile. --- ## 8. Collisions: TilemapCollider2D + CompositeCollider2D 1. Select `Ground` > Add Component **Tilemap Collider 2D**. Every tile whose collider type is Sprite/Grid gets a shape — many small boxes, which cause "ghost collisions" where characters snag on seams. 2. Add **Composite Collider 2D** to the same object. Unity adds a **Rigidbody 2D** — set **Body Type: Static**. 3. On the Tilemap Collider 2D set **Composite Operation: Merge** (Unity 6 replaced the old *Used By Composite* checkbox with `compositeOperation`). 4. On the Composite Collider 2D choose **Geometry Type: Outlines** (edge chains, best for platformer ground) or **Polygons** (needed if you want `OverlapPoint` inside solid areas / trigger volumes). 5. Optional: **Tilemap Collider 2D > Max Tile Change Count** controls when a full rebuild happens instead of incremental updates — relevant for runtime digging. For decoration tilemaps, set tiles' collider type to None or just don't add a collider. **Player for testing** (dynamic Rigidbody2D + CapsuleCollider2D, Input System): ```csharp // Assets/Scripts/SimplePlatformer.cs using UnityEngine; using UnityEngine.InputSystem; [RequireComponent(typeof(Rigidbody2D))] public class SimplePlatformer : MonoBehaviour { public float moveSpeed = 6f; public float jumpVelocity = 11f; public LayerMask groundMask; public Transform groundCheck; public float groundRadius = 0.15f; Rigidbody2D rb; void Awake() => rb = GetComponent<Rigidbody2D>(); void Update() { var kb = Keyboard.current; if (kb == null) return; float x = 0f; if (kb.aKey.isPressed || kb.leftArrowKey.isPressed) x -= 1f; if (kb.dKey.isPressed || kb.rightArrowKey.isPressed) x += 1f; Vector2 v = rb.linearVelocity; // Unity 6 name (was velocity) v.x = x * moveSpeed; bool grounded = Physics2D.OverlapCircle(groundCheck.position, groundRadius, groundMask); if (grounded && kb.spaceKey.wasPressedThisFrame) v.y = jumpVelocity; rb.linearVelocity = v; } } ``` Put `Ground` on a `Ground` layer and assign it to `groundMask`; set the player's Rigidbody2D **Interpolate** and **Collision Detection: Continuous**. --- ## 9. Runtime tile editing Key `Tilemap` API: `WorldToCell`, `CellToWorld`, `GetCellCenterWorld`, `GetTile<T>`, `HasTile`, `SetTile`, `SetTiles` (batch), `SetTilesBlock`, `BoxFill`, `RefreshTile`, `SetColor` (requires the tile's `TileFlags.LockColor` cleared), `SetTileFlags`, `CompressBounds`, and the static `Tilemap.tilemapTileChanged` event. ```csharp // Assets/Scripts/TileDigger.cs using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.Tilemaps; public class TileDigger : MonoBehaviour { public Camera cam; public Tilemap ground; public TileBase placeTile; // e.g. GroundRule public float maxReach = 6f; public Transform player; public int OreCollected { get; private set; } void Update() { var mouse = Mouse.current; if (mouse == null) return; Vector3 world = cam.ScreenToWorldPoint(mouse.position.ReadValue()); world.z = 0f; Vector3Int cell = ground.WorldToCell(world); if (player && Vector2.Distance(player.position, ground.GetCellCenterWorld(cell)) > maxReach) return; if (mouse.leftButton.wasPressedThisFrame) Dig(cell); else if (mouse.rightButton.wasPressedThisFrame) Place(cell); } void Dig(Vector3Int cell) { TileBase tile = ground.GetTile(cell); if (tile == null) return; if (tile is OreTile ore) OreCollected += ore.yield; ground.SetTile(cell, null); // rule tiles around it re-evaluate automatically Debug.Log(quot;Dug {tile.name} at {cell}. Ore: {OreCollected}"); } void Place(Vector3Int cell) { if (ground.HasTile(cell)) return; // Don't bury the player. if (player) { Vector3 c = ground.GetCellCenterWorld(cell); if (Physics2D.OverlapBox(c, Vector2.one * 0.9f, 0f, 1 << player.gameObject.layer)) return; } ground.SetTile(cell, placeTile); } // Batch example: fill a rectangle in one call (far cheaper than many SetTile calls). public void FillRect(Vector3Int min, Vector3Int size, TileBase tile) { var bounds = new BoundsInt(min, new Vector3Int(size.x, size.y, 1)); var tiles = new TileBase[size.x * size.y]; for (int i = 0; i < tiles.Length; i++) tiles[i] = tile; ground.SetTilesBlock(bounds, tiles); } // Flash a cell red (works because OreTile doesn't set LockColor). public void Highlight(Vector3Int cell) { ground.SetTileFlags(cell, TileFlags.None); ground.SetColor(cell, Color.red); } } ``` The `TilemapCollider2D` + composite update automatically after `SetTile`; Rule Tiles refresh their neighbours so edges re-form around holes. **Procedural generation sketch** — fill from noise using `SetTiles` with `TileChangeData` or position/tile arrays: ```csharp // Assets/Scripts/NoiseTerrain.cs using System.Collections.Generic; using UnityEngine; using UnityEngine.Tilemaps; public class NoiseTerrain : MonoBehaviour { public Tilemap ground; public TileBase dirt; // GroundRule public TileBase ore; // OreTile public int width = 120, depth = 30, surfaceHeight = 8; public float scale = 0.08f, oreThreshold = 0.78f; public int seed = 1234; void Start() => Generate(); [ContextMenu("Generate")] public void Generate() { ground.ClearAllTiles(); var positions = new List<Vector3Int>(); var tiles = new List<TileBase>(); float off = seed * 0.137f; for (int x = 0; x < width; x++) { int top = surfaceHeight + Mathf.RoundToInt(Mathf.PerlinNoise(x * scale + off, off) * 6f); for (int y = -depth; y < top; y++) { float n = Mathf.PerlinNoise(x * 0.2f + off, y * 0.2f - off); positions.Add(new Vector3Int(x, y, 0)); tiles.Add(n > oreThreshold && y < top - 3 ? ore : dirt); } } ground.SetTiles(positions.ToArray(), tiles.ToArray()); ground.CompressBounds(); } } ``` --- ## 10. Worked example: put it together 1. Grid with `Background` (water AnimatedTile), `Ground` (GroundRule + OreTile; TilemapCollider2D Merge → CompositeCollider2D Outlines, static Rigidbody2D, layer Ground), `Decor` (grass via Random Brush, no collider). 2. Player: Sprite + Rigidbody2D (Dynamic, freeze rotation Z) + CapsuleCollider2D + `SimplePlatformer`; child `GroundCheck` at the feet. 3. Empty `Systems` object with `TileDigger` (cam, ground, placeTile = GroundRule, player) and optionally `NoiseTerrain` (or paint by hand). 4. Camera: add **Pixel Perfect Camera** (Assets PPU 16) for crisp pixel art. 5. Play: run/jump; left-click digs (edges re-tile, colliders update, ore counter increments in Console); right-click places ground. **Checkpoints / troubleshooting** - Tiles look offset by half a cell → sprite pivot isn't Center, or Tile Anchor on the Tilemap isn't (0.5, 0.5, 0). - Player snags on seams → composite not merging (check Composite Operation = Merge and Rigidbody2D static). - `SetColor` does nothing → tile has `LockColor`; clear flags with `SetTileFlags(cell, TileFlags.None)`. - Rule tile picks wrong sprite → reorder rules; specific before general. **Docs:** Unity Manual › 2D › Tilemaps; 2D Tilemap Extras package docs (Rule Tile, Animated Tile, Scriptable Tiles); Scripting API `Tilemap`, `TileBase`, `TilemapCollider2D.compositeOperation`. See also (added 2026-11-10): [[2D Animation and Sprite Tools]] - character skinning and Sprite Swap on the tilemap world. [[2D Lighting in URP]] - 2D Renderer lights and shadows for that world.