Ladders
Ladders let the player climb vertically through the level using the up and down arrow keys. While climbing, gravity is disabled and the player snaps to the ladder’s centre column. This tutorial adds that system on top of the jumping mechanics from the previous tutorials.
Ladder Types
There are four common ladder variants, and the choice affects level design possibilities:
Type A — Wall Ladders: The ladder tile is inside a solid column. The player can climb but cannot move left/right. Used in Castlevania-style games with tight vertical shafts.
Type B — Walkable Ladders: The ladder tile is passable from the sides. The player can climb or walk through it. Common in Mega Man-style open stages.
Type C — Top-only Ladders: The ladder extends upward only, with no tile below. The player can ascend but not descend past the bottom rung.
Type D — Floating Ladders: The ladder ends in open air. Whether the player can stand on top is a design decision — consistency with other surfaces matters here.

Ladder Rules
The rules governing this tutorial’s implementation:
- The player climbs with the up/down arrow keys
- The player can climb up if there is a ladder tile at the current position or above
- The player can climb down if there is a ladder tile below the destination
- The player can move left/right off the ladder if no wall blocks the path
- The player cannot jump while in climbing mode
Tile Setup
// Tile type constants
const TILE_TYPES = {
EMPTY: 0,
SOLID: 1,
LADDER: 2,
LADDER_SOLID: 3 // Ladder you can also walk on
};
// Tile properties
const tileProperties = {
[TILE_TYPES.EMPTY]: {
solid: false,
climbable: false,
walkable: true
},
[TILE_TYPES.SOLID]: {
solid: true,
climbable: false,
walkable: false
},
[TILE_TYPES.LADDER]: {
solid: false,
climbable: true,
walkable: false // Can't walk through, only climb
},
[TILE_TYPES.LADDER_SOLID]: {
solid: false,
climbable: true,
walkable: true // Can walk AND climb
}
};
// Helper functions
function getTileProperties(tileType) {
return tileProperties[tileType] || tileProperties[TILE_TYPES.EMPTY];
}
function isClimbable(x, y) {
const tileType = getTileAt(x, y);
return getTileProperties(tileType).climbable;
}
function isSolid(x, y) {
const tileType = getTileAt(x, y);
return getTileProperties(tileType).solid;
}
The LADDER_SOLID type handles the case where a ladder sits on top of a floor tile — the player can both stand on and climb from that position.
Climbing Controls
function handleClimbingInput(player, keys) {
if (player.isClimbing) {
// Climbing mode - up/down movement
if (keys['ArrowUp'] && canClimbUp(player)) {
player.y -= player.climbSpeed;
centerPlayerOnLadder(player);
} else if (keys['ArrowDown'] && canClimbDown(player)) {
player.y += player.climbSpeed;
centerPlayerOnLadder(player);
} else if (keys['ArrowLeft'] || keys['ArrowRight']) {
// Try to exit ladder horizontally
tryExitLadder(player, keys['ArrowLeft'] ? -1 : 1);
}
} else {
// Normal mode - check for ladder entry
if ((keys['ArrowUp'] || keys['ArrowDown']) && canStartClimbing(player)) {
player.isClimbing = true;
player.velocityY = 0; // Stop falling
centerPlayerOnLadder(player);
}
}
}
function centerPlayerOnLadder(player) {
// Snap player to center of ladder for clean movement
const ladderCol = Math.floor((player.x + player.width/2) / TILE_SIZE);
const ladderCenterX = ladderCol * TILE_SIZE + TILE_SIZE/2;
player.x = ladderCenterX - player.width/2;
}
function tryExitLadder(player, direction) {
const newX = player.x + direction * player.speed;
// Check all four corners for wall collision
const corners = [
{x: newX, y: player.y}, // Top-left
{x: newX + player.width, y: player.y}, // Top-right
{x: newX, y: player.y + player.height}, // Bottom-left
{x: newX + player.width, y: player.y + player.height} // Bottom-right
];
// Only exit if no corners hit walls
const canExit = corners.every(corner => !isSolid(corner.x, corner.y));
if (canExit) {
player.x = newX;
player.isClimbing = false;
}
}
centerPlayerOnLadder snaps the player to the horizontal centre of the ladder column each frame, preventing drift. tryExitLadder checks all four corners before allowing a horizontal exit, preventing the player from stepping into a wall.
Climb Detection
// Check if player can start climbing at current position
function canStartClimbing(player) {
const centerX = player.x + player.width / 2;
const centerY = player.y + player.height / 2;
return isClimbable(centerX, centerY);
}
// Check if player can climb upward
function canClimbUp(player) {
const centerX = player.x + player.width / 2;
// Check current position and above
const currentClimbable = isClimbable(centerX, player.y + player.height/2);
const aboveClimbable = isClimbable(centerX, player.y - player.climbSpeed);
return currentClimbable && (aboveClimbable || isClimbable(centerX, player.y));
}
// Check if player can climb downward
function canClimbDown(player) {
const centerX = player.x + player.width / 2;
const futureY = player.y + player.height + player.climbSpeed;
// Must have ladder where we're going
return isClimbable(centerX, futureY);
}
// Main climbing physics update
function updateClimbing(player) {
if (player.isClimbing) {
// Disable gravity while climbing
player.velocityY = 0;
player.velocityX = 0;
// Check if still on a ladder
if (!canStartClimbing(player)) {
player.isClimbing = false;
// Resume normal physics
}
}
}
Visual Feedback
function updatePlayerVisuals(player) {
// Change appearance based on state
if (player.isClimbing) {
player.sprite.tint = 0xFFFF99; // Slight yellow tint
// Could add climbing animation here
} else {
player.sprite.tint = 0xFFFFFF; // Normal color
}
// Update sprite position
player.sprite.x = player.x;
player.sprite.y = player.y;
}
The sequence when the player enters a ladder: canStartClimbing() detects the ladder tile at the player’s centre, up/down input sets isClimbing = true, velocityY is zeroed (disabling gravity), and centerPlayerOnLadder aligns the player to the column. On exit, a left/right key with no wall collision sets isClimbing = false and normal physics resume.
Next: Moving Tiles