Leave without saving?

Your editor changes will be lost.

Mario-5 scatters ? blocks around the level. There’s one near the start, one above the hill, a row of three in the middle, and two on the mountain. Jump into one from below and it bumps up with a sound, then turns into a used block that can’t be hit again.

Special Tiles

Until now, every number in map.js was just scenery. Some tiles need behaviour of their own. A block animates and remembers whether it’s been hit, which a plain Tile can’t do. TileMap.initializeSpecialTiles() scans the map after building it and swaps those tiles for game objects:

if (tileId === Tile.BLOCK) {
this.blocks.push(new Block(this.services, pixelX, pixelY, spriteSheet));
// Leave a solid blank tile behind so the block still blocks movement.
this.setTileId(x, y, Tile.BLANK);
}

The map keeps a blocks array and draws every block after the tiles.

Hitting a Block

When the player is rising, checkVerticalCollisions() now also asks whether there’s a block in the row above their head:

checkTopBlockCollision(tileLeft, tileRight, tileTop) {
let blockHit = false;
for (let x = tileLeft; x <= tileRight; x++) {
const block = this.player.map.getBlockAt(
x * this.player.map.tileSize,
tileTop * this.player.map.tileSize,
);
if (block?.hit()) {
blockHit = true;
}
}
return blockHit;
}

getBlockAt() finds the block covering a pixel position, if there is one. Block.hit() only works once. The first call sets isHit and returns true, and every later call returns false.

The Bump

A bump is two short tweens back to back. The block moves up 2 pixels, then back down. async/await lets them read top to bottom, in the order they happen:

async bump() {
const timer = this.services.timer;
const halfDuration = Block.BUMP_DURATION / 2;
await timer.tweenAsync(
this,
{ bumpOffset: -Block.BUMP_HEIGHT },
halfDuration,
Easing.easeOutQuad,
);
await timer.tweenAsync(
this,
{ bumpOffset: 0 },
halfDuration,
Easing.easeInQuad,
);
this.currentSprite = this.usedSprite;
}

Notice the tween animates bumpOffset, not position. The block’s position never moves. render() simply draws it bumpOffset pixels higher:

this.currentSprite.render(
context,
this.position.x,
this.position.y + this.bumpOffset
);

hit() plays the bump sound, then starts the animation with void this.bump(). The void says we’re deliberately not waiting for the promise. The player’s update carries on immediately while the block animates.

📚 References

Powered by WebContainers
Files
Preparing Environment