Bringing it Together
Each tutorial in this series introduced one mechanic in isolation. Here they run together: gravity, cloud platforms, moving tiles, enemies with edge detection, projectiles, and a scrolling camera. The goal is not a polished game — it’s a working skeleton that demonstrates how the systems compose without fighting each other.
Controls: arrow keys to move, Space or Up to jump, Shift to shoot.
What Changed When the Systems Merged
Running these mechanics individually is straightforward. Running them together exposed ordering dependencies and shared state problems that don’t exist in isolation.
Update Order
The game loop above runs in this sequence:
updatePlatforms → updatePlayer → updateEnemies → updateBullets → updateCamera
Platforms run first because they carry the player. If the player updated before the platform moved, they’d trail behind by one frame — visible as jitter on vertical platforms. Enemy and bullet updates run after the player so that their positions are already final when collision checks happen.
The camera always runs last. Applying the camera offset before positions are final would read stale data.
player.lastY Must Be Set Before Anything Moves
The moving platform landing check uses player.lastY to determine whether the player was above the tile top on the previous frame. If platforms run first and the player moves before lastY is captured, the landing check will use the wrong reference frame. The fix is one line at the very top of gameLoop:
function gameLoop() {
player.lastY = player.y; // ← must be first
updatePlatforms();
// ...
}
Unified Tile Type Constants
Each standalone tutorial defined its own EMPTY = 0, SOLID = 1, CLOUD = 2 constants. Once combined, two tutorials using 2 for different tile types would conflict. The synthesis uses a single object:
const TILE = { EMPTY: 0, SOLID: 1, CLOUD: 2 };
All tile checks go through the same tileAt helper. Adding a new type (ladders, for instance) means adding one entry here and updating only the functions that need to respond to it.
Three Ground States, Not One
In the individual tutorials, player.onGround was a boolean. In the combined game, the player can be on a static tile, a cloud tile, or a moving platform — and these require different behaviour:
- Static tile: normal physics,
onGround = true - Cloud tile: same as static, but only from above; no ceiling or side collision
- Moving platform: player carried by the platform object; gravity skipped; lateral movement clamped to platform bounds
This is handled with two flags: player.onGround (true when on static or cloud) and player.onPlatform (holds a reference to the platform object, or null). Physics checks both before applying gravity or accepting a jump:
if (!player.onPlatform) {
player.velocityY += GRAV;
}
if ((keys['Space']) && (player.onGround || player.onPlatform)) {
player.velocityY = player.jumpPower;
player.onPlatform = null;
}
Collision Priority
When falling, the player checks surfaces in this order: static floor first, then clouds, then moving platforms. Static tiles are authoritative — if the player has landed on one, the check stops. This avoids a bug where a moving platform and a static floor occupy the same y-range and the player snaps to the wrong one.
if (hitStaticFloor) {
// snap to tile
} else if (hitCloud) {
// land on cloud
} else {
const landed = checkLandOnPlatform(player.velocityY);
if (landed) { /* snap to platform */ }
else { player.y += player.velocityY; }
}
Enemy Collision Uses AABB, Not Distance
The individual “Shoot Him” tutorial used circular distance collision (Math.sqrt(dx² + dy²)). The enemies in this series are axis-aligned rectangles and the combined game uses rectangle overlap (AABB) for bullets:
if (b.x < e.x + e.width && b.x + b.width > e.x &&
b.y < e.y + e.height && b.y + b.height > e.y) {
// hit
}
AABB is faster (no square root) and more accurate for rectangular hitboxes.
The Camera Is Invisible to Game Logic
None of the update functions reference camX or world.x. The camera is applied purely at render time as an offset on the world container. Collision checks, position updates, and spawn coordinates all use world-space pixels. This is the key architectural decision that keeps scrolling from complicating every other system.