Flukz — open-source shmup community resource open-source GPL devlog  ·  about
// indie shmup & game-dev resource

Game Programming · April 5, 2026

Sprites and Collision Detection: The Shmup Foundation

Shmup gameplay lives or dies on collision detection. AABB, circle overlap, pixel-perfect — each approach has tradeoffs. A guide to choosing the right method for a 2D shoot-em-up and implementing it without bugs.

A sprite is just a 2D image displayed at a position on screen. Rendering sprites is simple. Determining whether two sprites have collided is where most beginner game-dev projects stumble. In a shmup, you need to check collisions between the player and potentially hundreds of enemy bullets every tick, so both correctness and performance matter.

What a hitbox actually is

The hitbox is the region of a sprite that, when overlapped by an enemy bullet, registers a hit. Crucially, the hitbox is usually not the same as the visible sprite boundary. In most well-designed shmups, the player ship's hitbox is a small region near the cockpit or nose — much smaller than the ship's visible art. This lets the player weave through dense bullet patterns in ways that feel nearly impossible but are genuinely achievable with skill.

This is not deceptive design. Players quickly learn where the hitbox is (or the game tells them explicitly, as Touhou does with its visible center dot in focused mode). The smaller hitbox is a calibration choice that makes the game fun rather than merely technically possible.

AABB: axis-aligned bounding box

The simplest collision shape is a rectangle aligned with the screen axes — no rotation. Two AABBs overlap if and only if there is overlap on both the X and Y axes simultaneously:

function aabbOverlap(a, b): // a, b have: x, y (top-left), w, h (width, height) return (a.x < b.x + b.w) and (a.x + a.w > b.x) and (a.y < b.y + b.h) and (a.y + a.h > b.y)

AABB is fast: six comparisons per pair. For bullets, you can often use very small bounding boxes (a 4x4 pixel AABB for a bullet feels tight and fair), which makes the check both performant and accurate. Most small-scale shmups use AABB for bullet-vs-player and bullet-vs-enemy checks without needing anything more complex.

Circle overlap: better for many cases

A circle hitbox (defined by center position and radius) handles rotation naturally — a rotating enemy that should have the same hitbox regardless of orientation is much better represented as a circle than as an AABB. Circle vs. circle collision is also simple:

function circleOverlap(a, b): // a, b have: cx, cy (center), r (radius) dx = a.cx - b.cx dy = a.cy - b.cy dist = dx*dx + dy*dy // squared distance rSum = (a.r + b.r) * (a.r + b.r) // squared sum of radii return dist <= rSum // avoid sqrt for performance

Comparing squared distances avoids the expensive square root operation. For the player ship, a circle hitbox centered on the cockpit is often the most natural fit — it maps well to how players perceive their vulnerable area, and it handles the diagonal extremes of AABB (which counts corner-to-corner overlaps that feel unfair) more gracefully.

Pixel-perfect: only when necessary

Pixel-perfect collision checks each overlapping pixel in the sprite's alpha channel to determine whether a solid pixel exists at the collision point. It is the most accurate method and the most expensive. For a shmup with hundreds of bullets in flight, applying pixel-perfect collision to all bullet-vs-player checks would be prohibitively slow.

The practical use case for pixel-perfect in shmups is narrow: for large terrain or background obstacles that have irregular shapes and where using a simplified shape would create visibly unfair results. Even then, the typical approach is to precompute collision bitmasks at load time and use bitwise AND operations for the overlap check, rather than iterating pixels at runtime.

Broad phase and narrow phase

Even simple shapes get expensive when you have 400 bullets on screen and need to check each against the player. The solution is a two-phase approach:

For a typical shmup with a 640x480 play field and a player hitbox of about 8 pixels, a grid of 8x8 pixel cells means you only ever check a handful of bullets per tick in the narrow phase, regardless of total bullet count. This keeps the collision check O(k) where k is the number of bullets in the player's immediate grid region, not O(n) over all bullets.

Debugging hitboxes

Ship a debug mode that renders hitboxes visibly — colored outlines over sprite positions. This is essential during development and invaluable during playtesting. If testers are dying to bullets that appear to miss, the hitbox visualization will immediately reveal whether the shapes are misconfigured or the visual design is misleading. Flukz included a hitbox visualizer in its debug build, which made tuning the player ship's hitbox radius a quick iterative process rather than a guessing game.