feat: add mobile touch controls

pull/232/head
Jochum van der Ploeg 3 years ago
parent e63d893a9c
commit 80c460252f
No known key found for this signature in database
GPG Key ID: E961B7B51589CA09

@ -6,6 +6,7 @@ import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/gen/assets.gen.dart';
@ -18,7 +19,8 @@ class PinballGame extends Forge2DGame
with
FlameBloc,
HasKeyboardHandlerComponents,
Controls<_GameBallsController> {
Controls<_GameBallsController>,
TapDetector {
PinballGame({
required this.theme,
required this.audio,
@ -74,6 +76,65 @@ class PinballGame extends Forge2DGame
await super.onLoad();
}
BoardSide? boardSideActive;
@override
void onTapDown(TapDownInfo info) {
if (info.raw.kind == PointerDeviceKind.touch) {
final rocket = descendants().whereType<RocketSpriteComponent>().first;
final bounds = rocket.topLeftPosition & rocket.size;
if (bounds.contains(info.eventPosition.game.toOffset())) {
descendants().whereType<Plunger>().first.pull();
} else {
final leftSide = info.eventPosition.widget.x < canvasSize.x / 2;
boardSideActive = leftSide ? BoardSide.left : BoardSide.right;
final flippers = descendants().whereType<Flipper>().where((flipper) {
return flipper.side == boardSideActive;
});
for (final flipper in flippers) {
flipper.moveUp();
}
}
}
super.onTapDown(info);
}
@override
void onTapUp(TapUpInfo info) {
final rocket = descendants().whereType<RocketSpriteComponent>().first;
final bounds = rocket.topLeftPosition & rocket.size;
if (bounds.contains(info.eventPosition.game.toOffset())) {
descendants().whereType<Plunger>().first.release();
} else {
_moveFlippersDown();
}
super.onTapUp(info);
}
@override
void onTapCancel() {
descendants().whereType<Plunger>().first.release();
_moveFlippersDown();
super.onTapCancel();
}
void _moveFlippersDown() {
if (boardSideActive != null) {
final flippers = descendants().whereType<Flipper>().where((flipper) {
return flipper.side == boardSideActive;
});
for (final flipper in flippers) {
flipper.moveDown();
}
boardSideActive = null;
}
}
void _addContactCallbacks() {
addContactCallback(BallScorePointsCallback(this));
addContactCallback(BottomWallBallContactCallback());
@ -135,7 +196,7 @@ class _GameBallsController extends ComponentController<PinballGame>
}
}
class DebugPinballGame extends PinballGame with FPSCounter, TapDetector {
class DebugPinballGame extends PinballGame with FPSCounter {
DebugPinballGame({
required PinballTheme theme,
required PinballAudio audio,
@ -172,9 +233,11 @@ class DebugPinballGame extends PinballGame with FPSCounter, TapDetector {
@override
void onTapUp(TapUpInfo info) {
add(
ControlledBall.debug()..initialPosition = info.eventPosition.game,
);
super.onTapUp(info);
if (info.raw.kind == PointerDeviceKind.mouse) {
add(ControlledBall.debug()..initialPosition = info.eventPosition.game);
}
}
}

@ -3,6 +3,7 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
@ -199,6 +200,193 @@ void main() {
);
});
});
group('flipper control', () {
flameTester.test('move left flipper up', (game) async {
await game.ready();
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2.zero());
when(() => eventPosition.widget).thenReturn(Vector2.zero());
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final flippers = game.descendants().whereType<Flipper>().where(
(flipper) => flipper.side == BoardSide.left,
);
game.onTapDown(tapDownEvent);
await game.ready();
expect(flippers.first.body.linearVelocity.y, isNegative);
});
flameTester.test('move right flipper up', (game) async {
await game.ready();
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2.zero());
when(() => eventPosition.widget).thenReturn(game.canvasSize);
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final flippers = game.descendants().whereType<Flipper>().where(
(flipper) => flipper.side == BoardSide.right,
);
game.onTapDown(tapDownEvent);
await game.ready();
expect(flippers.first.body.linearVelocity.y, isNegative);
});
flameTester.test('tap up moves flipper down', (game) async {
await game.ready();
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2.zero());
when(() => eventPosition.widget).thenReturn(Vector2.zero());
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final flippers = game.descendants().whereType<Flipper>().where(
(flipper) => flipper.side == BoardSide.left,
);
game.onTapDown(tapDownEvent);
await game.ready();
expect(flippers.first.body.linearVelocity.y, isNegative);
final tapUpEvent = MockTapUpInfo();
when(() => tapUpEvent.eventPosition).thenReturn(eventPosition);
game.onTapUp(tapUpEvent);
await game.ready();
expect(flippers.first.body.linearVelocity.y, isPositive);
});
flameTester.test('tap cancel moves flipper down', (game) async {
await game.ready();
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2.zero());
when(() => eventPosition.widget).thenReturn(Vector2.zero());
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final flippers = game.descendants().whereType<Flipper>().where(
(flipper) => flipper.side == BoardSide.left,
);
game.onTapDown(tapDownEvent);
await game.ready();
expect(flippers.first.body.linearVelocity.y, isNegative);
final tapUpEvent = MockTapUpInfo();
game.onTapCancel();
await game.ready();
expect(flippers.first.body.linearVelocity.y, isPositive);
});
});
group('plunger control', () {
flameTester.test('tap down moves plunger down', (game) async {
await game.ready();
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2(40, 60));
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final plunger = game.descendants().whereType<Plunger>().first;
game.onTapDown(tapDownEvent);
await game.ready();
expect(plunger.body.linearVelocity.y, equals(7));
});
flameTester.test('tap up releases plunger', (game) async {
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2(40, 60));
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final plunger = game.descendants().whereType<Plunger>().first;
game.onTapDown(tapDownEvent);
await game.ready();
expect(plunger.body.linearVelocity.y, equals(7));
final tapUpEvent = MockTapUpInfo();
when(() => tapUpEvent.eventPosition).thenReturn(eventPosition);
game.onTapUp(tapUpEvent);
await game.ready();
expect(plunger.body.linearVelocity.y, equals(0));
});
flameTester.test('tap cancel releases plunger', (game) async {
await game.ready();
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2(40, 60));
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final plunger = game.descendants().whereType<Plunger>().first;
game.onTapDown(tapDownEvent);
await game.ready();
expect(plunger.body.linearVelocity.y, equals(7));
game.onTapCancel();
await game.ready();
expect(plunger.body.linearVelocity.y, equals(0));
});
});
});
group('DebugPinballGame', () {
@ -208,8 +396,12 @@ void main() {
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2.all(10));
final raw = MockTapUpDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.mouse);
final tapUpEvent = MockTapUpInfo();
when(() => tapUpEvent.eventPosition).thenReturn(eventPosition);
when(() => tapUpEvent.raw).thenReturn(raw);
final previousBalls = game.descendants().whereType<Ball>().toList();

@ -2,7 +2,7 @@ import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
@ -55,8 +55,14 @@ class MockRawKeyUpEvent extends Mock implements RawKeyUpEvent {
}
}
class MockTapDownInfo extends Mock implements TapDownInfo {}
class MockTapDownDetails extends Mock implements TapDownDetails {}
class MockTapUpInfo extends Mock implements TapUpInfo {}
class MockTapUpDetails extends Mock implements TapUpDetails {}
class MockEventPosition extends Mock implements EventPosition {}
class MockFilter extends Mock implements Filter {}

Loading…
Cancel
Save