Game dev
Game loops, input, collision, and sprites in plain TypeScript and Canvas.
Fixed-timestep game loop
game-looprequestanimationframephysicsRun physics in fixed steps and render as often as the screen allows. An accumulator carries leftover time between frames, so a 60 Hz and a 144 Hz monitor simulate the same game. Clamp the frame time so a backgrounded tab doesn't try to catch up on minutes of updates. Phaser, Godot, and Unity do this for you; write it yourself when you're on plain Canvas or a renderer like PixiJS.
Frame-rate independent movement
delta-timemovementgame-loopSpeeds should be per second, not per frame. Moving 4 px every frame means a 144 Hz player moves 2.4× faster than a 60 Hz one. Multiply by the elapsed time (delta time) instead. Good enough for simple games; use a fixed timestep when collisions or physics must be consistent.
Keyboard input without stuck keys
inputkeyboardeventsTrack which keys are held in a Set and read it during update, instead of moving things inside event handlers. Use `e.code` (physical key) so WASD works on any keyboard layout. Clear the set on blur: if the player alt-tabs while holding a key, keyup never fires and the character walks forever.
Box collision (AABB)
collisionaabbphysicsAxis-aligned bounding boxes are the cheapest useful collision check: two rectangles overlap unless one is fully to the side of, above, or below the other. To resolve a hit, push the moving box out along the axis with the smaller overlap. Very fast objects can pass through thin walls between frames (tunneling) — use smaller steps or a physics engine like Rapier for that.
Sprite sheet animation on canvas
spritesanimationcanvasPack animation frames side by side in one image and draw one slice at a time with the 9-argument `drawImage`. Pick the frame from elapsed time, not a frame counter, so the animation plays at the same speed on every monitor.
Crisp pixel-art scaling
pixel-artcanvasscalingRender at the game's native low resolution, then scale the canvas up with CSS. Turn off smoothing in both places, and scale by whole numbers — a 2.5× scale makes some pixels wider than others. Leftover space becomes letterboxing.