You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pinball/packages/pinball_components/lib/src/components/spaceship_ramp.dart

376 lines
10 KiB

import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_flame/pinball_flame.dart';
/// {@template spaceship_ramp}
/// Ramp leading into the [AndroidSpaceship].
/// {@endtemplate}
class SpaceshipRamp extends Component {
/// {@macro spaceship_ramp}
SpaceshipRamp()
: super(
children: [
_SpaceshipRampOpening(
outsidePriority: ZIndexes.ballOnBoard,
rotation: math.pi,
)
..initialPosition = Vector2(1.7, -19.8)
..layer = Layer.opening,
_SpaceshipRampOpening(
outsideLayer: Layer.spaceship,
outsidePriority: ZIndexes.ballOnSpaceship,
rotation: math.pi,
)
..initialPosition = Vector2(-13.7, -18.6)
..layer = Layer.spaceshipEntranceRamp,
_SpaceshipRampBackground(),
_SpaceshipRampBoardOpeningSpriteComponent()
..position = Vector2(3.4, -39.5),
_SpaceshipRampForegroundRailing(),
_SpaceshipRampBase()..initialPosition = Vector2(1.7, -20),
_SpaceshipRampBackgroundRailingSpriteComponent(),
_SpaceshipRampArrowSpriteComponent(),
],
);
/// Forwards the sprite to the next [SpaceshipRampArrowSpriteState].
///
/// If the current state is the last one it cycles back to the initial state.
void progress() =>
firstChild<_SpaceshipRampArrowSpriteComponent>()?.progress();
}
/// Indicates the state of the arrow on the [SpaceshipRamp].
@visibleForTesting
enum SpaceshipRampArrowSpriteState {
/// Arrow with no dashes lit up.
inactive,
/// Arrow with 1 light lit up.
active1,
/// Arrow with 2 lights lit up.
active2,
/// Arrow with 3 lights lit up.
active3,
/// Arrow with 4 lights lit up.
active4,
/// Arrow with all 5 lights lit up.
active5,
}
extension on SpaceshipRampArrowSpriteState {
String get path {
switch (this) {
case SpaceshipRampArrowSpriteState.inactive:
return Assets.images.android.ramp.arrow.inactive.keyName;
case SpaceshipRampArrowSpriteState.active1:
return Assets.images.android.ramp.arrow.active1.keyName;
case SpaceshipRampArrowSpriteState.active2:
return Assets.images.android.ramp.arrow.active2.keyName;
case SpaceshipRampArrowSpriteState.active3:
return Assets.images.android.ramp.arrow.active3.keyName;
case SpaceshipRampArrowSpriteState.active4:
return Assets.images.android.ramp.arrow.active4.keyName;
case SpaceshipRampArrowSpriteState.active5:
return Assets.images.android.ramp.arrow.active5.keyName;
}
}
SpaceshipRampArrowSpriteState get next {
return SpaceshipRampArrowSpriteState
.values[(index + 1) % SpaceshipRampArrowSpriteState.values.length];
}
}
class _SpaceshipRampBackground extends BodyComponent
with InitialPosition, Layered, ZIndex {
_SpaceshipRampBackground()
: super(
renderBody: false,
children: [
_SpaceshipRampBackgroundRampSpriteComponent(),
],
) {
layer = Layer.spaceshipEntranceRamp;
zIndex = ZIndexes.spaceshipRamp;
}
/// Width between walls of the ramp.
static const width = 5.0;
List<FixtureDef> _createFixtureDefs() {
final outerLeftCurveShape = BezierCurveShape(
controlPoints: [
Vector2(-30.75, -37.3),
Vector2(-32.5, -71.25),
Vector2(-14.2, -71.25),
],
);
final outerRightCurveShape = BezierCurveShape(
controlPoints: [
outerLeftCurveShape.vertices.last,
Vector2(2.5, -71.9),
Vector2(6.1, -44.9),
],
);
final boardOpeningEdgeShape = EdgeShape()
..set(
outerRightCurveShape.vertices.last,
Vector2(7.3, -41.1),
);
return [
FixtureDef(outerRightCurveShape),
FixtureDef(outerLeftCurveShape),
FixtureDef(boardOpeningEdgeShape),
];
}
@override
Body createBody() {
final bodyDef = BodyDef(position: initialPosition);
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture);
return body;
}
}
class _SpaceshipRampBackgroundRailingSpriteComponent extends SpriteComponent
with HasGameRef, ZIndex {
_SpaceshipRampBackgroundRailingSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-11.7, -54.3),
) {
zIndex = ZIndexes.spaceshipRampBackgroundRailing;
}
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.android.ramp.railingBackground.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
}
}
class _SpaceshipRampBackgroundRampSpriteComponent extends SpriteComponent
with HasGameRef {
_SpaceshipRampBackgroundRampSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-10.7, -53.6),
);
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.android.ramp.main.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
}
}
/// {@template spaceship_ramp_arrow_sprite_component}
/// An arrow inside [SpaceshipRamp].
///
/// Lights progressively whenever a [Ball] gets into [SpaceshipRamp].
/// {@endtemplate}
class _SpaceshipRampArrowSpriteComponent
extends SpriteGroupComponent<SpaceshipRampArrowSpriteState>
with HasGameRef, ZIndex {
/// {@macro spaceship_ramp_arrow_sprite_component}
_SpaceshipRampArrowSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-3.9, -56.5),
) {
zIndex = ZIndexes.spaceshipRampArrow;
}
/// Changes arrow image to the next [Sprite].
void progress() => current = current?.next;
@override
Future<void> onLoad() async {
await super.onLoad();
final sprites = <SpaceshipRampArrowSpriteState, Sprite>{};
this.sprites = sprites;
for (final spriteState in SpaceshipRampArrowSpriteState.values) {
sprites[spriteState] = Sprite(
gameRef.images.fromCache(spriteState.path),
);
}
current = SpaceshipRampArrowSpriteState.inactive;
size = sprites[current]!.originalSize / 10;
}
}
class _SpaceshipRampBoardOpeningSpriteComponent extends SpriteComponent
with HasGameRef, ZIndex {
_SpaceshipRampBoardOpeningSpriteComponent() : super(anchor: Anchor.center) {
zIndex = ZIndexes.spaceshipRampBoardOpening;
}
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.android.ramp.boardOpening.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
}
}
class _SpaceshipRampForegroundRailing extends BodyComponent
with InitialPosition, Layered, ZIndex {
_SpaceshipRampForegroundRailing()
: super(
renderBody: false,
children: [_SpaceshipRampForegroundRailingSpriteComponent()],
) {
layer = Layer.spaceshipEntranceRamp;
zIndex = ZIndexes.spaceshipRampForegroundRailing;
}
List<FixtureDef> _createFixtureDefs() {
final innerLeftCurveShape = BezierCurveShape(
controlPoints: [
Vector2(-24.5, -38),
Vector2(-26.3, -64),
Vector2(-13.8, -64.5),
],
);
final innerRightCurveShape = BezierCurveShape(
controlPoints: [
innerLeftCurveShape.vertices.last,
Vector2(-2.5, -66.2),
Vector2(0, -44.5),
],
);
final boardOpeningEdgeShape = EdgeShape()
..set(
innerRightCurveShape.vertices.last,
Vector2(-0.85, -40.8),
);
return [
FixtureDef(innerLeftCurveShape),
FixtureDef(innerRightCurveShape),
FixtureDef(boardOpeningEdgeShape),
];
}
@override
Body createBody() {
final bodyDef = BodyDef(position: initialPosition);
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture);
return body;
}
}
class _SpaceshipRampForegroundRailingSpriteComponent extends SpriteComponent
with HasGameRef {
_SpaceshipRampForegroundRailingSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-12.3, -52.5),
);
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.android.ramp.railingForeground.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
}
}
class _SpaceshipRampBase extends BodyComponent with InitialPosition, Layered {
_SpaceshipRampBase() : super(renderBody: false) {
layer = Layer.board;
}
@override
Body createBody() {
const baseWidth = 9;
final baseShape = BezierCurveShape(
controlPoints: [
Vector2(initialPosition.x - baseWidth / 2, initialPosition.y),
Vector2(initialPosition.x - baseWidth / 2, initialPosition.y) +
Vector2(2, -5),
Vector2(initialPosition.x + baseWidth / 2, initialPosition.y) +
Vector2(-2, -5),
Vector2(initialPosition.x + baseWidth / 2, initialPosition.y)
],
);
final fixtureDef = FixtureDef(baseShape);
final bodyDef = BodyDef(position: initialPosition);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
/// {@template spaceship_ramp_opening}
/// [LayerSensor] with [Layer.spaceshipEntranceRamp] to filter [Ball] collisions
/// inside [_SpaceshipRampBackground].
/// {@endtemplate}
class _SpaceshipRampOpening extends LayerSensor {
/// {@macro spaceship_ramp_opening}
_SpaceshipRampOpening({
Layer? outsideLayer,
int? outsidePriority,
required double rotation,
}) : _rotation = rotation,
super(
insideLayer: Layer.spaceshipEntranceRamp,
outsideLayer: outsideLayer,
orientation: LayerEntranceOrientation.down,
insideZIndex: ZIndexes.ballOnSpaceshipRamp,
outsideZIndex: outsidePriority,
);
final double _rotation;
static final Vector2 _size = Vector2(_SpaceshipRampBackground.width / 3, .1);
@override
Shape get shape {
return PolygonShape()
..setAsBox(
_size.x,
_size.y,
initialPosition,
_rotation,
);
}
}