Hit the Wall

Time to make your game world SOLID! 💥 Collision detection is what separates real games from slideshows - it’s the magic that makes walls feel solid, platforms feel sturdy, and your hero feel like they truly exist in the world. You’re about to master one of the most crucial skills in game development!

Try it! Use arrow keys to move around. Notice how the hero can’t pass through walls and turns slightly red when hitting them! 🚧

COLLISION DETECTION: THE FOUNDATION 🧱

Step 1: Simple Tile-Based Collision

Let’s start with the simplest approach - checking if the tile we want to move to is walkable:

// Our collision detection function - clean and simple!
function canMoveTo(tileX, tileY, gameMap) {
    // First, check if we're trying to go outside the map
    if (tileX < 0 || tileX >= gameMap[0].length || 
        tileY < 0 || tileY >= gameMap.length) {
        return false;  // Can't go outside the world!
    }
    
    // Check if the tile is walkable
    // 0 = floor (walkable), 1 = wall (solid)
    return gameMap[tileY][tileX] === 0;
}

// Using it in movement
function tryMoveTo(hero, newTileX, newTileY, gameMap) {
    if (canMoveTo(newTileX, newTileY, gameMap)) {
        // Safe to move!
        hero.tileX = newTileX;
        hero.tileY = newTileY;
        updateHeroPosition(hero);
        return true;
    } else {
        // Blocked! Stay where we are
        return false;
    }
}

Why this approach rocks:

ADVANCED: BOUNDING BOX COLLISION 📦

For pixel-perfect movement and larger characters:

Sometimes you want smooth pixel-based movement instead of tile-by-tile jumping, or your character is larger than one tile. Here’s how to handle that:

// Check collision using character's bounding box
function checkBoundingBoxCollision(x, y, width, height, gameMap, tileSize) {
    // Calculate which tiles the character's corners touch
    const leftTile = Math.floor((x - width/2) / tileSize);
    const rightTile = Math.floor((x + width/2 - 1) / tileSize);
    const topTile = Math.floor((y - height/2) / tileSize);
    const bottomTile = Math.floor((y + height/2 - 1) / tileSize);
    
    // Check all tiles the character overlaps
    for (let tileY = topTile; tileY <= bottomTile; tileY++) {
        for (let tileX = leftTile; tileX <= rightTile; tileX++) {
            // Check bounds
            if (tileX < 0 || tileX >= gameMap[0].length || 
                tileY < 0 || tileY >= gameMap.length) {
                return true; // Hit world boundary
            }
            
            // Check if any overlapping tile is solid
            if (gameMap[tileY][tileX] === 1) {
                return true; // Hit a wall!
            }
        }
    }
    
    return false; // No collision!
}

// Smooth movement with collision
function updateSmoothMovement(hero, keys, gameMap, tileSize) {
    let newX = hero.x;
    let newY = hero.y;
    
    if (keys.ArrowLeft) newX -= hero.speed;
    if (keys.ArrowRight) newX += hero.speed;
    if (keys.ArrowUp) newY -= hero.speed;
    if (keys.ArrowDown) newY += hero.speed;
    
    // Check X movement first
    if (newX !== hero.x && !checkBoundingBoxCollision(newX, hero.y, hero.width, hero.height, gameMap, tileSize)) {
        hero.x = newX;
    }
    
    // Check Y movement separately  
    if (newY !== hero.y && !checkBoundingBoxCollision(hero.x, newY, hero.width, hero.height, gameMap, tileSize)) {
        hero.y = newY;
    }
    
    // Update sprite position
    hero.sprite.x = hero.x;
    hero.sprite.y = hero.y;
}

Why separate X and Y checks? This allows “wall sliding” - if you hit a wall while moving diagonally, you can still slide along it. Just like in Mario or Zelda!

COMPLETE COLLISION SYSTEM 🎮

Put it all together:

// Complete movement system with collision
class CollisionSystem {
    constructor(gameMap, tileSize) {
        this.gameMap = gameMap;
        this.tileSize = tileSize;
    }
    
    // Check if a tile coordinate is walkable
    isTileWalkable(tileX, tileY) {
        if (tileX < 0 || tileX >= this.gameMap[0].length || 
            tileY < 0 || tileY >= this.gameMap.length) {
            return false;
        }
        return this.gameMap[tileY][tileX] === 0;
    }
    
    // Check pixel-based collision
    checkCollision(x, y, width, height) {
        const leftTile = Math.floor((x - width/2) / this.tileSize);
        const rightTile = Math.floor((x + width/2 - 1) / this.tileSize);
        const topTile = Math.floor((y - height/2) / this.tileSize);
        const bottomTile = Math.floor((y + height/2 - 1) / this.tileSize);
        
        for (let tileY = topTile; tileY <= bottomTile; tileY++) {
            for (let tileX = leftTile; tileX <= rightTile; tileX++) {
                if (!this.isTileWalkable(tileX, tileY)) {
                    return true; // Collision detected!
                }
            }
        }
        return false;
    }
    
    // Try to move an object
    moveObject(obj, deltaX, deltaY) {
        const newX = obj.x + deltaX;
        const newY = obj.y + deltaY;
        
        // Check X movement
        if (deltaX !== 0) {
            if (!this.checkCollision(newX, obj.y, obj.width, obj.height)) {
                obj.x = newX;
            }
        }
        
        // Check Y movement
        if (deltaY !== 0) {
            if (!this.checkCollision(obj.x, newY, obj.width, obj.height)) {
                obj.y = newY;
            }
        }
    }
}

// Usage
const collisionSystem = new CollisionSystem(gameMap, TILE_SIZE);

function updateMovement(hero, keys) {
    let deltaX = 0;
    let deltaY = 0;
    
    if (keys.ArrowLeft) deltaX = -hero.speed;
    if (keys.ArrowRight) deltaX = hero.speed;
    if (keys.ArrowUp) deltaY = -hero.speed;
    if (keys.ArrowDown) deltaY = hero.speed;
    
    collisionSystem.moveObject(hero, deltaX, deltaY);
    
    // Update sprite
    hero.sprite.x = hero.x;
    hero.sprite.y = hero.y;
}

PRO TIPS & ENHANCEMENTS 🚀

Visual Feedback:

// Show collision feedback
function updateCollisionFeedback(hero, hitWall) {
    if (hitWall) {
        hero.sprite.tint = 0xff8888;  // Light red
        // Optional: Add screen shake
        hero.sprite.x += Math.random() * 2 - 1;
    } else {
        hero.sprite.tint = 0xffffff;  // White
    }
}

Sound Effects:

// Add audio feedback (using Web Audio API)
function playCollisionSound() {
    // Simple beep when hitting walls
    const audioContext = new AudioContext();
    const oscillator = audioContext.createOscillator();
    const gainNode = audioContext.createGain();
    
    oscillator.connect(gainNode);
    gainNode.connect(audioContext.destination);
    
    oscillator.frequency.value = 200;
    gainNode.gain.value = 0.1;
    
    oscillator.start();
    oscillator.stop(audioContext.currentTime + 0.1);
}

Different Tile Types:

const TileTypes = {
    FLOOR: 0,
    WALL: 1,
    WATER: 2,    // Slows movement
    SPIKES: 3,   // Damages player
    ICE: 4       // Slippery movement
};

function getTileEffect(tileType) {
    switch(tileType) {
        case TileTypes.WATER: return { walkable: true, speedMultiplier: 0.5 };
        case TileTypes.SPIKES: return { walkable: true, damage: 10 };
        case TileTypes.ICE: return { walkable: true, friction: 0.1 };
        default: return { walkable: tileType === TileTypes.FLOOR };
    }
}

🏆 COLLISION MASTERY ACHIEVED!

You’ve just mastered one of the most important systems in game development! Your games now have solid, responsive collision detection that makes the world feel real and interactive.

What you’ve conquered:

Ready to make your world more interactive? Next up: doors, switches, and interactive objects! Next: Open the Door