Collision detection between children (#943)

* Enable children of different components to collide

* Re-add import
This commit is contained in:
Lukas Klingsbo
2021-09-15 15:11:00 +02:00
committed by GitHub
parent e3fa9f2a55
commit 55ab428a9e
8 changed files with 101 additions and 21 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -19,6 +19,12 @@ mixin Collidable on Hitbox {
void onCollision(Set<Vector2> intersectionPoints, Collidable other) {}
void onCollisionEnd(Collidable other) {}
@override
void onRemove() {
super.onRemove();
findParent<HasCollidables>()?.collidables.remove(this);
}
}
class ScreenCollidable<T extends FlameGame> extends PositionComponent

View File

@ -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<Collidable> collidables = [];
@override
void prepareComponent(Component component) {
super.prepareComponent(component);
if (component is Collidable) {
collidables.add(component);
}
}
@override
Future<void>? onLoad() {
children.register<Collidable>();
@ -16,6 +29,6 @@ mixin HasCollidables on FlameGame {
}
void handleCollidables() {
collisionDetection(children.query<Collidable>());
collisionDetection(collidables);
}
}

View File

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

View File

@ -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, <Collidable>[blockB, innerBlockB]);
expect(blockB.collisions, <Collidable>[blockA, innerBlockA]);
expect(innerBlockA.collisions, <Collidable>[blockB, innerBlockB]);
expect(innerBlockB.collisions, <Collidable>[blockA, innerBlockA]);
});
},
);
}