Skip to content

Add sweep_line() and a lasers and mirrors example - #2941

Merged
pvcraven merged 1 commit into
developmentfrom
feature/sweep-line
Oct 7, 2026
Merged

pvcraven merged 1 commit into
developmentfrom
feature/sweep-line

Conversation

@pvcraven

@pvcraven pvcraven commented Oct 7, 2026

Copy link
Copy Markdown
Member

Summary

Adds arcade.sweep_line(start, end, sprite_list), the line version of sweep_sprite, plus a Lasers and Mirrors example.

hit = arcade.sweep_line(gun.position, target, walls)
if hit:
    start, end = Vec2(*gun.position), Vec2(*target)
    point = start + (end - start) * hit.fraction
    arcade.draw_line(*start, *point, arcade.color.RED, 2)

It returns the existing SweepInfo (sprite, fraction, distance, normal) for the first sprite the line hits, or None. Use it for lasers, hitscan weapons, or seeing what's in the way between two points. The docstring shows how to get the hit point and how to reflect off the normal.

How it works

A line is a moving point, so it reuses sweep_sprite's swept overlap math:

  • Shared helper: that math now lives in _sweep_shape(), which takes any convex shape's points, bounds and edge axes. _sweep_against() passes the sprite's hit box, exactly as before, and sweep_line passes a single point with no edge axes.
  • The same rules as sweep_sprite:
    • touching doesn't count, so a line along an edge, or touching only a corner, doesn't hit;
    • a line that ends exactly on an edge doesn't hit;
    • a line that starts on an edge and goes inward hits at fraction 0, but one going outward doesn't. That's what lets a bounced laser leave a mirror.
  • Starting inside a sprite: an immediate hit with fraction 0, and a normal out of the nearest edge (new helper _point_push_out). If the start is inside several sprites, the one it's deepest inside wins.
  • Candidates: a spatial hash, if the list has one, narrows them to the line's bounding box. Otherwise every sprite is checked, with a quick bounding-box test first.

sweep_sprite behavior is unchanged, and its tests pass.

The example: sprite_laser_mirrors

A turret fires a laser at the mouse. Each straight part of the beam uses sweep_line():

  • crates stop the beam;
  • coins are collected, and the beam continues;
  • rotating mirrors reflect it with d - 2 * d.dot(normal) * normal.

It has the code, the rst page with a screenshot, and a gallery entry under "Shooting with Sprites", next to sprite_bullets_sweep.

Tests

New in tests/unit/sprite/test_sprite_collision.py, with and without a spatial hash where it matters:

  • First hit: both directions, and a vertical line entering through the bottom. Checks the sprite, fraction, distance and normal.
  • Rotated sprite: the bottom corner of a 45° square, hit at the exact distance.
  • Misses and touching: a line along an edge, along a side at x = 90, ending exactly on an edge, and ending 1 px past it.
  • Starting inside: the normal is out of the nearest edge; a zero-length line inside hits, and one outside doesn't.
  • Starting on an edge: going in hits at fraction 0, going out doesn't.
  • Compared with brute force: 150 random lines through 12 random rotated and scaled sprites, compared with walking each line in 1,000 steps and testing whether each point is inside a hit box. The fractions agree within 2 steps, and every normal is a unit vector.

Full suite: 1741 passed. With CI's ruff (uv run), formatting, import order and lint all pass. The docs build with sphinx-build -W, and the example page and gallery link render.

The changelog has an entry under Unreleased → New Features.

🤖 Generated with Claude Code

sweep_line(start, end, sprite_list) returns a SweepInfo for the first
sprite a line hits, or None. It's sweep_sprite() for a point: the swept
overlap code now takes any convex shape's points, bounds, and axes, so
sweep_sprite passes the sprite's hit box and sweep_line a single point.
It follows the same rules: touching doesn't count, a line ending on an
edge doesn't hit, and a line starting inside a sprite is an immediate
hit with a normal out of the nearest edge (the deepest, if several).

The sprite_laser_mirrors example aims a laser at the mouse; each part of
the beam uses sweep_line(), and mirrors reflect it with the normal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pvcraven
pvcraven merged commit e97a199 into development Oct 7, 2026
6 of 7 checks passed
@pvcraven
pvcraven deleted the feature/sweep-line branch October 7, 2026 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant