mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 19:12:31 +08:00
onCollisionEnd callbacks for Collidable and HitboxShape (#792)
* Add onCollisionEnd for HitboxShape and Collidable * Add tests for collision callbacks * Detect multiple collsions with same collidable in test * Remove unused import * Break out duplicated code * Fix formatting * Use correct hash set * Update examples/lib/stories/collision_detection/multiple_shapes.dart Co-authored-by: Erick <erickzanardoo@gmail.com> * Update examples/lib/stories/collision_detection/multiple_shapes.dart Co-authored-by: Erick <erickzanardoo@gmail.com> * Use hashValues instead of _combineHashCodes * hashValues is order dependent so we need to sort the objects first * Add section about onCollisionEnd * Fix missed hashValues * Use xor instead of hashValues * Update examples/lib/stories/collision_detection/collision_detection.dart Co-authored-by: Luan Nico <luanpotter27@gmail.com> Co-authored-by: Erick <erickzanardoo@gmail.com> Co-authored-by: Luan Nico <luanpotter27@gmail.com>
This commit is contained in:
@ -86,6 +86,15 @@ class MyCollidable extends PositionComponent with Hitbox, Collidable {
|
|||||||
...
|
...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onCollisionEnd(Collidable other) {
|
||||||
|
if (other is CollidableScreen) {
|
||||||
|
...
|
||||||
|
} else if (other is YourOtherCollidable) {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -93,7 +102,8 @@ In this example it can be seen how the Dart `is` keyword is used to check which
|
|||||||
that your component collided with. The set of points is where the edges of the hitboxes collided.
|
that your component collided with. The set of points is where the edges of the hitboxes collided.
|
||||||
Note that the `onCollision` method will be called on both collidable components if they
|
Note that the `onCollision` method will be called on both collidable components if they
|
||||||
have both implemented the `onCollision` method, and also on both shapes if they have that method
|
have both implemented the `onCollision` method, and also on both shapes if they have that method
|
||||||
implemented.
|
implemented. The same goes for the `onCollisionEnd` method, which is called when two components or
|
||||||
|
shapes that were previously colliding no longer colliding with each other.
|
||||||
|
|
||||||
If you want to check collisions with the screen edges, as we do in the example above, you can use
|
If you want to check collisions with the screen edges, as we do in the example above, you can use
|
||||||
the predefined [ScreenCollidable](#ScreenCollidable) class and since that one also is a `Collidable`
|
the predefined [ScreenCollidable](#ScreenCollidable) class and since that one also is a `Collidable`
|
||||||
|
|||||||
@ -6,6 +6,19 @@ import 'circles.dart';
|
|||||||
import 'multiple_shapes.dart';
|
import 'multiple_shapes.dart';
|
||||||
import 'only_shapes.dart';
|
import 'only_shapes.dart';
|
||||||
|
|
||||||
|
const basicInfo = '''
|
||||||
|
An example with many hitboxes that move around on the screen and during
|
||||||
|
collisions they change color depending on what it is that they have collided
|
||||||
|
with.
|
||||||
|
|
||||||
|
The snowman, the component built with three circles on top of each other, works
|
||||||
|
a little bit differently than the other components to show that you can have
|
||||||
|
multiple hitboxes within one component.
|
||||||
|
|
||||||
|
On this example, you can "throw" the components by dragging them quickly in any
|
||||||
|
direction.
|
||||||
|
''';
|
||||||
|
|
||||||
void addCollisionDetectionStories(Dashbook dashbook) {
|
void addCollisionDetectionStories(Dashbook dashbook) {
|
||||||
dashbook.storiesOf('Collision Detection')
|
dashbook.storiesOf('Collision Detection')
|
||||||
..add(
|
..add(
|
||||||
@ -17,6 +30,7 @@ void addCollisionDetectionStories(Dashbook dashbook) {
|
|||||||
'Multiple shapes',
|
'Multiple shapes',
|
||||||
(_) => GameWidget(game: MultipleShapes()),
|
(_) => GameWidget(game: MultipleShapes()),
|
||||||
codeLink: baseLink('collision_detection/multiple_shapes.dart'),
|
codeLink: baseLink('collision_detection/multiple_shapes.dart'),
|
||||||
|
info: basicInfo,
|
||||||
)
|
)
|
||||||
..add(
|
..add(
|
||||||
'Shapes without components',
|
'Shapes without components',
|
||||||
|
|||||||
@ -18,9 +18,9 @@ abstract class MyCollidable extends PositionComponent
|
|||||||
final delta = Vector2.zero();
|
final delta = Vector2.zero();
|
||||||
double angleDelta = 0;
|
double angleDelta = 0;
|
||||||
bool _isDragged = false;
|
bool _isDragged = false;
|
||||||
final _activePaint = Paint()..color = Colors.amber;
|
late final Paint _activePaint;
|
||||||
late final Color _defaultDebugColor = debugColor;
|
final Color _defaultColor = Colors.blue.withOpacity(0.8);
|
||||||
bool _isHit = false;
|
final Set<Collidable> _activeCollisions = {};
|
||||||
final ScreenCollidable screenCollidable;
|
final ScreenCollidable screenCollidable;
|
||||||
|
|
||||||
MyCollidable(
|
MyCollidable(
|
||||||
@ -34,17 +34,17 @@ abstract class MyCollidable extends PositionComponent
|
|||||||
anchor = Anchor.center;
|
anchor = Anchor.center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
_activePaint = Paint()..color = _defaultColor;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void update(double dt) {
|
void update(double dt) {
|
||||||
super.update(dt);
|
super.update(dt);
|
||||||
if (_isDragged) {
|
if (_isDragged) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!_isHit) {
|
|
||||||
debugColor = _defaultDebugColor;
|
|
||||||
} else {
|
|
||||||
_isHit = false;
|
|
||||||
}
|
|
||||||
delta.setFrom(velocity * dt);
|
delta.setFrom(velocity * dt);
|
||||||
position.add(delta);
|
position.add(delta);
|
||||||
angleDelta = dt * rotationSpeed;
|
angleDelta = dt * rotationSpeed;
|
||||||
@ -63,6 +63,7 @@ abstract class MyCollidable extends PositionComponent
|
|||||||
@override
|
@override
|
||||||
void render(Canvas canvas) {
|
void render(Canvas canvas) {
|
||||||
super.render(canvas);
|
super.render(canvas);
|
||||||
|
renderShapes(canvas, paint: _activePaint);
|
||||||
if (_isDragged) {
|
if (_isDragged) {
|
||||||
final localCenter = (size / 2).toOffset();
|
final localCenter = (size / 2).toOffset();
|
||||||
canvas.drawCircle(localCenter, 5, _activePaint);
|
canvas.drawCircle(localCenter, 5, _activePaint);
|
||||||
@ -71,25 +72,34 @@ abstract class MyCollidable extends PositionComponent
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onCollision(Set<Vector2> intersectionPoints, Collidable other) {
|
void onCollision(Set<Vector2> intersectionPoints, Collidable other) {
|
||||||
_isHit = true;
|
final isNew = _activeCollisions.add(other);
|
||||||
|
if (isNew) {
|
||||||
|
_activePaint.color = collisionColor(other).withOpacity(0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onCollisionEnd(Collidable other) {
|
||||||
|
_activeCollisions.remove(other);
|
||||||
|
if (_activeCollisions.isEmpty) {
|
||||||
|
_activePaint.color = _defaultColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color collisionColor(Collidable other) {
|
||||||
switch (other.runtimeType) {
|
switch (other.runtimeType) {
|
||||||
case ScreenCollidable:
|
case ScreenCollidable:
|
||||||
debugColor = Colors.teal;
|
return Colors.teal;
|
||||||
break;
|
|
||||||
case CollidablePolygon:
|
case CollidablePolygon:
|
||||||
debugColor = Colors.blue;
|
return Colors.deepOrange;
|
||||||
break;
|
|
||||||
case CollidableCircle:
|
case CollidableCircle:
|
||||||
debugColor = Colors.green;
|
return Colors.green;
|
||||||
break;
|
|
||||||
case CollidableRectangle:
|
case CollidableRectangle:
|
||||||
debugColor = Colors.cyan;
|
return Colors.cyan;
|
||||||
break;
|
|
||||||
case CollidableSnowman:
|
case CollidableSnowman:
|
||||||
debugColor = Colors.amber;
|
return Colors.amber;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
debugColor = Colors.pink;
|
return Colors.pink;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,20 +162,18 @@ class CollidableCircle extends MyCollidable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SnowmanPart extends HitboxCircle {
|
class SnowmanPart extends HitboxCircle {
|
||||||
static const startColor = Colors.white;
|
final startColor = Colors.blue.withOpacity(0.8);
|
||||||
final hitPaint = Paint()
|
final hitPaint = Paint();
|
||||||
..color = startColor
|
|
||||||
..strokeWidth = 1
|
|
||||||
..style = PaintingStyle.stroke;
|
|
||||||
|
|
||||||
SnowmanPart(double definition, Vector2 relativeOffset, Color hitColor)
|
SnowmanPart(double definition, Vector2 relativeOffset, Color hitColor)
|
||||||
: super(definition: definition) {
|
: super(definition: definition) {
|
||||||
this.relativeOffset.setFrom(relativeOffset);
|
this.relativeOffset.setFrom(relativeOffset);
|
||||||
|
hitPaint..color = startColor;
|
||||||
onCollision = (Set<Vector2> intersectionPoints, HitboxShape other) {
|
onCollision = (Set<Vector2> intersectionPoints, HitboxShape other) {
|
||||||
if (other.component is ScreenCollidable) {
|
if (other.component is ScreenCollidable) {
|
||||||
hitPaint..color = startColor;
|
hitPaint..color = startColor;
|
||||||
} else {
|
} else {
|
||||||
hitPaint..color = hitColor;
|
hitPaint.color = hitColor.withOpacity(0.8);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -196,9 +204,6 @@ class CollidableSnowman extends MyCollidable {
|
|||||||
|
|
||||||
class MultipleShapes extends BaseGame
|
class MultipleShapes extends BaseGame
|
||||||
with HasCollidables, HasDraggableComponents {
|
with HasCollidables, HasDraggableComponents {
|
||||||
@override
|
|
||||||
bool debugMode = true;
|
|
||||||
|
|
||||||
final TextPaint fpsTextPaint = TextPaint(
|
final TextPaint fpsTextPaint = TextPaint(
|
||||||
config: TextPaintConfig(
|
config: TextPaintConfig(
|
||||||
color: BasicPalette.white.color,
|
color: BasicPalette.white.color,
|
||||||
@ -219,7 +224,7 @@ class MultipleShapes extends BaseGame
|
|||||||
add(screenCollidable);
|
add(screenCollidable);
|
||||||
add(snowman);
|
add(snowman);
|
||||||
var totalAdded = 1;
|
var totalAdded = 1;
|
||||||
while (totalAdded < 10) {
|
while (totalAdded < 20) {
|
||||||
lastToAdd = createRandomCollidable(lastToAdd, screenCollidable);
|
lastToAdd = createRandomCollidable(lastToAdd, screenCollidable);
|
||||||
final lastBottomRight =
|
final lastBottomRight =
|
||||||
lastToAdd.toAbsoluteRect().bottomRight.toVector2();
|
lastToAdd.toAbsoluteRect().bottomRight.toVector2();
|
||||||
|
|||||||
@ -18,7 +18,7 @@ dependencies:
|
|||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
dart_code_metrics: ^3.1.0
|
dart_code_metrics: ^3.2.2
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
- Replace deprecated analysis option lines-of-executable-code with source-lines-of-code
|
- Replace deprecated analysis option lines-of-executable-code with source-lines-of-code
|
||||||
- Fix the anchor of SpriteWidget
|
- Fix the anchor of SpriteWidget
|
||||||
- Add test for re-adding previously removed component
|
- Add test for re-adding previously removed component
|
||||||
|
- Add onCollisionEnd to make it possible for the user to easily detect when a collision ends
|
||||||
|
|
||||||
## [1.0.0-rc10]
|
## [1.0.0-rc10]
|
||||||
- Updated tutorial documentation to indicate use of new version
|
- Updated tutorial documentation to indicate use of new version
|
||||||
|
|||||||
@ -18,6 +18,7 @@ mixin Collidable on Hitbox {
|
|||||||
CollidableType collidableType = CollidableType.active;
|
CollidableType collidableType = CollidableType.active;
|
||||||
|
|
||||||
void onCollision(Set<Vector2> intersectionPoints, Collidable other) {}
|
void onCollision(Set<Vector2> intersectionPoints, Collidable other) {}
|
||||||
|
void onCollisionEnd(Collidable other) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScreenCollidable extends PositionComponent
|
class ScreenCollidable extends PositionComponent
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import '../../../extensions.dart';
|
import '../../../extensions.dart';
|
||||||
import '../../geometry/shape.dart';
|
import '../../geometry/shape.dart';
|
||||||
@ -26,8 +27,8 @@ mixin Hitbox on PositionComponent {
|
|||||||
_shapes.any((shape) => shape.containsPoint(point));
|
_shapes.any((shape) => shape.containsPoint(point));
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderShapes(Canvas canvas) {
|
void renderShapes(Canvas canvas, {Paint? paint}) {
|
||||||
_shapes.forEach((shape) => shape.render(canvas, debugPaint));
|
_shapes.forEach((shape) => shape.render(canvas, paint ?? debugPaint));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Since this is a cheaper calculation than checking towards all shapes, this
|
/// Since this is a cheaper calculation than checking towards all shapes, this
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import '../../extensions.dart';
|
import '../../extensions.dart';
|
||||||
|
import '../../geometry.dart';
|
||||||
import '../components/mixins/collidable.dart';
|
import '../components/mixins/collidable.dart';
|
||||||
|
|
||||||
|
final Set<int> _collidableHashes = {};
|
||||||
|
final Set<int> _shapeHashes = {};
|
||||||
|
|
||||||
int _collidableTypeCompare(Collidable a, Collidable b) {
|
int _collidableTypeCompare(Collidable a, Collidable b) {
|
||||||
return a.collidableType.index - b.collidableType.index;
|
return a.collidableType.index - b.collidableType.index;
|
||||||
}
|
}
|
||||||
@ -25,11 +29,43 @@ void collisionDetection(List<Collidable> collidables) {
|
|||||||
if (intersectionPoints.isNotEmpty) {
|
if (intersectionPoints.isNotEmpty) {
|
||||||
collidableX.onCollision(intersectionPoints, collidableY);
|
collidableX.onCollision(intersectionPoints, collidableY);
|
||||||
collidableY.onCollision(intersectionPoints, collidableX);
|
collidableY.onCollision(intersectionPoints, collidableX);
|
||||||
|
final collisionHash = _combinedHash(collidableX, collidableY);
|
||||||
|
_collidableHashes.add(collisionHash);
|
||||||
|
} else {
|
||||||
|
_handleCollisionEnd(collidableX, collidableY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasActiveCollision(Collidable collidableA, Collidable collidableB) {
|
||||||
|
return _collidableHashes.contains(
|
||||||
|
_combinedHash(collidableA, collidableB),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasActiveShapeCollision(HitboxShape shapeA, HitboxShape shapeB) {
|
||||||
|
return _shapeHashes.contains(
|
||||||
|
_combinedHash(shapeA, shapeB),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleCollisionEnd(Collidable collidableA, Collidable collidableB) {
|
||||||
|
if (hasActiveCollision(collidableA, collidableB)) {
|
||||||
|
collidableA.onCollisionEnd(collidableB);
|
||||||
|
collidableB.onCollisionEnd(collidableA);
|
||||||
|
_collidableHashes.remove(_combinedHash(collidableA, collidableB));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleShapeCollisionEnd(HitboxShape shapeA, HitboxShape shapeB) {
|
||||||
|
if (hasActiveShapeCollision(shapeA, shapeB)) {
|
||||||
|
shapeA.onCollisionEnd(shapeB);
|
||||||
|
shapeB.onCollisionEnd(shapeA);
|
||||||
|
_shapeHashes.remove(_combinedHash(shapeA, shapeB));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Check what the intersection points of two collidables are
|
/// Check what the intersection points of two collidables are
|
||||||
/// returns an empty list if there are no intersections
|
/// returns an empty list if there are no intersections
|
||||||
Set<Vector2> intersections(
|
Set<Vector2> intersections(
|
||||||
@ -38,6 +74,13 @@ Set<Vector2> intersections(
|
|||||||
) {
|
) {
|
||||||
if (!collidableA.possiblyOverlapping(collidableB)) {
|
if (!collidableA.possiblyOverlapping(collidableB)) {
|
||||||
// These collidables can't have any intersection points
|
// These collidables can't have any intersection points
|
||||||
|
if (hasActiveCollision(collidableA, collidableB)) {
|
||||||
|
for (final shapeA in collidableA.shapes) {
|
||||||
|
for (final shapeB in collidableB.shapes) {
|
||||||
|
_handleShapeCollisionEnd(shapeA, shapeB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,8 +95,15 @@ Set<Vector2> intersections(
|
|||||||
shapeA.onCollision(currentResult, shapeB);
|
shapeA.onCollision(currentResult, shapeB);
|
||||||
shapeB.onCollision(currentResult, shapeA);
|
shapeB.onCollision(currentResult, shapeA);
|
||||||
currentResult.clear();
|
currentResult.clear();
|
||||||
|
_shapeHashes.add(_combinedHash(shapeA, shapeB));
|
||||||
|
} else {
|
||||||
|
_handleShapeCollisionEnd(shapeA, shapeB);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _combinedHash(Object o1, Object o2) {
|
||||||
|
return o1.hashCode ^ o2.hashCode;
|
||||||
|
}
|
||||||
|
|||||||
@ -126,6 +126,10 @@ mixin HitboxShape on Shape {
|
|||||||
/// Assign your own [CollisionCallback] if you want a callback when this
|
/// Assign your own [CollisionCallback] if you want a callback when this
|
||||||
/// shape collides with another [HitboxShape]
|
/// shape collides with another [HitboxShape]
|
||||||
CollisionCallback onCollision = emptyCollisionCallback;
|
CollisionCallback onCollision = emptyCollisionCallback;
|
||||||
|
|
||||||
|
/// Assign your own [CollisionEndCallback] if you want a callback when this
|
||||||
|
/// shape stops colliding with another [HitboxShape]
|
||||||
|
CollisionEndCallback onCollisionEnd = emptyCollisionEndCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef CollisionCallback = void Function(
|
typedef CollisionCallback = void Function(
|
||||||
@ -133,7 +137,10 @@ typedef CollisionCallback = void Function(
|
|||||||
HitboxShape other,
|
HitboxShape other,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
typedef CollisionEndCallback = void Function(HitboxShape other);
|
||||||
|
|
||||||
void emptyCollisionCallback(Set<Vector2> _, HitboxShape __) {}
|
void emptyCollisionCallback(Set<Vector2> _, HitboxShape __) {}
|
||||||
|
void emptyCollisionEndCallback(HitboxShape _) {}
|
||||||
|
|
||||||
/// Used for caching calculated shapes, the cache is determined to be valid by
|
/// Used for caching calculated shapes, the cache is determined to be valid by
|
||||||
/// comparing a list of values that can be of any type and is compared to the
|
/// comparing a list of values that can be of any type and is compared to the
|
||||||
|
|||||||
@ -14,7 +14,7 @@ dev_dependencies:
|
|||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
test: ^1.16.0
|
test: ^1.16.0
|
||||||
dart_code_metrics: ^3.1.0
|
dart_code_metrics: ^3.2.2
|
||||||
dartdoc: ^0.42.0
|
dartdoc: ^0.42.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
129
packages/flame/test/components/collision_callback_test.dart
Normal file
129
packages/flame/test/components/collision_callback_test.dart
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flame/geometry.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
|
class TestGame extends BaseGame with HasCollidables {
|
||||||
|
TestGame() {
|
||||||
|
onResize(Vector2.all(200));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestHitbox extends HitboxRectangle {
|
||||||
|
final Set<HitboxShape> collisions = {};
|
||||||
|
int endCounter = 0;
|
||||||
|
|
||||||
|
TestHitbox() {
|
||||||
|
onCollision = (_, shape) => collisions.add(shape);
|
||||||
|
onCollisionEnd = (shape) {
|
||||||
|
endCounter++;
|
||||||
|
collisions.remove(shape);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasCollisionWith(HitboxShape otherShape) {
|
||||||
|
return collisions.contains(otherShape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestBlock extends PositionComponent with Hitbox, Collidable {
|
||||||
|
final Set<Collidable> collisions = {};
|
||||||
|
final hitbox = TestHitbox();
|
||||||
|
int endCounter = 0;
|
||||||
|
|
||||||
|
TestBlock(Vector2 position, Vector2 size)
|
||||||
|
: super(
|
||||||
|
position: position,
|
||||||
|
size: size,
|
||||||
|
) {
|
||||||
|
addShape(hitbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasCollisionWith(Collidable otherCollidable) {
|
||||||
|
return collisions.contains(otherCollidable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onCollision(Set<Vector2> intersectionPoints, Collidable other) {
|
||||||
|
collisions.add(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onCollisionEnd(Collidable other) {
|
||||||
|
endCounter++;
|
||||||
|
collisions.remove(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestGame gameWithCollidables(List<Collidable> collidables) {
|
||||||
|
final game = TestGame();
|
||||||
|
game.addAll(collidables);
|
||||||
|
game.update(0);
|
||||||
|
expect(game.components.isNotEmpty, collidables.isNotEmpty);
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
group(
|
||||||
|
'Collision callbacks are called properly',
|
||||||
|
() {
|
||||||
|
test('collidable callbacks are called', () {
|
||||||
|
final blockA = TestBlock(
|
||||||
|
Vector2.zero(),
|
||||||
|
Vector2.all(10),
|
||||||
|
);
|
||||||
|
final blockB = TestBlock(
|
||||||
|
Vector2.all(1),
|
||||||
|
Vector2.all(10),
|
||||||
|
);
|
||||||
|
final game = gameWithCollidables([blockA, blockB]);
|
||||||
|
expect(blockA.hasCollisionWith(blockB), true);
|
||||||
|
expect(blockB.hasCollisionWith(blockA), true);
|
||||||
|
expect(blockA.collisions.length, 1);
|
||||||
|
expect(blockB.collisions.length, 1);
|
||||||
|
blockB.position = Vector2.all(21);
|
||||||
|
expect(blockA.endCounter, 0);
|
||||||
|
expect(blockB.endCounter, 0);
|
||||||
|
game.update(0);
|
||||||
|
expect(blockA.hasCollisionWith(blockB), false);
|
||||||
|
expect(blockB.hasCollisionWith(blockA), false);
|
||||||
|
expect(blockA.collisions.length, 0);
|
||||||
|
expect(blockB.collisions.length, 0);
|
||||||
|
game.update(0);
|
||||||
|
expect(blockA.endCounter, 1);
|
||||||
|
expect(blockB.endCounter, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hitbox callbacks are called', () {
|
||||||
|
final blockA = TestBlock(
|
||||||
|
Vector2.zero(),
|
||||||
|
Vector2.all(10),
|
||||||
|
);
|
||||||
|
final blockB = TestBlock(
|
||||||
|
Vector2.all(1),
|
||||||
|
Vector2.all(10),
|
||||||
|
);
|
||||||
|
final hitboxA = blockA.hitbox;
|
||||||
|
final hitboxB = blockB.hitbox;
|
||||||
|
final game = gameWithCollidables([blockA, blockB]);
|
||||||
|
expect(hitboxA.hasCollisionWith(hitboxB), true);
|
||||||
|
expect(hitboxB.hasCollisionWith(hitboxA), true);
|
||||||
|
expect(hitboxA.collisions.length, 1);
|
||||||
|
expect(hitboxB.collisions.length, 1);
|
||||||
|
blockB.position = Vector2.all(21);
|
||||||
|
expect(hitboxA.endCounter, 0);
|
||||||
|
expect(hitboxB.endCounter, 0);
|
||||||
|
game.update(0);
|
||||||
|
expect(hitboxA.hasCollisionWith(hitboxB), false);
|
||||||
|
expect(hitboxB.hasCollisionWith(hitboxA), false);
|
||||||
|
expect(hitboxA.collisions.length, 0);
|
||||||
|
expect(hitboxB.collisions.length, 0);
|
||||||
|
game.update(0);
|
||||||
|
expect(hitboxA.endCounter, 1);
|
||||||
|
expect(hitboxB.endCounter, 1);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user