Repository navigation
Add sweep_sprite for collisions along a moving sprite's path - #2914
Merged
Merged
Conversation
sweep_sprite(sprite, dx, dy, sprite_list) checks the whole straight-line path of a moving sprite and returns a SweepInfo (sprite, fraction, distance, normal) for the first sprite it would hit, or None. Fast sprites can no longer jump over thin walls between frames. It doesn't move the sprite. It extends the separating axis test to a moving shape: for each axis (x and y from the bounds, then the cached edge normals) it finds when the projections overlap during the move, and the sprites first overlap at the latest start, if before the earliest end. Starting already overlapping is an immediate hit at fraction 0, with the deepest overlap and get_collision_info's push-out normal. Touching isn't a hit, and a move ending exactly touching isn't either. Exact for convex hit boxes that don't rotate during the move. Candidates come from the spatial hash (near the path's bounding box) if there is one, otherwise the whole list, with a radius and bounding box check before the axis test. Add the sprite_bullets_sweep example comparing it with check_for_collision_with_list on fast lasers and thin walls, with its docs page, screenshot, and gallery entry under Shooting with Sprites. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
arcade.sweep_sprite(sprite, dx, dy, sprite_list), a collision check for fast-moving sprites (#5 on the collision list).check_for_collisiononly checks where a sprite is, so a sprite moving far enough in one frame can jump right over a thin wall.sweep_spritechecks the whole straight-line path and returns the first sprite it would hit. It doesn't move the sprite.It returns
Noneif the path is clear, otherwise aSweepInfonamed tuple:spritefractiondistancenormalBehavior
fraction0.0, with the push-out direction fromget_collision_infoas the normal. If several overlap, the deepest is returned, even if something else is in the way.check_for_collision. A sprite can slide along a wall or move away from one it touches. Moving into a touching wall is a hit at 0.0. A move that ends exactly touching isn't a hit.How it works
This extends the separating axis test to a moving shape. For each axis (x and y from the bounding boxes, then the cached distinct edge normals), it computes when the two projections overlap during the move. The sprites first overlap at the latest of those start times, if that's before the earliest end time. The axis that sets the start time gives the normal.
Per sprite, it does a radius check against the path's bounding box, then a bounding-box check, then the axis test, stopping early once an axis rules the sprite out or it can't beat the best hit so far.
Example (code, docs page, and gallery entry)
arcade/examples/sprite_bullets_sweep.pyhas two lanes of 80 px/frame lasers firing at three 6 px walls. The top lane moves and then callscheck_for_collision_with_list, so lasers skip walls and some pass through all three. The bottom lane usessweep_spriteand every laser stops at the first wall. Both lanes show their counts on screen. In a 1,200-frame headless run: top lane 265 hits and 33 pass-throughs; bottom lane 298 hits, 0 pass-throughs, all at the first wall.doc/example_code/sprite_bullets_sweep.rst, with a screenshot rendered from the example.doc/example_code/index.rstunder Shooting with Sprites. The full docs build (with-W) generates its thumbnail, and the built gallery page links to it.Performance
µs per call, one laser against 400 randomly placed boxes:
check_for_collision_with_listsweep_sprite(12 px or 60 px move)Without a spatial hash,
sweep_spriteis faster than the plain list check, because its circle check against the path's bounding box rejects distant sprites cheaply. With a spatial hash it's about 2× a plain check; most of the extra time is the rectangle query on the hash.Tests
test_sweep_sprite_thin_wall(with and without a spatial hash): the plain check at the end position misses, and the sweep hits at exactly 22/50, with normal (-1, 0).test_sweep_sprite_misses: moving away, stopping short, ending exactly touching, not moving, passing just above, empty list.test_sweep_sprite_touching: away, sliding along, and into.test_sweep_sprite_ends_touching_slanted_edge: diamond hit boxes with integer corners, so the touch along a 45° edge is exact.test_sweep_sprite_starts_overlapping: deepest overlap wins over a sprite in the way, with the push-out normal.test_sweep_sprite_first_hit: the closest sprite, not the first in the list, with and without a spatial hash.test_sweep_sprite_diagonal: normal of a 45° wall.test_sweep_sprite_matches_stepping: 2,000 random rotated, flipped and scaled pairs with random moves, compared against stepping along the path. For each hit:fraction,For each miss, no sampled point collides.
sin(π/4)value simulated. Ruff is clean, and mypy reports no errors incollision.py.Found along the way (not changed here)
Assigning
sprite.hit_box = HitBox(points)leaves the new hit box at (0, 0) until the sprite next moves, rather than at the sprite's position. One of my tests tripped on this; the test sets the position after the hit box. It's probably worth a separate fix.🤖 Generated with Claude Code