feat: defined AlienBumper behaviours

pull/234/head
alestiago 3 years ago
parent 318da8c23a
commit 1420f33049

@ -0,0 +1,36 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
class AlienBumperContactBehavior extends Component with ContactCallbacks {
@override
Future<void> onLoad() async {
await super.onLoad();
// TODO(alestiago): Refactor once the following is merged:
// https://github.com/flame-engine/flame/pull/1566
final parent = this.parent;
if (parent is! AlienBumper) return;
final userData = parent.body.userData;
if (userData is ContactCallbacksNotifer) {
userData.addCallback(this);
} else {
parent.body.userData = this;
}
}
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
// TODO(alestiago): Refactor once the following is merged:
// https://github.com/flame-engine/flame/pull/1566
final parent = this.parent;
if (parent is! AlienBumper) return;
parent.state = AlienBumperState.inactive;
}
}

@ -0,0 +1,51 @@
import 'package:flame/components.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template alien_bumper_sprite_behavior}
///
/// {@endtemplate}
class AlienBumperSpriteBehavior extends TimerComponent {
/// {@macro alien_bumper_sprite_behavior}
AlienBumperSpriteBehavior()
: super(
period: 0.05,
removeOnFinish: false,
);
void _onNewState(AlienBumperState state) {
switch (state) {
case AlienBumperState.active:
timer.stop();
break;
case AlienBumperState.inactive:
timer
..reset()
..start();
break;
}
}
@override
Future<void> onLoad() async {
await super.onLoad();
// TODO(alestiago): Refactor once the following is merged:
// https://github.com/flame-engine/flame/pull/1566
final parent = this.parent;
if (parent is! AlienBumper) return;
timer.stop();
parent.stream.listen(_onNewState);
}
@override
void onTick() {
super.onTick();
// TODO(alestiago): Refactor once the following is merged:
// https://github.com/flame-engine/flame/pull/1566
final parent = this.parent;
if (parent is! AlienBumper) return;
parent.state = AlienBumperState.active;
}
}

@ -0,0 +1,2 @@
export 'alien_bumper_contact_behavior.dart';
export 'alien_bumper_sprite_behavior.dart';

@ -1,7 +1,7 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/components/alien_zone/alien_bumper/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
@ -20,28 +20,17 @@ class AlienZone extends Blueprint {
AlienBumper.a(
children: [
ScoringBehaviour(points: 20),
AlienBumperContactBehavior(),
AlienBumperSpriteBehavior(),
],
)..initialPosition = Vector2(-32.52, -9.1),
AlienBumper.b(
children: [
ScoringBehaviour(points: 20),
AlienBumperContactBehavior(),
AlienBumperSpriteBehavior(),
],
)..initialPosition = Vector2(-22.89, -17.35),
],
);
}
/// Listens when a [Ball] bounces against an [AlienBumper].
// TODO(alestiago): Add Bumper animation behaviour.
// @visibleForTesting
// class AlienBumperBallContactCallback
// extends ContactCallback<AlienBumper, Ball> {
// @override
// void begin(
// AlienBumper alienBumper,
// Ball _,
// Contact __,
// ) {
// alienBumper.animate();
// }
// }

@ -1,4 +1,4 @@
export 'alien_zone.dart';
export 'alien_zone/alien_zone.dart';
export 'board.dart';
export 'camera_controller.dart';
export 'controlled_ball.dart';

@ -10,7 +10,7 @@ import 'package:pinball_flame/pinball_flame.dart';
///
/// {@endtemplate}
class ScoringBehaviour extends Component
with ContactCallbacks, HasGameRef<PinballGame> {
with ContactCallbacksNotifer, HasGameRef<PinballGame> {
/// {@macro scoring_behaviour}
ScoringBehaviour({
required int points,
@ -28,8 +28,8 @@ class ScoringBehaviour extends Component
if (parent is! BodyComponent) return;
final userData = parent.body.userData;
if (userData is ContactCallbacks) {
userData.add(this);
if (userData is ContactCallbacksNotifer) {
userData.addCallback(this);
} else {
parent.body.userData = this;
}
@ -42,7 +42,6 @@ class ScoringBehaviour extends Component
gameRef.read<GameBloc>().add(Scored(points: _points));
gameRef.audio.score();
gameRef.add(
ScoreText(
text: _points.toString(),

@ -53,7 +53,8 @@ class PinballGame extends Forge2DGame
final launcher = Launcher();
unawaited(addFromBlueprint(launcher));
unawaited(add(Board()));
unawaited(add(AlienZone()));
await addFromBlueprint(AlienZone());
await addFromBlueprint(SparkyFireZone());
unawaited(addFromBlueprint(Slingshots()));
unawaited(addFromBlueprint(DinoWalls()));

@ -2,13 +2,39 @@ import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
abstract class State<T> {
// TODO(alestiago): Investigate approaches to avoid having this as late.
late T _state;
T get state => _state;
set state(T value) {
if (value == _state) return;
_state = value;
_stream.sink.add(value);
}
final StreamController<T> _stream = StreamController<T>.broadcast();
Stream<T> get stream => _stream.stream;
}
/// Indicates the [AlienBumper]'s current sprite state.
enum AlienBumperState {
/// A lit up bumper.
active,
/// A dimmed bumper.
inactive,
}
/// {@template alien_bumper}
/// Bumper for area under the [Spaceship].
/// {@endtemplate}
class AlienBumper extends BodyComponent with InitialPosition {
class AlienBumper extends BodyComponent
with InitialPosition, State<AlienBumperState> {
/// {@macro alien_bumper}
AlienBumper._({
required double majorRadius,
@ -21,14 +47,17 @@ class AlienBumper extends BodyComponent with InitialPosition {
super(
priority: RenderPriority.alienBumper,
children: [
_AlienBumperSpriteGroupComponent(
onAssetPath: onAssetPath,
offAssetPath: offAssetPath,
),
if (children != null) ...children,
],
renderBody: false,
);
) {
_state = AlienBumperState.active;
_spriteGroupComponent = _AlienBumperSpriteGroupComponent(
offAssetPath: offAssetPath,
onAssetPath: onAssetPath,
state: state,
);
}
/// {@macro alien_bumper}
AlienBumper.a({
@ -53,8 +82,17 @@ class AlienBumper extends BodyComponent with InitialPosition {
);
final double _majorRadius;
final double _minorRadius;
late final _AlienBumperSpriteGroupComponent _spriteGroupComponent;
@override
Future<void> onLoad() async {
await super.onLoad();
await add(_spriteGroupComponent);
}
@override
Body createBody() {
final shape = EllipseShape(
@ -68,41 +106,24 @@ class AlienBumper extends BodyComponent with InitialPosition {
);
final bodyDef = BodyDef(
position: initialPosition,
userData: this,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
/// Animates the [AlienBumper].
Future<void> animate() async {
final spriteGroupComponent = firstChild<_AlienBumperSpriteGroupComponent>()
?..current = AlienBumperSpriteState.inactive;
await Future<void>.delayed(const Duration(milliseconds: 50));
spriteGroupComponent?.current = AlienBumperSpriteState.active;
}
}
/// Indicates the [AlienBumper]'s current sprite state.
@visibleForTesting
enum AlienBumperSpriteState {
/// A lit up bumper.
active,
/// A dimmed bumper.
inactive,
}
class _AlienBumperSpriteGroupComponent
extends SpriteGroupComponent<AlienBumperSpriteState> with HasGameRef {
extends SpriteGroupComponent<AlienBumperState> with HasGameRef {
_AlienBumperSpriteGroupComponent({
required String onAssetPath,
required String offAssetPath,
required AlienBumperState state,
}) : _onAssetPath = onAssetPath,
_offAssetPath = offAssetPath,
super(
anchor: Anchor.center,
position: Vector2(0, -0.1),
current: state,
);
final String _onAssetPath;
@ -113,14 +134,20 @@ class _AlienBumperSpriteGroupComponent
await super.onLoad();
final sprites = {
AlienBumperSpriteState.active:
Sprite(gameRef.images.fromCache(_onAssetPath)),
AlienBumperSpriteState.inactive:
AlienBumperState.active: Sprite(
gameRef.images.fromCache(_onAssetPath),
),
AlienBumperState.inactive:
Sprite(gameRef.images.fromCache(_offAssetPath)),
};
this.sprites = sprites;
current = AlienBumperSpriteState.active;
size = sprites[current]!.originalSize / 10;
// TODO(alestiago): Refactor once the following is merged:
// https://github.com/flame-engine/flame/pull/1566
final parent = this.parent;
if (parent is! AlienBumper) return;
parent.stream.listen((state) => current = state);
}
}

@ -40,21 +40,17 @@ void main() {
expect(
spriteGroupComponent.current,
equals(AlienBumperSpriteState.active),
equals(AlienBumperState.active),
);
final future = bumper.animate();
expect(
spriteGroupComponent.current,
equals(AlienBumperSpriteState.inactive),
equals(AlienBumperState.inactive),
);
await future;
expect(
spriteGroupComponent.current,
equals(AlienBumperSpriteState.active),
equals(AlienBumperState.active),
);
});
});

@ -1,38 +1,60 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
/// {@template contact_callbacks_adder}
///
/// {@endtemplate}
// TODO(alestiago): Consider adding streams to [ContactCallbacks].
extension ContactCallbacksAdder on ContactCallbacks {
/// {@macro contact_callbacks_adder}
void add(ContactCallbacks contactCallbacks) {
if (contactCallbacks.onBeginContact != null) {
onBeginContact = (other, contact) {
onBeginContact?.call(other, contact);
contactCallbacks.beginContact(other, contact);
};
class ContactCallbacksNotifer implements ContactCallbacks {
final List<ContactCallbacks> _callbacks = [];
@override
@mustCallSuper
void beginContact(Object other, Contact contact) {
onBeginContact?.call(other, contact);
for (final callback in _callbacks) {
callback.beginContact(other, contact);
}
}
if (contactCallbacks.onEndContact != null) {
onEndContact = (other, contact) {
onEndContact?.call(other, contact);
contactCallbacks.endContact(other, contact);
};
@override
@mustCallSuper
void endContact(Object other, Contact contact) {
onEndContact?.call(other, contact);
for (final callback in _callbacks) {
callback.endContact(other, contact);
}
}
if (contactCallbacks.onPreSolve != null) {
onPreSolve = (other, contact, oldManifold) {
onPreSolve?.call(other, contact, oldManifold);
contactCallbacks.preSolve(other, contact, oldManifold);
};
@override
@mustCallSuper
void preSolve(Object other, Contact contact, Manifold oldManifold) {
onPreSolve?.call(other, contact, oldManifold);
for (final callback in _callbacks) {
callback.preSolve(other, contact, oldManifold);
}
}
if (contactCallbacks.onPostSolve != null) {
onPostSolve = (other, contact, impulse) {
onPostSolve?.call(other, contact, impulse);
contactCallbacks.postSolve(other, contact, impulse);
};
@override
@mustCallSuper
void postSolve(Object other, Contact contact, ContactImpulse impulse) {
onPostSolve?.call(other, contact, impulse);
for (final callback in _callbacks) {
callback.postSolve(other, contact, impulse);
}
}
void addCallback(ContactCallbacks callback) {
_callbacks.add(callback);
}
@override
void Function(Object other, Contact contact)? onBeginContact;
@override
void Function(Object other, Contact contact)? onEndContact;
@override
void Function(Object other, Contact contact, ContactImpulse impulse)?
onPostSolve;
@override
void Function(Object other, Contact contact, Manifold oldManifold)?
onPreSolve;
}

Loading…
Cancel
Save