Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Change HasCollisionDetection to be on Component #2404

Merged
merged 10 commits into from
Mar 15, 2023
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:flame/collisions.dart';
import 'package:flame/game.dart';
import 'package:flame/components.dart';

/// Keeps track of all the [ShapeHitbox]s in the component tree and initiates
/// collision detection every tick.
mixin HasCollisionDetection<B extends Broadphase<ShapeHitbox>> on FlameGame {
mixin HasCollisionDetection<B extends Broadphase<ShapeHitbox>> on Component {
CollisionDetection<ShapeHitbox, B> _collisionDetection =
StandardCollisionDetection();
CollisionDetection<ShapeHitbox, B> get collisionDetection =>
Expand All @@ -27,7 +27,7 @@ mixin HasCollisionDetection<B extends Broadphase<ShapeHitbox>> on FlameGame {
/// Do note that [collisionDetection] has to be initialized before the game
/// starts the update loop for the collision detection to work.
mixin HasGenericCollisionDetection<T extends Hitbox<T>, B extends Broadphase<T>>
on FlameGame {
on Component {
CollisionDetection<T, B>? _collisionDetection;
CollisionDetection<T, B> get collisionDetection => _collisionDetection!;

Expand Down
6 changes: 3 additions & 3 deletions packages/flame/lib/src/collisions/hitboxes/shape_hitbox.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ mixin ShapeHitbox on ShapeComponent implements Hitbox<ShapeHitbox> {

// This should be placed after the hitbox parent listener
// since the correct hitbox size is required by the QuadTree.
final parentGame = findParent<FlameGame>();
if (parentGame is HasCollisionDetection) {
_collisionDetection = parentGame.collisionDetection;
final parent = findParent<HasCollisionDetection>();
spydon marked this conversation as resolved.
Show resolved Hide resolved
if (parent is HasCollisionDetection) {
_collisionDetection = parent.collisionDetection;
_collisionDetection?.add(this);
}
}
Expand Down
7 changes: 5 additions & 2 deletions packages/flame/lib/src/components/core/component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,11 @@ class Component {

/// Returns the closest parent further up the hierarchy that satisfies type=T,
/// or null if no such parent can be found.
T? findParent<T extends Component>() {
return ancestors().whereType<T>().firstOrNull;
///
/// If [includeSelf] is set to true (default is false) then the component
/// which the call is made for is also included in the search.
T? findParent<T extends Component>({bool includeSelf = false}) {
return ancestors(includeSelf: includeSelf).whereType<T>().firstOrNull;
}

/// Returns the first child that matches the given type [T], or null if there
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:math';

import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:test/test.dart';

Expand Down Expand Up @@ -41,7 +42,8 @@ class _TestBlock extends PositionComponent with CollisionCallbacks {
void main() {
group('Benchmark collision detection', () {
runCollisionTestRegistry({
'collidable callbacks are called': (game) async {
'collidable callbacks are called': (collisionSystem) async {
final game = collisionSystem as FlameGame;
final rng = Random(0);
final blocks = List.generate(
100,
Expand Down
45 changes: 44 additions & 1 deletion packages/flame/test/collisions/collision_callback_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/experimental.dart';
import 'package:flame/game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:test/test.dart';

Expand Down Expand Up @@ -463,7 +465,8 @@ void main() {
expect(player.endCounter, 0);
},
// Reproduced #1478
'collision callbacks with changed game size': (game) async {
'collision callbacks with changed game size': (collisionSystem) async {
final game = collisionSystem as FlameGame;
final block = TestBlock(Vector2.all(20), Vector2.all(10))
..anchor = Anchor.center;
await game.ensureAddAll([ScreenHitbox(), block]);
Expand Down Expand Up @@ -512,6 +515,46 @@ void main() {
expect(outerBlock.onCollisionCounter, 1);
expect(outerBlock.endCounter, 1);
},
'collision callbacks for nested World': (outerCollisionSystem) async {
final game = outerCollisionSystem as FlameGame;
final world1 = CollisionDetectionWorld();
final world2 = CollisionDetectionWorld();
final camera1 = CameraComponent(world: world1);
final camera2 = CameraComponent(world: world2);
await game.ensureAddAll([world1, world2, camera1, camera2]);
final block1 = TestBlock(Vector2(1, 1), Vector2.all(2))
..anchor = Anchor.center;
final block2 = TestBlock(Vector2(1, -1), Vector2.all(2))
..anchor = Anchor.center;
final block3 = TestBlock(Vector2(-1, 1), Vector2.all(2))
..anchor = Anchor.center;
final block4 = TestBlock(Vector2(-1, -1), Vector2.all(2))
..anchor = Anchor.center;
await world1.ensureAddAll([block1, block2]);
await world2.ensureAddAll([block3, block4]);

game.update(0);
for (final block in [block1, block2, block3, block4]) {
expect(block.startCounter, 1);
expect(block.onCollisionCounter, 1);
expect(block.endCounter, 0);
}
expect(block1.collidedWithExactly([block2]), isTrue);
expect(block2.collidedWithExactly([block1]), isTrue);
expect(block3.collidedWithExactly([block4]), isTrue);
expect(block4.collidedWithExactly([block3]), isTrue);

for (final block in [block1, block2, block3, block4]) {
block.position.scale(3);
}

game.update(0);
for (final block in [block1, block2, block3, block4]) {
expect(block.startCounter, 1);
expect(block.onCollisionCounter, 1);
expect(block.endCounter, 1);
}
},
});

group('ComponentTypeCheck(only supported in the QuadTree)', () {
Expand Down
Loading