Skip to content

Commit

Permalink
feat: Add collision completed listener (#2896)
Browse files Browse the repository at this point in the history
Add a new variable to the `CollisionDetection` class. Call
`notifyListeners` at the end of the collision detection step, so that
users can add a listener to their code to know when this step has
finished. The variable is a basic implementation of a `Listenable`
class, since it needs no more complexity.


Closes #2849
  • Loading branch information
christian-mindfulness authored Dec 6, 2023
1 parent 16a45b2 commit 957db3c
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 0 deletions.
37 changes: 37 additions & 0 deletions doc/flame/collision_detection.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<PositionComponent> collisionComponents = [];`. The `onCollision` callback is then used to
save all the other `PositionComponent`s to this list:

```dart
@override
void onCollision(Set<Vector2> 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
Expand Down
12 changes: 12 additions & 0 deletions packages/flame/lib/src/collisions/collision_detection.dart
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -13,6 +14,7 @@ abstract class CollisionDetection<T extends Hitbox<T>,

List<T> get items => broadphase.items;
final _lastPotentials = <CollisionProspect<T>>[];
final collisionsCompletedNotifier = CollisionDetectionCompletionNotifier();

CollisionDetection({required this.broadphase});

Expand Down Expand Up @@ -62,6 +64,9 @@ abstract class CollisionDetection<T extends Hitbox<T>,
}
}
_updateLastPotentials(potentials);

// Let all listeners know that the collision detection step has completed
collisionsCompletedNotifier.notifyListeners();
}

final _lastPotentialsPool = <CollisionProspect<T>>[];
Expand Down Expand Up @@ -161,3 +166,10 @@ abstract class CollisionDetection<T extends Hitbox<T>,
List<RaycastResult<T>>? out,
});
}

/// A class to handle callbacks for when the collision detection is done each
/// tick.
class CollisionDetectionCompletionNotifier extends ChangeNotifier {
@override
void notifyListeners() => super.notifyListeners();
}
15 changes: 15 additions & 0 deletions packages/flame/test/collisions/collision_detection_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}

0 comments on commit 957db3c

Please sign in to comment.