feat: add flame animation on boost (#196)

* feat: add flame animation on boost

* refactor: pr suggestions

* chore: add Component suffix
pull/211/head
Allison Ryan 3 years ago committed by GitHub
parent e4ab7c35d1
commit 2c879ab5b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -66,9 +66,8 @@ class BallController extends ComponentController<Ball>
// given animations. // given animations.
component.stop(); component.stop();
await Future<void>.delayed(const Duration(seconds: 1)); await Future<void>.delayed(const Duration(seconds: 1));
component component.resume();
..resume() await component.boost(Vector2(40, 110));
..boost(Vector2(200, 500));
} }
@override @override

@ -7,7 +7,8 @@ extension PinballGameAssetsX on PinballGame {
/// Returns a list of assets to be loaded /// Returns a list of assets to be loaded
List<Future> preLoadAssets() { List<Future> preLoadAssets() {
return [ return [
images.load(components.Assets.images.ball.keyName), images.load(components.Assets.images.ball.ball.keyName),
images.load(components.Assets.images.ball.flameEffect.keyName),
images.load(components.Assets.images.flutterSignPost.keyName), images.load(components.Assets.images.flutterSignPost.keyName),
images.load(components.Assets.images.flipper.left.keyName), images.load(components.Assets.images.flipper.left.keyName),
images.load(components.Assets.images.flipper.right.keyName), images.load(components.Assets.images.flipper.right.keyName),

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

@ -13,10 +13,7 @@ class $AssetsImagesGen {
$AssetsImagesAlienBumperGen get alienBumper => $AssetsImagesAlienBumperGen get alienBumper =>
const $AssetsImagesAlienBumperGen(); const $AssetsImagesAlienBumperGen();
$AssetsImagesBackboardGen get backboard => const $AssetsImagesBackboardGen(); $AssetsImagesBackboardGen get backboard => const $AssetsImagesBackboardGen();
$AssetsImagesBallGen get ball => const $AssetsImagesBallGen();
/// File path: assets/images/ball.png
AssetGenImage get ball => const AssetGenImage('assets/images/ball.png');
$AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen(); $AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen();
$AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen(); $AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen();
$AssetsImagesChromeDinoGen get chromeDino => $AssetsImagesChromeDinoGen get chromeDino =>
@ -63,6 +60,17 @@ class $AssetsImagesBackboardGen {
const AssetGenImage('assets/images/backboard/display.png'); const AssetGenImage('assets/images/backboard/display.png');
} }
class $AssetsImagesBallGen {
const $AssetsImagesBallGen();
/// File path: assets/images/ball/ball.png
AssetGenImage get ball => const AssetGenImage('assets/images/ball/ball.png');
/// File path: assets/images/ball/flame_effect.png
AssetGenImage get flameEffect =>
const AssetGenImage('assets/images/ball/flame_effect.png');
}
class $AssetsImagesBaseboardGen { class $AssetsImagesBaseboardGen {
const $AssetsImagesBaseboardGen(); const $AssetsImagesBaseboardGen();

@ -4,6 +4,7 @@ import 'dart:ui';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/widgets.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
/// {@template ball} /// {@template ball}
@ -49,9 +50,6 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
/// The base [Color] used to tint this [Ball]. /// The base [Color] used to tint this [Ball].
final Color baseColor; final Color baseColor;
double _boostTimer = 0;
static const _boostDuration = 2.0;
@override @override
Body createBody() { Body createBody() {
final shape = CircleShape()..radius = size.x / 2; final shape = CircleShape()..radius = size.x / 2;
@ -87,32 +85,20 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
body.gravityScale = Vector2(0, 1); body.gravityScale = Vector2(0, 1);
} }
/// Applies a boost and [_TurboChargeSpriteAnimationComponent] on this [Ball].
Future<void> boost(Vector2 impulse) async {
body.linearVelocity = impulse;
await add(_TurboChargeSpriteAnimationComponent());
}
@override @override
void update(double dt) { void update(double dt) {
super.update(dt); super.update(dt);
if (_boostTimer > 0) {
_boostTimer -= dt;
final direction = body.linearVelocity.normalized();
final effect = FireEffect(
burstPower: _boostTimer,
direction: direction,
position: Vector2(body.position.x, body.position.y),
priority: priority - 1,
);
unawaited(gameRef.add(effect));
}
_rescaleSize(); _rescaleSize();
_setPositionalGravity(); _setPositionalGravity();
} }
/// Applies a boost on this [Ball].
void boost(Vector2 impulse) {
body.linearVelocity = impulse;
_boostTimer = _boostDuration;
}
void _rescaleSize() { void _rescaleSize() {
final boardHeight = BoardDimensions.bounds.height; final boardHeight = BoardDimensions.bounds.height;
const maxShrinkValue = BoardDimensions.perspectiveShrinkFactor; const maxShrinkValue = BoardDimensions.perspectiveShrinkFactor;
@ -153,10 +139,61 @@ class _BallSpriteComponent extends SpriteComponent with HasGameRef {
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = await gameRef.loadSprite(
Assets.images.ball.keyName, Assets.images.ball.ball.keyName,
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
anchor = Anchor.center; anchor = Anchor.center;
} }
} }
class _TurboChargeSpriteAnimationComponent extends SpriteAnimationComponent
with HasGameRef {
_TurboChargeSpriteAnimationComponent()
: super(
anchor: const Anchor(0.53, 0.72),
priority: Ball.boardPriority + 1,
removeOnFinish: true,
);
late final Vector2 _textureSize;
@override
Future<void> onLoad() async {
await super.onLoad();
final spriteSheet = await gameRef.images.load(
Assets.images.ball.flameEffect.keyName,
);
const amountPerRow = 8;
const amountPerColumn = 4;
_textureSize = Vector2(
spriteSheet.width / amountPerRow,
spriteSheet.height / amountPerColumn,
);
animation = SpriteAnimation.fromFrameData(
spriteSheet,
SpriteAnimationData.sequenced(
amount: amountPerRow * amountPerColumn,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: _textureSize,
loop: false,
),
);
}
@override
void update(double dt) {
super.update(dt);
if (parent != null) {
final body = (parent! as BodyComponent).body;
final direction = -body.linearVelocity.normalized();
angle = math.atan2(direction.x, -direction.y);
size = (_textureSize / 45) * body.fixtures.first.shape.radius;
}
}
}

@ -39,6 +39,7 @@ flutter:
assets: assets:
- assets/images/ - assets/images/
- assets/images/ball/
- assets/images/baseboard/ - assets/images/baseboard/
- assets/images/boundary/ - assets/images/boundary/
- assets/images/dino/ - assets/images/dino/

@ -1,5 +1,6 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -169,20 +170,41 @@ void main() {
expect(ball.body.linearVelocity, equals(Vector2.zero())); expect(ball.body.linearVelocity, equals(Vector2.zero()));
ball.boost(Vector2.all(10)); await ball.boost(Vector2.all(10));
expect(ball.body.linearVelocity.x, greaterThan(0)); expect(ball.body.linearVelocity.x, greaterThan(0));
expect(ball.body.linearVelocity.y, greaterThan(0)); expect(ball.body.linearVelocity.y, greaterThan(0));
}); });
flameTester.test('adds fire effect components to the game', (game) async { flameTester.test('adds TurboChargeSpriteAnimation', (game) async {
final ball = Ball(baseColor: Colors.blue); final ball = Ball(baseColor: Colors.blue);
await game.ensureAdd(ball); await game.ensureAdd(ball);
ball.boost(Vector2.all(10)); await ball.boost(Vector2.all(10));
game.update(0); game.update(0);
await game.ready();
expect(game.children.whereType<FireEffect>().length, greaterThan(0)); expect(
ball.children.whereType<SpriteAnimationComponent>().single,
isNotNull,
);
});
flameTester.test('removes TurboChargeSpriteAnimation after it finishes',
(game) async {
final ball = Ball(baseColor: Colors.blue);
await game.ensureAdd(ball);
await ball.boost(Vector2.all(10));
game.update(0);
final turboChargeSpriteAnimation =
ball.children.whereType<SpriteAnimationComponent>().single;
expect(ball.contains(turboChargeSpriteAnimation), isTrue);
game.update(turboChargeSpriteAnimation.animation!.totalDuration());
game.update(0.1);
expect(ball.contains(turboChargeSpriteAnimation), isFalse);
}); });
}); });
}); });

@ -94,6 +94,7 @@ void main() {
final controller = WrappedBallController(ball, gameRef); final controller = WrappedBallController(ball, gameRef);
when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc); when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc);
when(() => ball.controller).thenReturn(controller); when(() => ball.controller).thenReturn(controller);
when(() => ball.boost(any())).thenAnswer((_) async {});
await controller.turboCharge(); await controller.turboCharge();
@ -109,6 +110,7 @@ void main() {
final controller = WrappedBallController(ball, gameRef); final controller = WrappedBallController(ball, gameRef);
when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc); when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc);
when(() => ball.controller).thenReturn(controller); when(() => ball.controller).thenReturn(controller);
when(() => ball.boost(any())).thenAnswer((_) async {});
await controller.turboCharge(); await controller.turboCharge();
@ -124,6 +126,7 @@ void main() {
final controller = WrappedBallController(ball, gameRef); final controller = WrappedBallController(ball, gameRef);
when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc); when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc);
when(() => ball.controller).thenReturn(controller); when(() => ball.controller).thenReturn(controller);
when(() => ball.boost(any())).thenAnswer((_) async {});
await controller.turboCharge(); await controller.turboCharge();

Loading…
Cancel
Save