TCJSGame TileMap Class: Complete Reference Guide


TileMap Systems



Introduction to the TileMap Class

TCJSGame’s TileMap Class provides a powerful grid-based level design system for creating complex game worlds. It allows you to build levels using tile-based layouts with collision detection, layer management, and dynamic tile manipulation.



🏗️ Basic TileMap Setup



Creating Tile Definitions

const tiles = [
    new Component(0, 0, "green", 0, 0, "rect"),  // Index 1 - Grass
    new Component(0, 0, "gray", 0, 0, "rect"),   // Index 2 - Stone wall
    new Component(0, 0, "blue", 0, 0, "rect"),   // Index 3 - Water
    new Component(0, 0, "brown", 0, 0, "rect"),  // Index 4 - Dirt
    new Component(0, 0, "yellow", 0, 0, "rect")  // Index 5 - Sand
];

// Image-based tiles
const imageTiles = [
    new Component(0, 0, "tiles/grass.png", 0, 0, "image"),
    new Component(0, 0, "tiles/stone.png", 0, 0, "image"),
    new Component(0, 0, "tiles/water.png", 0, 0, "image")
];
Enter fullscreen mode

Exit fullscreen mode



Creating Map Layouts

// 2D array representing your level (rows x columns)
const mapLayout = [
    [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
    [2, 1, 1, 1, 1, 1, 1, 1, 1, 2],
    [2, 1, 1, 3, 3, 3, 1, 1, 1, 2],
    [2, 1, 1, 3, 3, 3, 1, 1, 1, 2],
    [2, 1, 1, 1, 1, 1, 1, 1, 1, 2],
    [2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
];

// Larger, more complex map
const largeMap = [
    [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
    [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2],
    [2, 1, 4, 4, 1, 1, 5, 5, 5, 1, 1, 4, 1, 2],
    [2, 1, 4, 1, 1, 5, 5, 5, 5, 5, 1, 1, 1, 2],
    [2, 1, 1, 1, 5, 5, 3, 3, 3, 5, 5, 1, 1, 2],
    [2, 1, 1, 5, 5, 3, 3, 3, 3, 3, 5, 1, 1, 2],
    [2, 1, 1, 5, 3, 3, 3, 3, 3, 3, 5, 1, 1, 2],
    [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
];
Enter fullscreen mode

Exit fullscreen mode



Initializing the TileMap

// Setup display and assign tilemap data
const display = new Display();
display.start(800, 600);

// Assign tiles and map to display
display.tile = tiles;
display.map = mapLayout;

// Create and show the tilemap
display.tileMap();              // Creates tileFace instance
display.tileFace.show();        // Renders the tilemap

// Access the tilemap directly
const tilemap = display.tileFace;
Enter fullscreen mode

Exit fullscreen mode



🎯 TileMap Properties and Methods



Core Properties

class TileMap {
    constructor(render, map, tile, width, height, scene = 0) {
        this.map = map;                 // 2D map array
        this.tile = tile;               // Tile definitions
        this.tile.unshift(0);           // Add empty slot at index 0
        this.tileHeight = height / map.length;
        this.tileWidth = width / map[0].length;
        this.tileList = [];             // Generated tile objects
        this.scene = scene;
    }
}
Enter fullscreen mode

Exit fullscreen mode



Basic TileMap Operations

// Get all tiles of a specific type
const grassTiles = tilemap.tiles(1);        // All grass tiles (id=1)
const wallTiles = tilemap.tiles(2);         // All wall tiles (id=2)

// Get a specific tile by grid coordinates
const tileAtPosition = tilemap.rTile(3, 2); // Tile at column 3, row 2

// Check if tile exists at position
if (tileAtPosition) {
    console.log("Tile found:", tileAtPosition.tid, "at", tileAtPosition.tx, tileAtPosition.ty);
}

// Get all tiles (returns entire tileList)
const allTiles = tilemap.tiles();
Enter fullscreen mode

Exit fullscreen mode



🚀 Collision Detection Systems



Basic Collision Checking

// Check collision with any tile
if (tilemap.crashWith(player)) {
    // Player collided with a solid tile
    player.hitBottom();
}

// Check collision with specific tile types
if (tilemap.crashWith(player, 2)) {     // Collision with walls (id=2)
    player.speedX = 0;
    player.speedY = 0;
}

if (tilemap.crashWith(player, 3)) {     // Collision with water (id=3)
    player.speedX *= 0.5;  // Slow movement in water
    player.speedY *= 0.5;
}
Enter fullscreen mode

Exit fullscreen mode



Advanced Collision System

class AdvancedCollision {
    constructor(tilemap) {
        this.tilemap = tilemap;
    }

    checkAllCollisions(player) {
        // Check different tile types for different effects
        if (this.tilemap.crashWith(player, 2)) { // Walls
            this.handleWallCollision(player);
        }

        if (this.tilemap.crashWith(player, 3)) { // Water
            this.handleWaterCollision(player);
        }

        if (this.tilemap.crashWith(player, 5)) { // Lava
            this.handleLavaCollision(player);
        }

        // Check spikes (id=6) only from top
        if (this.checkSpikeCollision(player)) {
            this.handleSpikeCollision(player);
        }
    }

    handleWallCollision(player) {
        // Stop movement when hitting walls
        player.speedX = 0;
        player.speedY = 0;
    }

    handleWaterCollision(player) {
        // Apply water physics
        player.speedX *= 0.7;
        player.speedY *= 0.7;
    }

    handleLavaCollision(player) {
        // Damage player in lava
        player.health -= 1;
        // Add visual effect
        player.color = "red";
        setTimeout(() => player.color = "blue", 200);
    }

    checkSpikeCollision(player) {
        // Only spikes hurt when touching from top
        const spikeTiles = this.tilemap.tiles(6);
        for (let spike of spikeTiles) {
            if (player.crashWith(spike) && player.y + player.height < spike.y + 10) {
                return true;
            }
        }
        return false;
    }
}

// Usage
const collisionSystem = new AdvancedCollision(display.tileFace);

function update() {
    collisionSystem.checkAllCollisions(player);
}
Enter fullscreen mode

Exit fullscreen mode



🔧 Dynamic Tile Manipulation



Adding and Removing Tiles

// Add a tile at specific grid position
tilemap.add(1, 3, 2);  // Add grass (id=1) at column 3, row 2

// Remove a tile from grid position
tilemap.remove(4, 3);  // Remove tile at column 4, row 3

// Example: Destructible environment
function destroyTile(x, y) {
    const tile = tilemap.rTile(x, y);
    if (tile && tile.tid === 2) { // Only destroy stone walls
        tilemap.remove(x, y);

        // Add destruction effect
        createExplosionEffect(tile.x + tile.width/2, tile.y + tile.height/2);
    }
}

// Example: Building system
function placeTile(tileId, x, y) {
    if (tilemap.rTile(x, y) === null) { // Only place on empty tiles
        tilemap.add(tileId, x, y);
    }
}
Enter fullscreen mode

Exit fullscreen mode



Tile Modification at Runtime

// Change tile properties dynamically
function changeTileColor(tileX, tileY, newColor) {
    const tile = tilemap.rTile(tileX, tileY);
    if (tile) {
        tile.color = newColor;
    }
}

// Animate tiles
function animateWaterTiles() {
    const waterTiles = tilemap.tiles(3); // All water tiles
    waterTiles.forEach((tile, index) => {
        // Simple wave animation
        tile.y += Math.sin(display.frameNo * 0.1 + index * 0.5) * 0.5;
    });
}

// Tile state management
class SmartTile {
    constructor(tile, properties = {}) {
        this.tile = tile;
        this.properties = properties;
        this.state = 'normal';
    }

    activate() {
        this.state = 'active';
        this.tile.color = 'yellow';
    }

    deactivate() {
        this.state = 'normal';
        this.tile.color = this.properties.originalColor;
    }
}
Enter fullscreen mode

Exit fullscreen mode



🎮 Practical Game Examples



Platformer Level Design

class PlatformerLevel {
    constructor() {
        this.display = new Display();
        this.display.start(800, 600);

        this.setupTiles();
        this.setupLevel();
        this.setupPlayer();
    }

    setupTiles() {
        this.tiles = [
            new Component(0, 0, "#7CFC00", 0, 0, "rect"), // 1 - Grass
            new Component(0, 0, "#8B4513", 0, 0, "rect"), // 2 - Dirt
            new Component(0, 0, "#C0C0C0", 0, 0, "rect"), // 3 - Stone
            new Component(0, 0, "#87CEEB", 0, 0, "rect")  // 4 - Water
        ];
    }

    setupLevel() {
        this.levelMap = [
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 3, 3, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
            [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
        ];

        this.display.tile = this.tiles;
        this.display.map = this.levelMap;
        this.display.tileMap();
        this.display.tileFace.show();
    }

    setupPlayer() {
        this.player = new Component(30, 30, "red", 100, 100, "rect");
        this.player.physics = true;
        this.player.gravity = 0.5;
        this.player.bounce = 0.2;
        this.display.add(this.player);

        // Setup camera to follow player
        this.display.camera.worldWidth = this.levelMap[0].length * this.display.tileFace.tileWidth;
        this.display.camera.worldHeight = this.levelMap.length * this.display.tileFace.tileHeight;
        this.display.camera.follow(this.player, true);
    }

    update() {
        // Player controls
        if (this.display.keys[37]) this.player.speedX = -5; // Left
        if (this.display.keys[39]) this.player.speedX = 5;  // Right
        if (this.display.keys[38] && this.player.gravitySpeed === 0) {
            this.player.speedY = -12; // Jump
        }

        // Collision detection
        if (this.display.tileFace.crashWith(this.player, 1) || 
            this.display.tileFace.crashWith(this.player, 2) ||
            this.display.tileFace.crashWith(this.player, 3)) {
            this.player.hitBottom();
        }

        // Water physics
        if (this.display.tileFace.crashWith(this.player, 4)) {
            this.player.speedX *= 0.7;
            this.player.speedY *= 0.7;
        }
    }
}
Enter fullscreen mode

Exit fullscreen mode



RPG World Map

class RPGWorld {
    constructor() {
        this.display = new Display();
        this.display.start(800, 600);

        this.setupWorldTiles();
        this.createWorldMap();
        this.setupPlayer();
    }

    setupWorldTiles() {
        this.tiles = [
            new Component(0, 0, "#228B22", 0, 0, "rect"), // 1 - Forest
            new Component(0, 0, "#DEB887", 0, 0, "rect"), // 2 - Desert
            new Component(0, 0, "#0000FF", 0, 0, "rect"), // 3 - Ocean
            new Component(0, 0, "#808080", 0, 0, "rect"), // 4 - Mountains
            new Component(0, 0, "#90EE90", 0, 0, "rect"), // 5 - Plains
            new Component(0, 0, "#FF0000", 0, 0, "rect")  // 6 - Town
        ];
    }

    createWorldMap() {
        this.worldMap = [
            [3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
            [3, 4, 4, 1, 1, 1, 1, 4, 4, 3],
            [3, 4, 1, 1, 5, 5, 1, 1, 4, 3],
            [3, 1, 1, 5, 5, 5, 5, 1, 1, 3],
            [3, 1, 5, 5, 6, 5, 5, 5, 1, 3],
            [3, 1, 5, 5, 5, 5, 5, 5, 1, 3],
            [3, 1, 1, 5, 5, 5, 5, 1, 1, 3],
            [3, 4, 1, 1, 2, 2, 1, 1, 4, 3],
            [3, 4, 4, 1, 2, 2, 1, 4, 4, 3],
            [3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
        ];

        this.display.tile = this.tiles;
        this.display.map = this.worldMap;
        this.display.tileMap();
        this.display.tileFace.show();
    }

    setupPlayer() {
        this.player = new Component(20, 20, "red", 200, 200, "rect");
        this.display.add(this.player);

        // RPG-style movement (grid-based)
        this.moveSpeed = 3;
        this.isMoving = false;
    }

    movePlayer(direction) {
        if (this.isMoving) return;

        this.isMoving = true;
        let targetX = this.player.x;
        let targetY = this.player.y;

        switch(direction) {
            case 'up': targetY -= this.display.tileFace.tileHeight; break;
            case 'down': targetY += this.display.tileFace.tileHeight; break;
            case 'left': targetX -= this.display.tileFace.tileWidth; break;
            case 'right': targetX += this.display.tileFace.tileWidth; break;
        }

        // Check if movement is valid
        if (this.isValidMove(targetX, targetY)) {
            move.glideTo(this.player, 0.2, targetX, targetY);
        }

        setTimeout(() => { this.isMoving = false; }, 200);
    }

    isValidMove(x, y) {
        // Convert world coordinates to grid coordinates
        const gridX = Math.floor(x / this.display.tileFace.tileWidth);
        const gridY = Math.floor(y / this.display.tileFace.tileHeight);

        // Check if within bounds and not ocean/mountains
        if (gridX < 0 || gridY < 0 || 
            gridX >= this.worldMap[0].length || 
            gridY >= this.worldMap.length) {
            return false;
        }

        const tileId = this.worldMap[gridY][gridX];
        return tileId !== 3 && tileId !== 4; // Can't move into ocean or mountains
    }

    update() {
        // RPG controls
        if (!this.isMoving) {
            if (this.display.keys[38]) this.movePlayer('up');     // Up arrow
            if (this.display.keys[40]) this.movePlayer('down');   // Down arrow
            if (this.display.keys[37]) this.movePlayer('left');   // Left arrow
            if (this.display.keys[39]) this.movePlayer('right');  // Right arrow
        }

        // Check for town entry
        this.checkTownEntry();
    }

    checkTownEntry() {
        const gridX = Math.floor(this.player.x / this.display.tileFace.tileWidth);
        const gridY = Math.floor(this.player.y / this.display.tileFace.tileHeight);

        if (this.worldMap[gridY][gridX] === 6) { // Town tile
            console.log("Entering town!");
            // Trigger town scene
        }
    }
}
Enter fullscreen mode

Exit fullscreen mode



🔧 Advanced TileMap Techniques



Multi-Layer TileMaps

class MultiLayerTileMap {
    constructor() {
        this.layers = [];
        this.activeLayer = 0;
    }

    addLayer(tiles, map, zIndex = 0) {
        this.layers.push({
            tiles: tiles,
            map: map,
            zIndex: zIndex,
            visible: true
        });

        // Sort layers by z-index
        this.layers.sort((a, b) => a.zIndex - b.zIndex);
    }

    show() {
        this.layers.forEach(layer => {
            if (layer.visible) {
                display.tile = layer.tiles;
                display.map = layer.map;
                display.tileMap();
                // Note: This would need custom rendering for multiple layers
            }
        });
    }

    setLayerVisibility(layerIndex, visible) {
        if (this.layers[layerIndex]) {
            this.layers[layerIndex].visible = visible;
        }
    }
}

// Usage
const multiMap = new MultiLayerTileMap();

// Background layer (ground)
multiMap.addLayer(
    [new Component(0, 0, "green", 0, 0, "rect")],
    [[1,1,1],[1,1,1],[1,1,1]],
    0
);

// Foreground layer (objects)
multiMap.addLayer(
    [new Component(0, 0, "brown", 0, 0, "rect")],
    [[0,1,0],[0,0,0],[0,0,1]],
    1
);
Enter fullscreen mode

Exit fullscreen mode



Procedural Map Generation

class ProceduralMap {
    constructor(width, height) {
        this.width = width;
        this.height = height;
        this.map = this.generateEmptyMap();
    }

    generateEmptyMap() {
        return Array(this.height).fill().map(() => Array(this.width).fill(0));
    }

    generateCave() {
        // Simple cellular automata for cave generation
        for (let y = 0; y < this.height; y++) {
            for (let x = 0; x < this.width; x++) {
                // 45% chance of wall
                this.map[y][x] = Math.random() < 0.45 ? 2 : 1;
            }
        }

        // Smooth the cave
        this.smoothCave(4);
        return this.map;
    }

    smoothCave(iterations) {
        for (let i = 0; i < iterations; i++) {
            const newMap = this.generateEmptyMap();

            for (let y = 0; y < this.height; y++) {
                for (let x = 0; x < this.width; x++) {
                    const wallCount = this.countAdjacentWalls(x, y);

                    if (wallCount > 4) {
                        newMap[y][x] = 2; // Wall
                    } else if (wallCount < 4) {
                        newMap[y][x] = 1; // Floor
                    } else {
                        newMap[y][x] = this.map[y][x]; // Unchanged
                    }
                }
            }

            this.map = newMap;
        }
    }

    countAdjacentWalls(x, y) {
        let count = 0;
        for (let dy = -1; dy <= 1; dy++) {
            for (let dx = -1; dx <= 1; dx++) {
                const nx = x + dx;
                const ny = y + dy;

                if (nx >= 0 && nx < this.width && ny >= 0 && ny < this.height) {
                    if (this.map[ny][nx] === 2) count++;
                } else {
                    count++; // Count edges as walls
                }
            }
        }
        return count;
    }
}

// Usage
const proceduralMap = new ProceduralMap(20, 15);
const caveLayout = proceduralMap.generateCave();

display.tile = [
    new Component(0, 0, "gray", 0, 0, "rect"),  // Floor
    new Component(0, 0, "darkgray", 0, 0, "rect") // Wall
];
display.map = caveLayout;
display.tileMap();
display.tileFace.show();
Enter fullscreen mode

Exit fullscreen mode



⚡ Performance Optimization



Efficient Tile Rendering

class OptimizedTileMap {
    constructor() {
        this.visibleTiles = [];
        this.lastCameraX = 0;
        this.lastCameraY = 0;
        this.updateThreshold = 50; // Update when camera moves 50 pixels
    }

    updateVisibleTiles() {
        const camera = display.camera;

        // Only update if camera moved significantly
        if (Math.abs(camera.x - this.lastCameraX) < this.updateThreshold &&
            Math.abs(camera.y - this.lastCameraY) < this.updateThreshold) {
            return;
        }

        this.lastCameraX = camera.x;
        this.lastCameraY = camera.y;

        this.visibleTiles = display.tileFace.tileList.filter(tile => {
            return this.isTileVisible(tile);
        });
    }

    isTileVisible(tile) {
        return tile.x + tile.width >= display.camera.x &&
               tile.x <= display.camera.x + display.canvas.width &&
               tile.y + tile.height >= display.camera.y &&
               tile.y <= display.camera.y + display.canvas.height;
    }

    render() {
        this.visibleTiles.forEach(tile => {
            tile.update(display.context);
        });
    }
}

// Usage
const optimizedRenderer = new OptimizedTileMap();

function update() {
    optimizedRenderer.updateVisibleTiles();
    optimizedRenderer.render();
}
Enter fullscreen mode

Exit fullscreen mode



🐛 Common Issues and Solutions



TileMap Not Appearing

// Common setup issues and solutions

// 1. Forgot to call show()
display.tileMap();        // Creates tileFace
display.tileFace.show();  // Actually renders tiles

// 2. Tiles array doesn't have null at index 0
const correctTiles = [
    new Component(0, 0, "green", 0, 0, "rect")
];

// 3. Map dimensions don't match tile dimensions
function validateMapSize(tilemap) {
    const expectedWidth = tilemap.map[0].length * tilemap.tileWidth;
    const expectedHeight = tilemap.map.length * tilemap.tileHeight;

    console.log(`Map size: ${expectedWidth}x${expectedHeight}`);
}
Enter fullscreen mode

Exit fullscreen mode



Performance Issues with Large Maps

// Solutions for large tilemaps

// 1. Use chunk-based loading
class ChunkedTileMap {
    constructor(chunkSize = 10) {
        this.chunkSize = chunkSize;
        this.loadedChunks = new Set();
    }

    loadChunk(chunkX, chunkY) {
        const chunkId = `${chunkX},${chunkY}`;
        if (!this.loadedChunks.has(chunkId)) {
            // Load chunk data
            this.loadedChunks.add(chunkId);
        }
    }

    unloadDistantChunks(playerX, playerY) {
        const playerChunkX = Math.floor(playerX / (this.chunkSize * display.tileFace.tileWidth));
        const playerChunkY = Math.floor(playerY / (this.chunkSize * display.tileFace.tileHeight));

        this.loadedChunks.forEach(chunkId => {
            const [chunkX, chunkY] = chunkId.split(',').map(Number);
            const distance = Math.abs(chunkX - playerChunkX) + Math.abs(chunkY - playerChunkY);

            if (distance > 2) { // Unload chunks more than 2 away
                this.loadedChunks.delete(chunkId);
            }
        });
    }
}
Enter fullscreen mode

Exit fullscreen mode



📚 Conclusion

TCJSGame’s TileMap Class provides:

  • Grid-based level design with visual tile placement
  • Efficient collision detection for game physics
  • Dynamic tile manipulation at runtime
  • Large world support with camera-relative rendering
  • Flexible tile definitions for varied game aesthetics

Key features for different game types:

  • Platformers: Solid ground and platform tiles
  • RPGs: Terrain-based movement restrictions
  • Puzzle games: Interactive and movable tiles
  • Strategy games: Grid-based unit placement

The TileMap system integrates seamlessly with TCJSGame’s other components, allowing you to create complex, interactive game worlds with minimal code. Combine tilemaps with the camera system for scrolling levels, with physics for collision detection, and with state utilities for interactive environments!



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *