Hit the Wall
Collision detection prevents the hero from entering solid tiles. Before applying a movement, check whether the destination tile is walkable — if it isn’t, cancel the move.
The hero turns light red when a move is blocked and white when it moves freely.
Tile-based collision
The canMoveTo function checks whether a tile coordinate is both within bounds and walkable:
function canMoveTo(tileX, tileY, gameMap) {
// Out of bounds — treat as solid
if (tileX < 0 || tileX >= gameMap[0].length ||
tileY < 0 || tileY >= gameMap.length) {
return false;
}
// 0 = floor (walkable), 1 = wall (solid)
return gameMap[tileY][tileX] === 0;
}
The movement function calls this before applying any change to tileX/tileY:
function tryMoveTo(hero, newTileX, newTileY, gameMap) {
if (canMoveTo(newTileX, newTileY, gameMap)) {
hero.tileX = newTileX;
hero.tileY = newTileY;
updateHeroPosition(hero);
return true;
}
return false; // Blocked — position unchanged
}
One array lookup per attempted move. For tile-based movement this is all the collision detection you need.
Bounding box collision
For pixel-based movement — where the hero moves a few pixels per frame rather than one tile at a time — tile-based collision needs to account for the hero’s size. A 12px hero can partially overlap a tile before its centre crosses the boundary.
Check all four corners of the hero’s bounding box:
function checkBoundingBoxCollision(x, y, width, height, gameMap, tileSize) {
const leftTile = Math.floor(x / tileSize);
const rightTile = Math.floor((x + width - 1) / tileSize);
const topTile = Math.floor(y / tileSize);
const bottomTile = Math.floor((y + height - 1) / tileSize);
for (let tileY = topTile; tileY <= bottomTile; tileY++) {
for (let tileX = leftTile; tileX <= rightTile; tileX++) {
if (tileX < 0 || tileX >= gameMap[0].length ||
tileY < 0 || tileY >= gameMap.length) {
return true; // Map boundary
}
if (gameMap[tileY][tileX] === 1) return true;
}
}
return false;
}
Apply X and Y movement separately so the hero can slide along walls when moving diagonally:
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 and Y independently — allows wall sliding
if (!checkBoundingBoxCollision(newX, hero.y, hero.width, hero.height, gameMap, tileSize)) {
hero.x = newX;
}
if (!checkBoundingBoxCollision(hero.x, newY, hero.width, hero.height, gameMap, tileSize)) {
hero.y = newY;
}
hero.sprite.x = hero.x;
hero.sprite.y = hero.y;
}
Checking X and Y separately means a diagonal collision into a corner doesn’t stop both axes — the hero slides along the wall face it’s parallel to.
Enhancements
Different tile behaviours:
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 };
}
}
Wall-hit feedback:
function updateCollisionFeedback(hero, hitWall) {
hero.sprite.tint = hitWall ? 0xff8888 : 0xffffff;
}
What you built:
- A
canMoveTofunction that checks bounds and tile walkability - Separate X and Y collision checks for smooth wall-sliding movement
- The pattern for per-tile effects (damage, friction, speed change)