TileMap¶
Generic structure for tile-based background rendering.
Description¶
TileMapGeneric<T> is a template structure for rendering tile-based backgrounds efficiently. It supports multiple bit-depths (1bpp, 2bpp, 4bpp) by using the appropriate sprite type for tiles.
Tilemaps are ideal for large backgrounds, levels, and static environments. They support viewport culling (only visible tiles are drawn) for optimal performance.
Namespace¶
namespace pixelroot32::graphics {
template<typename T>
struct TileMapGeneric {
// ...
};
using TileMap = TileMapGeneric<Sprite>;
using TileMap2bpp = TileMapGeneric<Sprite2bpp>;
using TileMap4bpp = TileMapGeneric<Sprite4bpp>;
}
Template Parameters¶
T¶
The sprite type used for tiles.
Supported types: - Sprite (1bpp) - Sprite2bpp (2bpp) - Sprite4bpp (4bpp)
Structure¶
uint8_t* indices¶
Array of tile indices mapping to tile sprites.
Type: uint8_t*
Access: Read-write
Notes: - Array size = width * height - Each value is an index into the tiles array - 0 = first tile, 1 = second tile, etc. - Should be stored in flash (const) for best performance
Example:
// 16x16 tilemap (256 tiles)
static const uint8_t LEVEL_INDICES[] = {
0, 0, 0, 0, 1, 1, 1, 1, // Row 0
0, 2, 2, 2, 2, 2, 2, 0, // Row 1
// ... more rows
};
uint8_t width¶
Width of the tilemap in tiles.
Type: uint8_t
Access: Read-write
Example:
uint8_t height¶
Height of the tilemap in tiles.
Type: uint8_t
Access: Read-write
Example:
const T* tiles¶
Array of tile sprites of type T.
Type: const T*
Access: Read-only
Notes: - Array of sprite pointers, one per unique tile - Indices reference this array - All tiles should be the same size - Should be stored in flash (const) for best performance
Example (1bpp):
static const Sprite TILE_SPRITES[] = {
EMPTY_TILE, // Index 0
WALL_TILE, // Index 1
FLOOR_TILE, // Index 2
// ... more tiles
};
Example (2bpp):
static const Sprite2bpp TILE_SPRITES_2BPP[] = {
TILE_GRASS, // Index 0
TILE_DIRT, // Index 1
// ... more tiles
};
uint8_t tileWidth¶
Width of each tile in pixels.
Type: uint8_t
Access: Read-write
Notes: - All tiles must have the same width - Common values: 8, 16 pixels - Should match sprite width
Example:
uint8_t tileHeight¶
Height of each tile in pixels.
Type: uint8_t
Access: Read-write
Notes: - All tiles must have the same height - Common values: 8, 16 pixels - Should match sprite height
Example:
uint16_t tileCount¶
Number of unique tiles in the tiles array.
Type: uint16_t
Access: Read-write
Notes: - Must match the size of the tiles array - Indices must be < tileCount
Example:
Creating Tilemaps¶
Step 1: Create Tile Sprites¶
// Empty tile (index 0)
static const uint16_t EMPTY_DATA[] = {
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000
};
// Wall tile (index 1)
static const uint16_t WALL_DATA[] = {
0b11111111,
0b10000001,
0b10000001,
0b10000001,
0b10000001,
0b10000001,
0b10000001,
0b11111111
};
// Floor tile (index 2)
static const uint16_t FLOOR_DATA[] = {
0b00000000,
0b01111110,
0b01111110,
0b01111110,
0b01111110,
0b01111110,
0b01111110,
0b00000000
};
static const Sprite TILE_SPRITES[] = {
{EMPTY_DATA, 8, 8}, // Index 0
{WALL_DATA, 8, 8}, // Index 1
{FLOOR_DATA, 8, 8} // Index 2
};
Step 2: Create Index Array¶
// 16x16 level (256 tiles)
// 0 = empty, 1 = wall, 2 = floor
static const uint8_t LEVEL_INDICES[] = {
// Row 0: Top wall
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
// Row 1: Walls on sides
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1,
// Row 2
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1,
// ... more rows
// Row 15: Bottom wall
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
};
Step 3: Create TileMap Structure¶
static const TileMap LEVEL_MAP = {
const_cast<uint8_t*>(LEVEL_INDICES), // indices (non-const for struct)
16, // width in tiles
16, // height in tiles
TILE_SPRITES, // tile sprites array
8, // tile width
8, // tile height
3 // tile count
};
Rendering Tilemaps¶
Use Renderer::drawTileMap():
void draw(pixelroot32::graphics::Renderer& renderer) override {
// Draw tilemap at origin (0, 0)
renderer.drawTileMap(LEVEL_MAP, 0, 0, Color::White);
// With camera offset
camera.apply(renderer);
renderer.drawTileMap(LEVEL_MAP, 0, 0, Color::White);
}
Viewport Culling¶
Tilemaps automatically cull tiles outside the viewport:
- Only visible tiles are drawn
- Very efficient for large levels
- Works with camera scrolling
Example:
// Large level (256x256 tiles)
// Only tiles visible on screen are drawn
camera.apply(renderer);
renderer.drawTileMap(LARGE_LEVEL_MAP, 0, 0, Color::White);
Collision Detection with Tilemaps¶
Check tile at world position:
bool isSolidTile(int worldX, int worldY, const TileMap& map) {
int tileX = worldX / map.tileWidth;
int tileY = worldY / map.tileHeight;
if (tileX < 0 || tileX >= map.width ||
tileY < 0 || tileY >= map.height) {
return true; // Outside map = solid
}
int index = tileY * map.width + tileX;
uint8_t tileIndex = map.indices[index];
// Check if tile is solid (e.g., wall = index 1)
return (tileIndex == 1);
}
Usage Example¶
#include "graphics/TileMap.h"
#include "graphics/Renderer.h"
class LevelScene : public pixelroot32::core::Scene {
private:
pixelroot32::graphics::TileMap levelMap;
pixelroot32::graphics::Camera2D camera;
public:
void init() override {
// Level map is already defined (see above)
// Create camera
auto& renderer = engine.getRenderer();
camera = pixelroot32::graphics::Camera2D(
renderer.getWidth(),
renderer.getHeight()
);
// Set level boundaries
int levelWidth = levelMap.width * levelMap.tileWidth;
int levelHeight = levelMap.height * levelMap.tileHeight;
camera.setBounds(0.0f, levelWidth - renderer.getWidth());
camera.setVerticalBounds(0.0f, levelHeight - renderer.getHeight());
}
void update(unsigned long deltaTime) override {
Scene::update(deltaTime);
// Camera follows player
if (player) {
camera.followTarget(player->x, player->y);
}
}
void draw(pixelroot32::graphics::Renderer& renderer) override {
// Apply camera
camera.apply(renderer);
// Draw tilemap (viewport culling automatic)
renderer.drawTileMap(levelMap, 0, 0, Color::White);
// Draw entities
Scene::draw(renderer);
// Reset for UI
renderer.setDisplayOffset(0, 0);
}
bool checkTileCollision(float x, float y) {
int tileX = static_cast<int>(x) / levelMap.tileWidth;
int tileY = static_cast<int>(y) / levelMap.tileHeight;
if (tileX < 0 || tileX >= levelMap.width ||
tileY < 0 || tileY >= levelMap.height) {
return true; // Outside = solid
}
int index = tileY * levelMap.width + tileX;
uint8_t tile = levelMap.indices[index];
return (tile == 1); // Wall tile
}
};
Performance Considerations¶
- Viewport culling: Only visible tiles are drawn (automatic)
- Tile reuse: Reuse tile sprites across the map
- Index storage: Compact
uint8_tindices (1 byte per tile) - Memory: Store indices and tiles in flash (const) for best performance
- Tile size: Smaller tiles = more tiles to draw, but more detail
ESP32 Considerations¶
- Memory: Store tilemap data in flash, not RAM
- Map size: Large maps use more flash memory
- Tile count: Limit unique tiles to save memory
- Culling: Viewport culling is essential for large levels
See Also¶
- Renderer - Rendering system that draws tilemaps
- Sprite - Sprite structure used for tiles
- Camera2D - Camera for scrolling tilemaps
- Manual - Tilemaps
- API Overview