mirror of https://github.com/flutter/pinball.git
feat: Blueprints as a method of grouping game components (#62)
* feat: Blueprints as a method of grouping game components * fix: lint * feat: pr suggestions * feat: pr suggestions * fix: test from mergepull/69/head
parent
74e448cbf9
commit
1af0c47328
@ -0,0 +1,87 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
const _attachedErrorMessage = "Can't add to attached Blueprints";
|
||||||
|
|
||||||
|
// TODO(erickzanardo): Keeping this inside our code base
|
||||||
|
// so we can experiment with the idea, but this is a
|
||||||
|
// potential upstream change on Flame.
|
||||||
|
|
||||||
|
/// A [Blueprint] is a virtual way of grouping [Component]s
|
||||||
|
/// that are related, but they need to be added directly on
|
||||||
|
/// the [FlameGame] level.
|
||||||
|
abstract class Blueprint {
|
||||||
|
final List<Component> _components = [];
|
||||||
|
bool _isAttached = false;
|
||||||
|
|
||||||
|
/// Called before the the [Component]s managed
|
||||||
|
/// by this blueprint is added to the [FlameGame]
|
||||||
|
void build();
|
||||||
|
|
||||||
|
/// Attach the [Component]s built on [build] to the [game]
|
||||||
|
/// instance
|
||||||
|
@mustCallSuper
|
||||||
|
Future<void> attach(FlameGame game) async {
|
||||||
|
build();
|
||||||
|
await game.addAll(_components);
|
||||||
|
_isAttached = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a list of [Component]s to this blueprint.
|
||||||
|
void addAll(List<Component> components) {
|
||||||
|
assert(!_isAttached, _attachedErrorMessage);
|
||||||
|
_components.addAll(components);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a single [Component] to this blueprint.
|
||||||
|
void add(Component component) {
|
||||||
|
assert(!_isAttached, _attachedErrorMessage);
|
||||||
|
_components.add(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a copy of the components built by this blueprint
|
||||||
|
List<Component> get components => List.unmodifiable(_components);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Blueprint] that provides additional
|
||||||
|
/// structures specific to flame_forge2d
|
||||||
|
abstract class Forge2DBlueprint extends Blueprint {
|
||||||
|
final List<ContactCallback> _callbacks = [];
|
||||||
|
|
||||||
|
/// Adds a single [ContactCallback] to this blueprint
|
||||||
|
void addContactCallback(ContactCallback callback) {
|
||||||
|
assert(!_isAttached, _attachedErrorMessage);
|
||||||
|
_callbacks.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a collection of [ContactCallback]s to this blueprint
|
||||||
|
void addAllContactCallback(List<ContactCallback> callbacks) {
|
||||||
|
assert(!_isAttached, _attachedErrorMessage);
|
||||||
|
_callbacks.addAll(callbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> attach(FlameGame game) async {
|
||||||
|
await super.attach(game);
|
||||||
|
|
||||||
|
assert(game is Forge2DGame, 'Forge2DBlueprint used outside a Forge2DGame');
|
||||||
|
|
||||||
|
for (final callback in _callbacks) {
|
||||||
|
(game as Forge2DGame).addContactCallback(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a copy of the callbacks built by this blueprint
|
||||||
|
List<ContactCallback> get callbacks => List.unmodifiable(_callbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds helper methods regardin [Blueprint]s to [FlameGame]
|
||||||
|
extension FlameGameBlueprint on FlameGame {
|
||||||
|
/// Shortcut to attach a [Blueprint] instance to this game
|
||||||
|
/// equivalent to `MyBluepinrt().attach(game)`
|
||||||
|
Future<void> addFromBlueprint(Blueprint blueprint) async {
|
||||||
|
await blueprint.attach(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/flame/blueprint.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
|
||||||
|
import '../helpers/helpers.dart';
|
||||||
|
|
||||||
|
class MyBlueprint extends Blueprint {
|
||||||
|
@override
|
||||||
|
void build() {
|
||||||
|
add(Component());
|
||||||
|
addAll([Component(), Component()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyForge2dBlueprint extends Forge2DBlueprint {
|
||||||
|
@override
|
||||||
|
void build() {
|
||||||
|
addContactCallback(MockContactCallback());
|
||||||
|
addAllContactCallback([MockContactCallback(), MockContactCallback()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Blueprint', () {
|
||||||
|
test('components can be added to it', () {
|
||||||
|
final blueprint = MyBlueprint()..build();
|
||||||
|
|
||||||
|
expect(blueprint.components.length, equals(3));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('adds the components to a game on attach', () {
|
||||||
|
final mockGame = MockPinballGame();
|
||||||
|
when(() => mockGame.addAll(any())).thenAnswer((_) async {});
|
||||||
|
MyBlueprint().attach(mockGame);
|
||||||
|
|
||||||
|
verify(() => mockGame.addAll(any())).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'throws assertion error when adding to an already attached blueprint',
|
||||||
|
() async {
|
||||||
|
final mockGame = MockPinballGame();
|
||||||
|
when(() => mockGame.addAll(any())).thenAnswer((_) async {});
|
||||||
|
final blueprint = MyBlueprint();
|
||||||
|
await blueprint.attach(mockGame);
|
||||||
|
|
||||||
|
expect(() => blueprint.add(Component()), throwsAssertionError);
|
||||||
|
expect(() => blueprint.addAll([Component()]), throwsAssertionError);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Forge2DBlueprint', () {
|
||||||
|
setUpAll(() {
|
||||||
|
registerFallbackValue(SpaceshipHoleBallContactCallback());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('callbacks can be added to it', () {
|
||||||
|
final blueprint = MyForge2dBlueprint()..build();
|
||||||
|
|
||||||
|
expect(blueprint.callbacks.length, equals(3));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('adds the callbacks to a game on attach', () async {
|
||||||
|
final mockGame = MockPinballGame();
|
||||||
|
when(() => mockGame.addAll(any())).thenAnswer((_) async {});
|
||||||
|
when(() => mockGame.addContactCallback(any())).thenAnswer((_) async {});
|
||||||
|
await MyForge2dBlueprint().attach(mockGame);
|
||||||
|
|
||||||
|
verify(() => mockGame.addContactCallback(any())).called(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'throws assertion error when adding to an already attached blueprint',
|
||||||
|
() async {
|
||||||
|
final mockGame = MockPinballGame();
|
||||||
|
when(() => mockGame.addAll(any())).thenAnswer((_) async {});
|
||||||
|
when(() => mockGame.addContactCallback(any())).thenAnswer((_) async {});
|
||||||
|
final blueprint = MyForge2dBlueprint();
|
||||||
|
await blueprint.attach(mockGame);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => blueprint.addContactCallback(MockContactCallback()),
|
||||||
|
throwsAssertionError,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
() => blueprint.addAllContactCallback([MockContactCallback()]),
|
||||||
|
throwsAssertionError,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('throws assertion error when used on a non Forge2dGame', () {
|
||||||
|
expect(
|
||||||
|
() => MyForge2dBlueprint().attach(FlameGame()),
|
||||||
|
throwsAssertionError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in new issue