feat: Add collision completed listener (#2896)

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
This commit is contained in:
christian-mindfulness
2023-12-06 21:45:11 +00:00
committed by GitHub
parent 16a45b27a2
commit 957db3c1ed
3 changed files with 64 additions and 0 deletions

View File

@ -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

View File

@ -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<T extends Hitbox<T>,
List<T> get items => broadphase.items;
final _lastPotentials = <CollisionProspect<T>>[];
final collisionsCompletedNotifier = CollisionDetectionCompletionNotifier();
CollisionDetection({required this.broadphase});
@ -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>>[];
@ -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();
}

View File

@ -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 {}