From 55ab428a9ee6ff1bd1b74eb9183d14ccd512f8da Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 15 Sep 2021 15:11:00 +0200 Subject: [PATCH] Collision detection between children (#943) * Enable children of different components to collide * Re-add import --- doc/collision_detection.md | 4 +- .../collision_detection/multiple_shapes.dart | 55 +++++++++++++------ packages/flame/CHANGELOG.md | 1 + .../flame/lib/src/components/component.dart | 6 ++ .../lib/src/components/mixins/collidable.dart | 6 ++ .../components/mixins/has_collidables.dart | 15 ++++- packages/flame/lib/src/game/flame_game.dart | 3 +- .../test/components/collidable_type_test.dart | 32 +++++++++++ 8 files changed, 101 insertions(+), 21 deletions(-) diff --git a/doc/collision_detection.md b/doc/collision_detection.md index 007223e23..1d8c8d309 100644 --- a/doc/collision_detection.md +++ b/doc/collision_detection.md @@ -129,8 +129,8 @@ This could be used for example if you have components outside of the screen that about at the moment but that might later come back in to view so they are not completely removed from the game. -These are just examples of how you could use these types, there will be a lot more usecases for them -so don't doubt to use them even if your use-case isn't listed here. +These are just examples of how you could use these types, there will be a lot more use cases for +them so don't doubt to use them even if your use case isn't listed here. ### HasCollidables If you want to use this collision detection in your game you have to add the `HasCollidables` mixin diff --git a/examples/lib/stories/collision_detection/multiple_shapes.dart b/examples/lib/stories/collision_detection/multiple_shapes.dart index 8fd9821ea..9b11b22a7 100644 --- a/examples/lib/stories/collision_detection/multiple_shapes.dart +++ b/examples/lib/stories/collision_detection/multiple_shapes.dart @@ -213,6 +213,35 @@ class CollidableSnowman extends MyCollidable { addHitbox(top); addHitbox(middle); addHitbox(bottom); + add(randomCollidable( + Vector2(size.x / 2, size.y * 0.75), + size / 4, + Vector2.zero(), + screenCollidable, + )); + } +} + +MyCollidable randomCollidable( + Vector2 position, + Vector2 size, + Vector2 velocity, + ScreenCollidable screenCollidable, { + Random? rng, +}) { + final _rng = rng ?? Random(); + final rotationSpeed = 0.5 - _rng.nextDouble(); + final shapeType = Shapes.values[_rng.nextInt(Shapes.values.length)]; + switch (shapeType) { + case Shapes.circle: + return CollidableCircle(position, size, velocity, screenCollidable) + ..rotationSpeed = rotationSpeed; + case Shapes.rectangle: + return CollidableRectangle(position, size, velocity, screenCollidable) + ..rotationSpeed = rotationSpeed; + case Shapes.polygon: + return CollidablePolygon(position, size, velocity, screenCollidable) + ..rotationSpeed = rotationSpeed; } } @@ -239,7 +268,7 @@ class MultipleShapes extends FlameGame add(snowman); var totalAdded = 1; while (totalAdded < 20) { - lastToAdd = createRandomCollidable(lastToAdd, screenCollidable); + lastToAdd = nextRandomCollidable(lastToAdd, screenCollidable); final lastBottomRight = lastToAdd.toAbsoluteRect().bottomRight.toVector2(); if (lastBottomRight.x < size.x && lastBottomRight.y < size.y) { @@ -254,9 +283,9 @@ class MultipleShapes extends FlameGame final _rng = Random(); final _distance = Vector2(100, 0); - MyCollidable createRandomCollidable( + MyCollidable nextRandomCollidable( MyCollidable lastCollidable, - ScreenCollidable screen, + ScreenCollidable screenCollidable, ) { final collidableSize = Vector2.all(50) + Vector2.random(_rng) * 100; final isXOverflow = lastCollidable.position.x + @@ -270,19 +299,13 @@ class MultipleShapes extends FlameGame ..x += collidableSize.x / 2; } final velocity = (Vector2.random(_rng) - Vector2.random(_rng)) * 400; - final rotationSpeed = 0.5 - _rng.nextDouble(); - final shapeType = Shapes.values[_rng.nextInt(Shapes.values.length)]; - switch (shapeType) { - case Shapes.circle: - return CollidableCircle(position, collidableSize, velocity, screen) - ..rotationSpeed = rotationSpeed; - case Shapes.rectangle: - return CollidableRectangle(position, collidableSize, velocity, screen) - ..rotationSpeed = rotationSpeed; - case Shapes.polygon: - return CollidablePolygon(position, collidableSize, velocity, screen) - ..rotationSpeed = rotationSpeed; - } + return randomCollidable( + position, + collidableSize, + velocity, + screenCollidable, + rng: _rng, + ); } @override diff --git a/packages/flame/CHANGELOG.md b/packages/flame/CHANGELOG.md index ef9ebb8d7..b28909075 100644 --- a/packages/flame/CHANGELOG.md +++ b/packages/flame/CHANGELOG.md @@ -56,6 +56,7 @@ - `HasGameRef` can now operate independently from `Game` - `initialDelay` and `peakDelay` for effects to handle time before and after an effect - `component.onMount` now runs every time a component gets a new parent + - Add collision detection between child components ## [1.0.0-releasecandidate.13] - Fix camera not ending up in the correct position on long jumps diff --git a/packages/flame/lib/src/components/component.dart b/packages/flame/lib/src/components/component.dart index f82aafcb8..6c06d5b02 100644 --- a/packages/flame/lib/src/components/component.dart +++ b/packages/flame/lib/src/components/component.dart @@ -325,6 +325,12 @@ class Component with Loadable { } } + /// This method sets up the `OrderedSet` instance used by this component to + /// handle its children, + /// This is set up before any lifecycle methods happen. + /// + /// You can return a specific sub-class of OrderedSet, like + /// `QueryableOrderedSet` for example. @mustCallSuper ComponentSet createComponentSet() { return ComponentSet.createDefault(this); diff --git a/packages/flame/lib/src/components/mixins/collidable.dart b/packages/flame/lib/src/components/mixins/collidable.dart index 70dfd4d4f..74c2d7627 100644 --- a/packages/flame/lib/src/components/mixins/collidable.dart +++ b/packages/flame/lib/src/components/mixins/collidable.dart @@ -19,6 +19,12 @@ mixin Collidable on Hitbox { void onCollision(Set intersectionPoints, Collidable other) {} void onCollisionEnd(Collidable other) {} + + @override + void onRemove() { + super.onRemove(); + findParent()?.collidables.remove(this); + } } class ScreenCollidable extends PositionComponent diff --git a/packages/flame/lib/src/components/mixins/has_collidables.dart b/packages/flame/lib/src/components/mixins/has_collidables.dart index 80f1d20d6..07ab48e77 100644 --- a/packages/flame/lib/src/components/mixins/has_collidables.dart +++ b/packages/flame/lib/src/components/mixins/has_collidables.dart @@ -1,8 +1,21 @@ +import '../../../components.dart'; import '../../../game.dart'; import '../../components/mixins/collidable.dart'; import '../../geometry/collision_detection.dart'; +/// Keeps track of all the [Collidable]s in the component tree and initiates +/// collision detection every tick. mixin HasCollidables on FlameGame { + final List collidables = []; + + @override + void prepareComponent(Component component) { + super.prepareComponent(component); + if (component is Collidable) { + collidables.add(component); + } + } + @override Future? onLoad() { children.register(); @@ -16,6 +29,6 @@ mixin HasCollidables on FlameGame { } void handleCollidables() { - collisionDetection(children.query()); + collisionDetection(collidables); } } diff --git a/packages/flame/lib/src/game/flame_game.dart b/packages/flame/lib/src/game/flame_game.dart index 45d6fadf3..d1092eaa5 100644 --- a/packages/flame/lib/src/game/flame_game.dart +++ b/packages/flame/lib/src/game/flame_game.dart @@ -2,14 +2,13 @@ import 'dart:ui'; import 'package:meta/meta.dart'; -import '../../components.dart'; -import '../../extensions.dart'; import '../components/component.dart'; import '../components/mixins/collidable.dart'; import '../components/mixins/draggable.dart'; import '../components/mixins/has_collidables.dart'; import '../components/mixins/hoverable.dart'; import '../components/mixins/tappable.dart'; +import '../extensions/vector2.dart'; import 'camera/camera.dart'; import 'camera/camera_wrapper.dart'; import 'mixins/game.dart'; diff --git a/packages/flame/test/components/collidable_type_test.dart b/packages/flame/test/components/collidable_type_test.dart index 8606ee387..7c0e03990 100644 --- a/packages/flame/test/components/collidable_type_test.dart +++ b/packages/flame/test/components/collidable_type_test.dart @@ -294,6 +294,38 @@ void main() { game.update(0); expect(blockA.containsPoint(Vector2.all(11)), true); }); + + test('Detects collision on child components', () async { + final blockA = TestBlock( + Vector2.zero(), + Vector2.all(10), + CollidableType.active, + ); + final innerBlockA = TestBlock( + blockA.size / 4, + blockA.size / 2, + CollidableType.active, + ); + blockA.add(innerBlockA); + + final blockB = TestBlock( + Vector2.all(5), + Vector2.all(10), + CollidableType.active, + ); + final innerBlockB = TestBlock( + blockA.size / 4, + blockA.size / 2, + CollidableType.active, + ); + blockB.add(innerBlockB); + + await gameWithCollidables([blockA, blockB]); + expect(blockA.collisions, [blockB, innerBlockB]); + expect(blockB.collisions, [blockA, innerBlockA]); + expect(innerBlockA.collisions, [blockB, innerBlockB]); + expect(innerBlockB.collisions, [blockA, innerBlockA]); + }); }, ); }