diff --git a/packages/flame/lib/src/collisions/hitboxes/circle_hitbox.dart b/packages/flame/lib/src/collisions/hitboxes/circle_hitbox.dart index aa6b92dca4f..09d01eb12ff 100644 --- a/packages/flame/lib/src/collisions/hitboxes/circle_hitbox.dart +++ b/packages/flame/lib/src/collisions/hitboxes/circle_hitbox.dart @@ -44,10 +44,11 @@ class CircleHitbox extends CircleComponent with ShapeHitbox { // the parent size and the radius is defined from the shortest side. } - late final _temporaryLineSegment = LineSegment.zero(); - late final _temporaryNormal = Vector2.zero(); - late final _temporaryCenter = Vector2.zero(); - late final _temporaryAbsoluteCenter = Vector2.zero(); + static final _temporaryLineSegment = LineSegment.zero(); + static final _temporaryNormal = Vector2.zero(); + static final _temporaryCenter = Vector2.zero(); + static final _temporaryAbsoluteCenter = Vector2.zero(); + static final _temporaryOrigin = Vector2.zero(); @override RaycastResult? rayIntersection( @@ -56,6 +57,13 @@ class CircleHitbox extends CircleComponent with ShapeHitbox { }) { var isInsideHitbox = false; _temporaryLineSegment.from.setFrom(ray.origin); + // Adding a small value to the origin to avoid the ray to be on the edge + // of the circle and then directly intersecting and causing the reflecting + // ray to go in the wrong direction. + _temporaryOrigin.setValues( + ray.origin.x + ray.direction.x * 0.00001, + ray.origin.y + ray.direction.y * 0.00001, + ); _temporaryAbsoluteCenter.setFrom(absoluteCenter); _temporaryCenter ..setFrom(_temporaryAbsoluteCenter) @@ -73,7 +81,7 @@ class CircleHitbox extends CircleComponent with ShapeHitbox { ..y *= (ray.direction.y.sign * _temporaryLineSegment.to.y.sign); } - if (ray.origin.distanceToSquared(_temporaryAbsoluteCenter) < + if (_temporaryOrigin.distanceToSquared(_temporaryAbsoluteCenter) < radius * radius) { _temporaryLineSegment.to.scaleTo(2 * radius); isInsideHitbox = true; @@ -105,7 +113,10 @@ class CircleHitbox extends CircleComponent with ShapeHitbox { origin: intersectionPoint, direction: reflectionDirection, )) ?? - Ray2(origin: intersectionPoint, direction: reflectionDirection); + Ray2( + origin: intersectionPoint, + direction: reflectionDirection, + ); result.setWith( hitbox: this, diff --git a/packages/flame/test/collisions/collision_detection_test.dart b/packages/flame/test/collisions/collision_detection_test.dart index bc15d8d2dc6..d5abfda27db 100644 --- a/packages/flame/test/collisions/collision_detection_test.dart +++ b/packages/flame/test/collisions/collision_detection_test.dart @@ -1804,6 +1804,31 @@ void main() { expect(reflectionRay2?.origin, Vector2(50, 0)); expect(reflectionRay2?.direction, Vector2(1, 1)..normalize()); }, + 'make sure that ray does not escape circle hitbox': (game) async { + final world = (game as FlameGame).world; + final circle = CircleComponent( + position: Vector2(0, 0), + radius: 5, + anchor: Anchor.center, + )..add(CircleHitbox()); + await world.ensureAdd(circle); + final ray = Ray2( + origin: Vector2(0, 0), + direction: Vector2(1.0, 0), + ); + final results = game.collisionDetection.raytrace(ray); + expect(results.length, 10); + expect(results.first.isActive, isTrue); + expect(results.first.isInsideHitbox, isTrue); + expect( + results.first.intersectionPoint, + Vector2(5, 0), + ); + final reflectionRay = results.first.reflectionRay; + expect(reflectionRay?.origin, Vector2(5, 0)); + expect(reflectionRay?.direction, Vector2(-1, 0)..normalize()); + expect(results.first.normal, Vector2(-1, 0)); + }, }); });