In a small shooter, a hit can look like one boolean check. In GalaxyInvaders, that branch also emits particles, erases a bullet and an enemy from separate std::list containers, updates the score, starts a visual pulse and changes which iterator the outer loop may advance. The geometry test is the easy part; the interesting engineering boundary is the state change around it.

I reviewed the public source at commit 5418009. The project uses C++14 and SFML 2.5.1. This is a source review: I have not run or profiled the executable, so the article describes the code path rather than claiming a verified build or performance result.

GALAXY INVADERS / HIT RESOLUTION
SOURCE REVIEW
  1. 01 / ITERATEPlayer bulletbulletItWalk the projectile list one item at a time.
  2. 02 / TESTEnemy boundsgetGlobalBounds().intersects()Compare axis-aligned rectangles in the inner loop.
  3. 03 / HITResolve one target− 1 enemy · − 1 bullet · +10 scoreEmit a 25-particle burst and trigger the score pulse.
  4. 04 / ADVANCEContinue safelynext_bulletSkip incrementing the erased iterator; test the next projectile.
WORST-CASE CANDIDATE PAIRSB × E

E starts at 40 (10 columns × 4 rows). This is an upper-bound shape from the source loops, not a timing result.

Source-derived control flow from the reviewed C++ snapshot. It describes how a hit mutates game state; it is not a profiler capture or a performance claim.

One overlap has several consequences

The game checks each player projectile against enemies with getGlobalBounds().intersects(). This is an axis-aligned bounding-box test. When it finds a match, it creates an enemy explosion, removes the matching enemy and projectile, adds 10 points, and briefly changes the score text’s size and color. Because the projectile is erased on the first match, one player bullet can remove at most one enemy in that pass.

The core of the branch is short, but it mutates both containers:

if (bulletIt->getGlobalBounds().intersects(enemyIt->getGlobalBounds())) {
    // Create the hit effect and update the score.
    enemyIt = enemies.erase(enemyIt);
    bulletIt = bullets.erase(bulletIt);
    bulletRemoved = true;
    goto next_bullet;
}

The erase return value supplies the next valid iterator. The goto exits the enemy loop and lands at the outer loop’s advancement point; bulletRemoved prevents incrementing the iterator that was already erased. The current control flow accounts for iterator invalidation, but the label makes that contract less obvious to a reader than a break plus the existing removal flag would be.

Keep the search proportional to the game that exists

The reset path creates 10 columns and 4 rows of enemies, so a fresh wave begins with 40 targets. If there are B live player bullets and E remaining enemies, a frame can perform up to B × E rectangle checks before a hit shortens a scan. For a 40-enemy wave, that gives a simple upper bound of 40B candidate pairs at the start of a level.

That equation describes loop shape, not measured cost. The repository publishes no profiler trace, frame-time sample or controlled workload, so it would be premature to call the nested scan a bottleneck or to add a spatial index. A small bounded list may be the right tradeoff until profiling shows otherwise.

A narrow test would protect the mutation contract

At the reviewed revision, the repository has a CMakeLists.txt that fetches SFML 2.5.1 and builds main.cpp, but it has no README or test target. I would extract hit resolution from the rendering loop and test the observable outcomes before changing the container or collision strategy:

Setup Expected result
Bullet and enemy bounds do not overlap Neither object is removed and the score is unchanged.
One bullet overlaps one enemy Both are removed, the score increases by 10 and one enemy burst is created.
A bullet overlaps two enemies Only the first matching enemy in list order is removed; the consumed bullet cannot hit a second target.
The first bullet is erased while another follows it The next bullet is still visited; the invalidated iterator is never incremented.

Those tests would make the collision response easier to change without accidentally skipping an item or applying a hit twice. Only after they pass would I capture a repeatable workload and compare frame time across bullet and enemy counts. Until then, the source shows a bounded game loop and a clear next test—not evidence that collision checks are expensive.

For a broader C++ example of separating visible capabilities from verified internals, read how I documented the boundaries of Arcglass Engine.

Source reviewed

← All notes