diff --git a/doc/flame/collision_detection.md b/doc/flame/collision_detection.md index 8de52087a..fe91444f7 100644 --- a/doc/flame/collision_detection.md +++ b/doc/flame/collision_detection.md @@ -120,6 +120,43 @@ other way around. If there are no intersections with the edges on a solid hitbox position is instead returned. +### Collision order + +If a `Hitbox` collides with more than one other `Hitbox` within a given time step, then +the `onCollision` callbacks will be called in an essentially random order. In some cases this can +be a problem, such as in a bouncing ball game where the trajectory of the ball can differ depending +on which other object was hit first. To help resolve this the `collisionsCompletedNotifier` +listener can be used - this triggers at the end of the collision detection process. + +An example of how this might be used is to add a local variable in your `PositionComponent` to save +the other components with which it's colliding: +`List collisionComponents = [];`. The `onCollision` callback is then used to +save all the other `PositionComponent`s to this list: + +```dart +@override +void onCollision(Set intersectionPoints, PositionComponent other) { + collisionComponents.add(other); + super.onCollision(intersectionPoints, other); +} + +``` + +Finally, one adds a listener to the `onLoad` method of the `PositionComponent` to call a function +which will resolve how the collisions should be dealt with: + +```dart +(game as HasCollisionDetection) + .collisionDetection + .collisionsCompletedNotifier + .addListener(() { + resolveCollisions(); +}); +``` + +The list `collisionComponents` would need to be cleared in each call to `update`. + + ## ShapeHitbox The `ShapeHitbox`s are normal components, so you add them to the component that you want to add diff --git a/packages/flame/lib/src/collisions/collision_detection.dart b/packages/flame/lib/src/collisions/collision_detection.dart index 42cde1b2e..11440221c 100644 --- a/packages/flame/lib/src/collisions/collision_detection.dart +++ b/packages/flame/lib/src/collisions/collision_detection.dart @@ -1,6 +1,7 @@ import 'package:flame/collisions.dart'; import 'package:flame/components.dart'; import 'package:flame/geometry.dart'; +import 'package:flutter/material.dart'; /// [CollisionDetection] is the foundation of the collision detection system in /// Flame. @@ -13,6 +14,7 @@ abstract class CollisionDetection, List get items => broadphase.items; final _lastPotentials = >[]; + final collisionsCompletedNotifier = CollisionDetectionCompletionNotifier(); CollisionDetection({required this.broadphase}); @@ -62,6 +64,9 @@ abstract class CollisionDetection, } } _updateLastPotentials(potentials); + + // Let all listeners know that the collision detection step has completed + collisionsCompletedNotifier.notifyListeners(); } final _lastPotentialsPool = >[]; @@ -161,3 +166,10 @@ abstract class CollisionDetection, List>? out, }); } + +/// A class to handle callbacks for when the collision detection is done each +/// tick. +class CollisionDetectionCompletionNotifier extends ChangeNotifier { + @override + void notifyListeners() => super.notifyListeners(); +} diff --git a/packages/flame/test/collisions/collision_detection_test.dart b/packages/flame/test/collisions/collision_detection_test.dart index 133f60437..1496eba88 100644 --- a/packages/flame/test/collisions/collision_detection_test.dart +++ b/packages/flame/test/collisions/collision_detection_test.dart @@ -1756,6 +1756,21 @@ void main() { }, }); }); + + group('collisionsCompletedNotifier', () { + runCollisionTestRegistry({ + 'collisionsCompletedNotifier calls listeners': (game) async { + var calledTimes = 0; + final listeners = List.generate(10, (_) => () => calledTimes++); + for (final listener in listeners) { + game.collisionDetection.collisionsCompletedNotifier + .addListener(listener); + } + game.update(0); + expect(calledTimes, listeners.length); + }, + }); + }); } class _CollisionDetectionGame extends FlameGame with HasCollisionDetection {}