mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-03 04:18:25 +08:00
Collision detection between children (#943)
* Enable children of different components to collide * Re-add import
This commit is contained in:
@ -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
|
about at the moment but that might later come back in to view so they are not completely removed
|
||||||
from the game.
|
from the game.
|
||||||
|
|
||||||
These are just examples of how you could use these types, there will be a lot more usecases for them
|
These are just examples of how you could use these types, there will be a lot more use cases for
|
||||||
so don't doubt to use them even if your use-case isn't listed here.
|
them so don't doubt to use them even if your use case isn't listed here.
|
||||||
|
|
||||||
### HasCollidables
|
### HasCollidables
|
||||||
If you want to use this collision detection in your game you have to add the `HasCollidables` mixin
|
If you want to use this collision detection in your game you have to add the `HasCollidables` mixin
|
||||||
|
|||||||
@ -213,6 +213,35 @@ class CollidableSnowman extends MyCollidable {
|
|||||||
addHitbox(top);
|
addHitbox(top);
|
||||||
addHitbox(middle);
|
addHitbox(middle);
|
||||||
addHitbox(bottom);
|
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);
|
add(snowman);
|
||||||
var totalAdded = 1;
|
var totalAdded = 1;
|
||||||
while (totalAdded < 20) {
|
while (totalAdded < 20) {
|
||||||
lastToAdd = createRandomCollidable(lastToAdd, screenCollidable);
|
lastToAdd = nextRandomCollidable(lastToAdd, screenCollidable);
|
||||||
final lastBottomRight =
|
final lastBottomRight =
|
||||||
lastToAdd.toAbsoluteRect().bottomRight.toVector2();
|
lastToAdd.toAbsoluteRect().bottomRight.toVector2();
|
||||||
if (lastBottomRight.x < size.x && lastBottomRight.y < size.y) {
|
if (lastBottomRight.x < size.x && lastBottomRight.y < size.y) {
|
||||||
@ -254,9 +283,9 @@ class MultipleShapes extends FlameGame
|
|||||||
final _rng = Random();
|
final _rng = Random();
|
||||||
final _distance = Vector2(100, 0);
|
final _distance = Vector2(100, 0);
|
||||||
|
|
||||||
MyCollidable createRandomCollidable(
|
MyCollidable nextRandomCollidable(
|
||||||
MyCollidable lastCollidable,
|
MyCollidable lastCollidable,
|
||||||
ScreenCollidable screen,
|
ScreenCollidable screenCollidable,
|
||||||
) {
|
) {
|
||||||
final collidableSize = Vector2.all(50) + Vector2.random(_rng) * 100;
|
final collidableSize = Vector2.all(50) + Vector2.random(_rng) * 100;
|
||||||
final isXOverflow = lastCollidable.position.x +
|
final isXOverflow = lastCollidable.position.x +
|
||||||
@ -270,19 +299,13 @@ class MultipleShapes extends FlameGame
|
|||||||
..x += collidableSize.x / 2;
|
..x += collidableSize.x / 2;
|
||||||
}
|
}
|
||||||
final velocity = (Vector2.random(_rng) - Vector2.random(_rng)) * 400;
|
final velocity = (Vector2.random(_rng) - Vector2.random(_rng)) * 400;
|
||||||
final rotationSpeed = 0.5 - _rng.nextDouble();
|
return randomCollidable(
|
||||||
final shapeType = Shapes.values[_rng.nextInt(Shapes.values.length)];
|
position,
|
||||||
switch (shapeType) {
|
collidableSize,
|
||||||
case Shapes.circle:
|
velocity,
|
||||||
return CollidableCircle(position, collidableSize, velocity, screen)
|
screenCollidable,
|
||||||
..rotationSpeed = rotationSpeed;
|
rng: _rng,
|
||||||
case Shapes.rectangle:
|
);
|
||||||
return CollidableRectangle(position, collidableSize, velocity, screen)
|
|
||||||
..rotationSpeed = rotationSpeed;
|
|
||||||
case Shapes.polygon:
|
|
||||||
return CollidablePolygon(position, collidableSize, velocity, screen)
|
|
||||||
..rotationSpeed = rotationSpeed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -56,6 +56,7 @@
|
|||||||
- `HasGameRef` can now operate independently from `Game`
|
- `HasGameRef` can now operate independently from `Game`
|
||||||
- `initialDelay` and `peakDelay` for effects to handle time before and after an effect
|
- `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
|
- `component.onMount` now runs every time a component gets a new parent
|
||||||
|
- Add collision detection between child components
|
||||||
|
|
||||||
## [1.0.0-releasecandidate.13]
|
## [1.0.0-releasecandidate.13]
|
||||||
- Fix camera not ending up in the correct position on long jumps
|
- Fix camera not ending up in the correct position on long jumps
|
||||||
|
|||||||
@ -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
|
@mustCallSuper
|
||||||
ComponentSet createComponentSet() {
|
ComponentSet createComponentSet() {
|
||||||
return ComponentSet.createDefault(this);
|
return ComponentSet.createDefault(this);
|
||||||
|
|||||||
@ -19,6 +19,12 @@ mixin Collidable on Hitbox {
|
|||||||
|
|
||||||
void onCollision(Set<Vector2> intersectionPoints, Collidable other) {}
|
void onCollision(Set<Vector2> intersectionPoints, Collidable other) {}
|
||||||
void onCollisionEnd(Collidable other) {}
|
void onCollisionEnd(Collidable other) {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onRemove() {
|
||||||
|
super.onRemove();
|
||||||
|
findParent<HasCollidables>()?.collidables.remove(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScreenCollidable<T extends FlameGame> extends PositionComponent
|
class ScreenCollidable<T extends FlameGame> extends PositionComponent
|
||||||
|
|||||||
@ -1,8 +1,21 @@
|
|||||||
|
import '../../../components.dart';
|
||||||
import '../../../game.dart';
|
import '../../../game.dart';
|
||||||
import '../../components/mixins/collidable.dart';
|
import '../../components/mixins/collidable.dart';
|
||||||
import '../../geometry/collision_detection.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 {
|
mixin HasCollidables on FlameGame {
|
||||||
|
final List<Collidable> collidables = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void prepareComponent(Component component) {
|
||||||
|
super.prepareComponent(component);
|
||||||
|
if (component is Collidable) {
|
||||||
|
collidables.add(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void>? onLoad() {
|
Future<void>? onLoad() {
|
||||||
children.register<Collidable>();
|
children.register<Collidable>();
|
||||||
@ -16,6 +29,6 @@ mixin HasCollidables on FlameGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handleCollidables() {
|
void handleCollidables() {
|
||||||
collisionDetection(children.query<Collidable>());
|
collisionDetection(collidables);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,14 +2,13 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import '../../components.dart';
|
|
||||||
import '../../extensions.dart';
|
|
||||||
import '../components/component.dart';
|
import '../components/component.dart';
|
||||||
import '../components/mixins/collidable.dart';
|
import '../components/mixins/collidable.dart';
|
||||||
import '../components/mixins/draggable.dart';
|
import '../components/mixins/draggable.dart';
|
||||||
import '../components/mixins/has_collidables.dart';
|
import '../components/mixins/has_collidables.dart';
|
||||||
import '../components/mixins/hoverable.dart';
|
import '../components/mixins/hoverable.dart';
|
||||||
import '../components/mixins/tappable.dart';
|
import '../components/mixins/tappable.dart';
|
||||||
|
import '../extensions/vector2.dart';
|
||||||
import 'camera/camera.dart';
|
import 'camera/camera.dart';
|
||||||
import 'camera/camera_wrapper.dart';
|
import 'camera/camera_wrapper.dart';
|
||||||
import 'mixins/game.dart';
|
import 'mixins/game.dart';
|
||||||
|
|||||||
@ -294,6 +294,38 @@ void main() {
|
|||||||
game.update(0);
|
game.update(0);
|
||||||
expect(blockA.containsPoint(Vector2.all(11)), true);
|
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]);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user