mirror of https://github.com/flutter/pinball.git
parent
2d076cceca
commit
6d92eb3f1d
@ -0,0 +1,86 @@
|
|||||||
|
// TODO(alestiago): Remove once the below is merged and updated.
|
||||||
|
// https://github.com/flame-engine/flame/pull/1547
|
||||||
|
|
||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
// ignore_for_file: avoid_renaming_method_parameters
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
|
||||||
|
abstract class ContactCallbacks2 {
|
||||||
|
void beginContact(Object other, Contact contact) {}
|
||||||
|
void endContact(Object other, Contact contact) {}
|
||||||
|
void preSolve(Object other, Contact contact, Manifold oldManifold) {}
|
||||||
|
void postSolve(Object other, Contact contact, ContactImpulse impulse) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Listens to the entire [World] contacts events.
|
||||||
|
///
|
||||||
|
/// It propagates contact events ([beginContact], [endContact], [preSolve],
|
||||||
|
/// [postSolve]) to other [ContactCallbacks]s when a [Body] or at least one of
|
||||||
|
/// its fixtures `userData` is set to a [ContactCallbacks].
|
||||||
|
///
|
||||||
|
/// If the [Body] `userData` is set to a [ContactCallbacks] the contact events
|
||||||
|
/// of this will be called to when any [Body]'s fixture contacts another
|
||||||
|
/// [Fixture].
|
||||||
|
///
|
||||||
|
/// If instead you wish to be more specific and only trigger contact events
|
||||||
|
/// when a specific [Body]'s fixture contacts with another [Fixture] you can
|
||||||
|
/// set the fixture `userData` to a [ContactCallbacks].
|
||||||
|
///
|
||||||
|
/// The described behaviour is a simple out of the box solution to propagate
|
||||||
|
/// contact events. If you wish to implement your own logic you can subclass
|
||||||
|
/// [ContactListener] and provide it to your [Forge2DGame].
|
||||||
|
class WorldContactListener extends ContactListener {
|
||||||
|
void _callback(
|
||||||
|
Contact contact,
|
||||||
|
void Function(ContactCallbacks2 contactCallback, Object other) callback,
|
||||||
|
) {
|
||||||
|
final userDatas = {
|
||||||
|
contact.bodyA.userData,
|
||||||
|
contact.fixtureA.userData,
|
||||||
|
contact.bodyB.userData,
|
||||||
|
contact.fixtureB.userData,
|
||||||
|
}.whereType<Object>();
|
||||||
|
|
||||||
|
for (final contactCallback in userDatas.whereType<ContactCallbacks2>()) {
|
||||||
|
for (final object in userDatas) {
|
||||||
|
if (object != contactCallback) {
|
||||||
|
callback(contactCallback, object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void beginContact(Contact contact) {
|
||||||
|
_callback(
|
||||||
|
contact,
|
||||||
|
(contactCallback, other) => contactCallback.beginContact(other, contact),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void endContact(Contact contact) {
|
||||||
|
_callback(
|
||||||
|
contact,
|
||||||
|
(contactCallback, other) => contactCallback.endContact(other, contact),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void preSolve(Contact contact, Manifold oldManifold) {
|
||||||
|
_callback(
|
||||||
|
contact,
|
||||||
|
(contactCallback, other) =>
|
||||||
|
contactCallback.preSolve(other, contact, oldManifold),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void postSolve(Contact contact, ContactImpulse contactImpulse) {
|
||||||
|
_callback(
|
||||||
|
contact,
|
||||||
|
(contactCallback, other) =>
|
||||||
|
contactCallback.postSolve(other, contact, contactImpulse),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,364 @@
|
|||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
import 'package:test/scaffolding.dart';
|
||||||
|
|
||||||
|
class MockContactCallback extends Mock implements ContactCallbacks2 {}
|
||||||
|
|
||||||
|
class MockContact extends Mock implements Contact {}
|
||||||
|
|
||||||
|
class MockBody extends Mock implements Body {}
|
||||||
|
|
||||||
|
class MockFixture extends Mock implements Fixture {}
|
||||||
|
|
||||||
|
class MockManifold extends Mock implements Manifold {}
|
||||||
|
|
||||||
|
class MockContactImpulse extends Mock implements ContactImpulse {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group(
|
||||||
|
'WorldContactListener',
|
||||||
|
() {
|
||||||
|
late ContactCallbacks2 contactCallback;
|
||||||
|
late Contact contact;
|
||||||
|
late Body bodyA;
|
||||||
|
late Body bodyB;
|
||||||
|
late Fixture fixtureA;
|
||||||
|
late Fixture fixtureB;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
contactCallback = MockContactCallback();
|
||||||
|
contact = MockContact();
|
||||||
|
bodyA = MockBody();
|
||||||
|
bodyB = MockBody();
|
||||||
|
fixtureA = MockFixture();
|
||||||
|
fixtureB = MockFixture();
|
||||||
|
|
||||||
|
when(() => contact.bodyA).thenReturn(bodyA);
|
||||||
|
when(() => contact.bodyB).thenReturn(bodyB);
|
||||||
|
when(() => contact.fixtureA).thenReturn(fixtureA);
|
||||||
|
when(() => contact.fixtureB).thenReturn(fixtureB);
|
||||||
|
});
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
registerFallbackValue(Object());
|
||||||
|
});
|
||||||
|
|
||||||
|
group(
|
||||||
|
'preSolve',
|
||||||
|
() {
|
||||||
|
late Manifold manifold;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
manifold = MockManifold();
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
"doesn't callback if userData are null",
|
||||||
|
() {
|
||||||
|
final contactListener = WorldContactListener();
|
||||||
|
|
||||||
|
when(() => bodyA.userData).thenReturn(contactCallback);
|
||||||
|
when(() => bodyB.userData).thenReturn(null);
|
||||||
|
when(() => fixtureA.userData).thenReturn(null);
|
||||||
|
when(() => fixtureB.userData).thenReturn(null);
|
||||||
|
|
||||||
|
contactListener.preSolve(contact, manifold);
|
||||||
|
|
||||||
|
verifyNever<void>(
|
||||||
|
() => contactCallback.preSolve(any(), contact, manifold),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'callbacks for userData when not null',
|
||||||
|
() {
|
||||||
|
final contactListener = WorldContactListener();
|
||||||
|
|
||||||
|
when(() => bodyA.userData).thenReturn(contactCallback);
|
||||||
|
when(() => bodyB.userData).thenReturn(Object());
|
||||||
|
when(() => fixtureA.userData).thenReturn(Object());
|
||||||
|
when(() => fixtureB.userData).thenReturn(Object());
|
||||||
|
|
||||||
|
contactListener.preSolve(contact, manifold);
|
||||||
|
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.preSolve(
|
||||||
|
bodyB.userData!,
|
||||||
|
contact,
|
||||||
|
manifold,
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.preSolve(
|
||||||
|
fixtureA.userData!,
|
||||||
|
contact,
|
||||||
|
manifold,
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.preSolve(
|
||||||
|
fixtureB.userData!,
|
||||||
|
contact,
|
||||||
|
manifold,
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.preSolve(
|
||||||
|
any(),
|
||||||
|
contact,
|
||||||
|
manifold,
|
||||||
|
),
|
||||||
|
).called(3);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
"doesn't callback itself",
|
||||||
|
() {
|
||||||
|
final contactListener = WorldContactListener();
|
||||||
|
|
||||||
|
when(() => bodyA.userData).thenReturn(contactCallback);
|
||||||
|
when(() => bodyB.userData).thenReturn(contactCallback);
|
||||||
|
when(() => fixtureA.userData).thenReturn(null);
|
||||||
|
when(() => fixtureB.userData).thenReturn(null);
|
||||||
|
|
||||||
|
contactListener.preSolve(contact, manifold);
|
||||||
|
|
||||||
|
verifyNever<void>(
|
||||||
|
() => contactCallback.preSolve(any(), contact, manifold),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'beginContact',
|
||||||
|
() {
|
||||||
|
test(
|
||||||
|
"doesn't callback if userData are null",
|
||||||
|
() {
|
||||||
|
final contactListener = WorldContactListener();
|
||||||
|
|
||||||
|
when(() => bodyA.userData).thenReturn(contactCallback);
|
||||||
|
when(() => bodyB.userData).thenReturn(null);
|
||||||
|
when(() => fixtureA.userData).thenReturn(null);
|
||||||
|
when(() => fixtureB.userData).thenReturn(null);
|
||||||
|
|
||||||
|
contactListener.beginContact(contact);
|
||||||
|
|
||||||
|
verifyNever<void>(
|
||||||
|
() => contactCallback.beginContact(any(), contact),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'callbacks for userData when not null',
|
||||||
|
() {
|
||||||
|
final contactListener = WorldContactListener();
|
||||||
|
|
||||||
|
when(() => bodyA.userData).thenReturn(contactCallback);
|
||||||
|
when(() => bodyB.userData).thenReturn(Object());
|
||||||
|
when(() => fixtureA.userData).thenReturn(Object());
|
||||||
|
when(() => fixtureB.userData).thenReturn(Object());
|
||||||
|
|
||||||
|
contactListener.beginContact(contact);
|
||||||
|
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.beginContact(bodyB.userData!, contact),
|
||||||
|
).called(1);
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.beginContact(fixtureA.userData!, contact),
|
||||||
|
).called(1);
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.beginContact(fixtureB.userData!, contact),
|
||||||
|
).called(1);
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.beginContact(any(), contact),
|
||||||
|
).called(3);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
"doesn't callback itself",
|
||||||
|
() {
|
||||||
|
final contactListener = WorldContactListener();
|
||||||
|
|
||||||
|
when(() => bodyA.userData).thenReturn(contactCallback);
|
||||||
|
when(() => bodyB.userData).thenReturn(contactCallback);
|
||||||
|
when(() => fixtureA.userData).thenReturn(null);
|
||||||
|
when(() => fixtureB.userData).thenReturn(null);
|
||||||
|
|
||||||
|
contactListener.beginContact(contact);
|
||||||
|
|
||||||
|
verifyNever<void>(
|
||||||
|
() => contactCallback.beginContact(any(), contact),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'endContact',
|
||||||
|
() {
|
||||||
|
test(
|
||||||
|
"doesn't callback if userData are null",
|
||||||
|
() {
|
||||||
|
final contactListener = WorldContactListener();
|
||||||
|
|
||||||
|
when(() => bodyA.userData).thenReturn(contactCallback);
|
||||||
|
when(() => bodyB.userData).thenReturn(null);
|
||||||
|
when(() => fixtureA.userData).thenReturn(null);
|
||||||
|
when(() => fixtureB.userData).thenReturn(null);
|
||||||
|
|
||||||
|
contactListener.endContact(contact);
|
||||||
|
|
||||||
|
verifyNever<void>(
|
||||||
|
() => contactCallback.endContact(any(), contact),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'callbacks for userData when not null',
|
||||||
|
() {
|
||||||
|
final contactListener = WorldContactListener();
|
||||||
|
|
||||||
|
when(() => bodyA.userData).thenReturn(contactCallback);
|
||||||
|
when(() => bodyB.userData).thenReturn(Object());
|
||||||
|
when(() => fixtureA.userData).thenReturn(Object());
|
||||||
|
when(() => fixtureB.userData).thenReturn(Object());
|
||||||
|
|
||||||
|
contactListener.endContact(contact);
|
||||||
|
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.endContact(bodyB.userData!, contact),
|
||||||
|
).called(1);
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.endContact(fixtureA.userData!, contact),
|
||||||
|
).called(1);
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.endContact(fixtureB.userData!, contact),
|
||||||
|
).called(1);
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.endContact(any(), contact),
|
||||||
|
).called(3);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
"doesn't callback itself",
|
||||||
|
() {
|
||||||
|
final contactListener = WorldContactListener();
|
||||||
|
|
||||||
|
when(() => bodyA.userData).thenReturn(contactCallback);
|
||||||
|
when(() => bodyB.userData).thenReturn(contactCallback);
|
||||||
|
when(() => fixtureA.userData).thenReturn(null);
|
||||||
|
when(() => fixtureB.userData).thenReturn(null);
|
||||||
|
|
||||||
|
contactListener.endContact(contact);
|
||||||
|
|
||||||
|
verifyNever<void>(
|
||||||
|
() => contactCallback.endContact(any(), contact),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'postSolve',
|
||||||
|
() {
|
||||||
|
late ContactImpulse contactImpulse;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
contactImpulse = MockContactImpulse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
"doesn't callback if userData are null",
|
||||||
|
() {
|
||||||
|
final contactListener = WorldContactListener();
|
||||||
|
|
||||||
|
when(() => bodyA.userData).thenReturn(contactCallback);
|
||||||
|
when(() => bodyB.userData).thenReturn(null);
|
||||||
|
when(() => fixtureA.userData).thenReturn(null);
|
||||||
|
when(() => fixtureB.userData).thenReturn(null);
|
||||||
|
|
||||||
|
contactListener.postSolve(contact, contactImpulse);
|
||||||
|
|
||||||
|
verifyNever<void>(
|
||||||
|
() => contactCallback.postSolve(any(), contact, contactImpulse),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'callbacks for userData when not null',
|
||||||
|
() {
|
||||||
|
final contactListener = WorldContactListener();
|
||||||
|
|
||||||
|
when(() => bodyA.userData).thenReturn(contactCallback);
|
||||||
|
when(() => bodyB.userData).thenReturn(Object());
|
||||||
|
when(() => fixtureA.userData).thenReturn(Object());
|
||||||
|
when(() => fixtureB.userData).thenReturn(Object());
|
||||||
|
|
||||||
|
contactListener.postSolve(contact, contactImpulse);
|
||||||
|
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.postSolve(
|
||||||
|
bodyB.userData!,
|
||||||
|
contact,
|
||||||
|
contactImpulse,
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.postSolve(
|
||||||
|
fixtureA.userData!,
|
||||||
|
contact,
|
||||||
|
contactImpulse,
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.postSolve(
|
||||||
|
fixtureB.userData!,
|
||||||
|
contact,
|
||||||
|
contactImpulse,
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
|
verify<void>(
|
||||||
|
() => contactCallback.postSolve(
|
||||||
|
any(),
|
||||||
|
contact,
|
||||||
|
contactImpulse,
|
||||||
|
),
|
||||||
|
).called(3);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
"doesn't callback itself",
|
||||||
|
() {
|
||||||
|
final contactListener = WorldContactListener();
|
||||||
|
|
||||||
|
when(() => bodyA.userData).thenReturn(contactCallback);
|
||||||
|
when(() => bodyB.userData).thenReturn(contactCallback);
|
||||||
|
when(() => fixtureA.userData).thenReturn(null);
|
||||||
|
when(() => fixtureB.userData).thenReturn(null);
|
||||||
|
|
||||||
|
contactListener.postSolve(contact, contactImpulse);
|
||||||
|
|
||||||
|
verifyNever<void>(
|
||||||
|
() => contactCallback.postSolve(any(), contact, contactImpulse),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in new issue