Tile Maps
Building worlds from blocks
Tile maps constructed game worlds from small reusable graphic blocks, enabling large levels within tight memory constraints while simplifying collision detection and level design.
Don’t draw a forest — draw one tree and repeat it. Tile maps build game worlds from small graphic blocks (typically 8×8 or 16×16 pixels) arranged in a grid. One set of tiles can compose endless levels. Super Mario Bros. uses around 256 unique tiles to build its entire Mushroom Kingdom. Memory-efficient, collision-friendly, and the foundation for almost every 2D level editor of the era.
For the concept of tile-based rendering and which platforms supported it, see Tile-Based Graphics; this entry focuses on the map data structure.
Fast facts
- Purpose: Construct levels from reusable blocks.
- Tile size: Typically 8×8 or 16×16 pixels.
- Memory saving: ~98% versus a 1-byte-per-pixel bitmap of the same screen.
- Usage: Nearly universal in 8/16-bit era.
Memory efficiency
Comparison for a single 256×240 NES screen:
| Method | Memory | Notes |
|---|---|---|
| 1-byte-per-pixel bitmap | 61,440 bytes | One palette index per pixel |
| Tile map (32×30 cells, 8×8 tiles) | 960 bytes | Plus a shared 4 KB tile set in CHR |
| Saving | ~98% | Tile set is paid once; map data alone is ~1.5% |
Tile map structure
| Component | Purpose |
|---|---|
| Tile set | Pixel definitions (CHR-ROM on NES, charset RAM on C64, VRAM on Mega Drive) |
| Map data | One byte (or word) per cell, indexing the tile set |
| Attributes | Per-region palette/flip/priority data |
| Collision flags | Solid, platform, hazard — usually a parallel array keyed by tile index |
C64: map data and block data
The C64 has no nametable; its screen is 1,000 character codes in RAM, and a tile map is whatever the program makes of it. The common shape, which Cadaver’s Codebase64 rant credits to Jukka Tapanimäki’s C-64 Pelintekijän Opas and to SEUCK, is a two-level scheme he calls map data and block data:
| Level | Contents | Notes |
|---|---|---|
| Map data | One byte per block, rows stored end to end from the top of the map | 8-bit block numbers, so 256 blocks at most; a map header records the row length in blocks |
| Block data | The screen codes for each block, rows end to end | Cadaver prefers 4×4; “the block size should be a power of two” for cheap arithmetic |
| Address tables | Precalculated start address of every map row and every block | Avoids multiplication on the 6502 |
| Colour | Either one colour per block or one per character | Per-block colour saves raster time but is “obviously less colourful” (Achim) |
Drawing a new column or row at the screen edge means tracking the top-left corner’s position in block coordinates plus the offset within a block, and decoding the right slice of each block as it comes into view; in eight-way scrolling both the X and Y offsets within the block matter. Cadaver notes the scheme is a choice rather than a necessity — Chris Butler’s Commando and Ghosts’n Goblins store whole screens as raw character codes “and do just fine” — and mentions blocks made of smaller blocks in Faery Tale Adventure as the way to scroll much larger maps.
NES nametables
The NES PPU stores one screen’s worth of tile indices in a nametable:
| Element | Specification |
|---|---|
| Tile size | 8×8 pixels |
| Nametable | 32×30 tiles = 960 bytes |
| Pattern tables | 256 tiles each (two 4 KB tables in CHR memory) |
| Attribute table | 64 bytes after each nametable; 4×4 tiles per byte, 2×2 tile palette resolution |
| Hardware nametables | 4 logical, 2 in PPU VRAM; mirroring (horizontal / vertical / four-screen via mapper) decides how the four logical tables overlap |
Nametable address layout
$2000 + (row × 32) + col ; row 0-29, col 0-31
For example, the bottom-right tile of nametable 0 is at $2000 + (29 × 32) + 31 = $23BF. The 64 attribute bytes for that nametable live at $23C0-$23FF.
Attribute byte layout
Each attribute byte covers a 32×32-pixel block (4×4 tiles), divided into four 2×2-tile palette quadrants:
bits 7-6: bottom-right 2×2 tiles
bits 5-4: bottom-left 2×2 tiles
bits 3-2: top-right 2×2 tiles
bits 1-0: top-left 2×2 tiles
Two tiles in the same 2×2 quadrant are forced to share a palette — a constraint that shapes NES tile design.
Collision detection
| Advantage | Explanation |
|---|---|
| Grid-based | Simple coordinate lookup — tile = map[y >> 3][x >> 3] for 8×8 tiles |
| Tile properties | A parallel tile_attrs[] table maps tile index → collision class |
| Efficient | No per-pixel checking; one shift and one indirect read per query |
| Consistent | Predictable behaviour, easy to debug |
Level design workflow
| Step | Process |
|---|---|
| 1 | Create tile graphics |
| 2 | Build tile set |
| 3 | Design levels in editor |
| 4 | Export map data |
| 5 | Runtime: draw from map |
Common tile types
| Type | Collision |
|---|---|
| Empty | Pass through |
| Solid | Block all |
| Platform | Block from above only |
| Hazard | Damage on contact |
| Collectible | Trigger collection |
Scrolling with tiles
| Challenge | Solution |
|---|---|
| New tiles needed at edge | Update the nametable column/row off-screen each frame |
| Seam visibility | Update during VBlank or under the status bar split |
| Large levels | Stream map data from ROM/disk in chunks |
The NES updates one column (30 tiles) per frame as the screen scrolls horizontally — small enough to fit in the VBlank window. The Mega Drive does the same with two BG planes; the C64 decodes the incoming column or row from block data at the screen edge as part of its software scroll.
See also
- Tile-Based Graphics — the rendering concept and platform support
- Character Graphics
- Hardware Scroll
- Multidirectional Scrolling
- PPU
- VIC-II