octicon-rss(16/)
You've already forked flame
mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-10 22:51:30 +08:00
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:
octicon-git-branch(16/)
octicon-tag(16/)
committed by
GitHub
gitea-unlock(16/)
parent
16a45b27a2
commit
957db3c1ed
octicon-diff(16/tw-mr-1) 3 changed files with 64 additions and 0 deletions
@@ -120,6 +120,43 @@ other way around. If there are no intersections with the edges on a solid hitbox
|
|||||||
position is instead returned.
|
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
|
## ShapeHitbox
|
||||||
|
|
||||||
The `ShapeHitbox`s are normal components, so you add them to the component that you want to add
|
The `ShapeHitbox`s are normal components, so you add them to the component that you want to add
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flame/collisions.dart';
|
import 'package:flame/collisions.dart';
|
||||||
import 'package:flame/components.dart';
|
import 'package:flame/components.dart';
|
||||||
import 'package:flame/geometry.dart';
|
import 'package:flame/geometry.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// [CollisionDetection] is the foundation of the collision detection system in
|
/// [CollisionDetection] is the foundation of the collision detection system in
|
||||||
/// Flame.
|
/// Flame.
|
||||||
@@ -13,6 +14,7 @@ abstract class CollisionDetection<T extends Hitbox<T>,
|
|||||||
|
|
||||||
List<T> get items => broadphase.items;
|
List<T> get items => broadphase.items;
|
||||||
final _lastPotentials = <CollisionProspect<T>>[];
|
final _lastPotentials = <CollisionProspect<T>>[];
|
||||||
|
final collisionsCompletedNotifier = CollisionDetectionCompletionNotifier();
|
||||||
|
|
||||||
CollisionDetection({required this.broadphase});
|
CollisionDetection({required this.broadphase});
|
||||||
|
|
||||||
@@ -62,6 +64,9 @@ abstract class CollisionDetection<T extends Hitbox<T>,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_updateLastPotentials(potentials);
|
_updateLastPotentials(potentials);
|
||||||
|
|
||||||
|
// Let all listeners know that the collision detection step has completed
|
||||||
|
collisionsCompletedNotifier.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
final _lastPotentialsPool = <CollisionProspect<T>>[];
|
final _lastPotentialsPool = <CollisionProspect<T>>[];
|
||||||
@@ -161,3 +166,10 @@ abstract class CollisionDetection<T extends Hitbox<T>,
|
|||||||
List<RaycastResult<T>>? out,
|
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();
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {}
|
class _CollisionDetectionGame extends FlameGame with HasCollisionDetection {}
|
||||||
|
|||||||
Reference in New Issue
Block a user