feat: adding KeyboardListenerComponent (#1594)

This commit is contained in:
Erick
2022-05-23 03:32:10 -03:00
committed by GitHub
parent 2e82dc95ec
commit c887c3616e
6 changed files with 282 additions and 2 deletions

View File

@ -91,6 +91,32 @@ that triggered the callback in the first place. The second is a set of the curre
The returned value should be `true` to allow the continuous propagation of the key event among other
components. To not allow any other component to receive the event, return `false`.
Flame also provides a default implementation called `KeyboardListenerComponent` which can be used
to handle keyboard events. Like any other component, it can be added as a child to a `FlameGame`
or another `Component`:
For example, imagine a `PositionComponent` which has methods to move on the X and Y axis,
then the following code could be used to bind those methods to key events:
```dart
add(
KeyboardListenerComponent(
keyUp: {
LogicalKeyboardKey.keyA: (keysPressed) { ... },
LogicalKeyboardKey.keyD: (keysPressed) { ... },
LogicalKeyboardKey.keyW: (keysPressed) { ... },
LogicalKeyboardKey.keyS: (keysPressed) { ... },
},
keyDown: {
LogicalKeyboardKey.keyA: (keysPressed) { ... },
LogicalKeyboardKey.keyD: (keysPressed) { ... },
LogicalKeyboardKey.keyW: (keysPressed) { ... },
LogicalKeyboardKey.keyS: (keysPressed) { ... },
},
),
);
```
### Controlling focus
On the widget level, it is possible to use the

View File

@ -6,6 +6,7 @@ import 'package:examples/stories/input/hoverables_example.dart';
import 'package:examples/stories/input/joystick_advanced_example.dart';
import 'package:examples/stories/input/joystick_example.dart';
import 'package:examples/stories/input/keyboard_example.dart';
import 'package:examples/stories/input/keyboard_listener_component_example.dart';
import 'package:examples/stories/input/mouse_cursor_example.dart';
import 'package:examples/stories/input/mouse_movement_example.dart';
import 'package:examples/stories/input/multitap_advanced_example.dart';
@ -48,6 +49,12 @@ void addInputStories(Dashbook dashbook) {
codeLink: baseLink('input/keyboard_example.dart'),
info: KeyboardExample.description,
)
..add(
'Keyboard (Component)',
(_) => GameWidget(game: KeyboardListenerComponentExample()),
codeLink: baseLink('input/keyboard_component_example.dart'),
info: KeyboardListenerComponentExample.description,
)
..add(
'Mouse Movement',
(_) => GameWidget(game: MouseMovementExample()),

View File

@ -0,0 +1,132 @@
import 'package:examples/commons/ember.dart';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flame/palette.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
class KeyboardListenerComponentExample extends FlameGame
with HasKeyboardHandlerComponents {
static const String description = '''
Similar to the default Keyboard example, but shows a different
implementation approach, which uses Flame's
KeyboardListenerComponent to handle input.
Usage: Use A S D W to steer Ember.
''';
static final Paint white = BasicPalette.white.paint();
static const int speed = 200;
late final Ember ember;
final Vector2 velocity = Vector2(0, 0);
@override
Future<void> onLoad() async {
ember = Ember(position: size / 2, size: Vector2.all(100));
add(ember);
add(
KeyboardListenerComponent(
keyUp: {
LogicalKeyboardKey.keyA: (keys) => _handleKey(
false,
LogicalKeyboardKey.keyA,
keys,
),
LogicalKeyboardKey.keyD: (keys) => _handleKey(
false,
LogicalKeyboardKey.keyD,
keys,
),
LogicalKeyboardKey.keyW: (keys) => _handleKey(
false,
LogicalKeyboardKey.keyW,
keys,
),
LogicalKeyboardKey.keyS: (keys) => _handleKey(
false,
LogicalKeyboardKey.keyS,
keys,
),
},
keyDown: {
LogicalKeyboardKey.keyA: (keys) => _handleKey(
true,
LogicalKeyboardKey.keyA,
keys,
),
LogicalKeyboardKey.keyD: (keys) => _handleKey(
true,
LogicalKeyboardKey.keyD,
keys,
),
LogicalKeyboardKey.keyW: (keys) => _handleKey(
true,
LogicalKeyboardKey.keyW,
keys,
),
LogicalKeyboardKey.keyS: (keys) => _handleKey(
true,
LogicalKeyboardKey.keyS,
keys,
),
},
),
);
}
bool _handleKey(
bool isDown,
LogicalKeyboardKey key,
Set<LogicalKeyboardKey> keysPressed,
) {
const w = LogicalKeyboardKey.keyW;
const a = LogicalKeyboardKey.keyA;
const s = LogicalKeyboardKey.keyS;
const d = LogicalKeyboardKey.keyD;
if (key == w) {
if (isDown) {
velocity.y = -1;
} else if (keysPressed.contains(s)) {
velocity.y = 1;
} else {
velocity.y = 0;
}
} else if (key == s) {
if (isDown) {
velocity.y = 1;
} else if (keysPressed.contains(w)) {
velocity.y = -1;
} else {
velocity.y = 0;
}
} else if (key == a) {
if (isDown) {
velocity.x = -1;
} else if (keysPressed.contains(d)) {
velocity.x = 1;
} else {
velocity.x = 0;
}
} else if (key == d) {
if (isDown) {
velocity.x = 1;
} else if (keysPressed.contains(a)) {
velocity.x = -1;
} else {
velocity.x = 0;
}
}
return true;
}
@override
void update(double dt) {
super.update(dt);
final displacement = velocity * (speed * dt);
ember.position.add(displacement);
}
}

View File

@ -8,6 +8,7 @@ export 'src/components/custom_painter_component.dart';
export 'src/components/fps_component.dart';
export 'src/components/fps_text_component.dart';
export 'src/components/input/joystick_component.dart';
export 'src/components/input/keyboard_listener_component.dart';
export 'src/components/isometric_tile_map_component.dart';
export 'src/components/mixins/draggable.dart';
export 'src/components/mixins/gesture_hitboxes.dart';

View File

@ -0,0 +1,38 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/src/game/mixins/keyboard.dart';
import 'package:flutter/services.dart';
/// The signature for a key handle function
typedef KeyHandlerCallback = bool Function(Set<LogicalKeyboardKey>);
/// {@template keyboard_listener_component}
/// A [Component] that receives keyboard input and executes registered methods.
/// This component is based on [KeyboardHandler], which requires the [FlameGame]
/// which is used to be mixed with [HasKeyboardHandlerComponents].
/// {@endtemplate}
class KeyboardListenerComponent extends Component with KeyboardHandler {
/// {@macro keyboard_listener_component}
KeyboardListenerComponent({
Map<LogicalKeyboardKey, KeyHandlerCallback> keyUp = const {},
Map<LogicalKeyboardKey, KeyHandlerCallback> keyDown = const {},
}) : _keyUp = keyUp,
_keyDown = keyDown;
final Map<LogicalKeyboardKey, KeyHandlerCallback> _keyUp;
final Map<LogicalKeyboardKey, KeyHandlerCallback> _keyDown;
@override
bool onKeyEvent(RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
final isUp = event is RawKeyUpEvent;
final handlers = isUp ? _keyUp : _keyDown;
final handler = handlers[event.logicalKey];
if (handler != null) {
return handler(keysPressed);
}
return true;
}
}

View File

@ -0,0 +1,76 @@
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
abstract class _KeyCallStub {
bool onCall(Set<LogicalKeyboardKey> keysPressed);
}
class KeyCallStub extends Mock implements _KeyCallStub {}
class MockRawKeyUpEvent extends Mock implements RawKeyUpEvent {
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return super.toString();
}
}
RawKeyUpEvent _mockKeyUp(LogicalKeyboardKey key) {
final event = MockRawKeyUpEvent();
when(() => event.logicalKey).thenReturn(key);
return event;
}
void main() {
group('KeyboardListenerComponent', () {
test('calls registered handlers', () {
final stub = KeyCallStub();
when(() => stub.onCall(any())).thenReturn(true);
final input = KeyboardListenerComponent(
keyUp: {
LogicalKeyboardKey.arrowUp: stub.onCall,
},
);
input.onKeyEvent(_mockKeyUp(LogicalKeyboardKey.arrowUp), {});
verify(() => stub.onCall({})).called(1);
});
test(
'returns false the handler return value',
() {
final stub = KeyCallStub();
when(() => stub.onCall(any())).thenReturn(false);
final input = KeyboardListenerComponent(
keyUp: {
LogicalKeyboardKey.arrowUp: stub.onCall,
},
);
expect(
input.onKeyEvent(_mockKeyUp(LogicalKeyboardKey.arrowUp), {}),
isFalse,
);
},
);
test(
'returns true (allowing event to bubble) when no handler is registered',
() {
final stub = KeyCallStub();
when(() => stub.onCall(any())).thenReturn(true);
final input = KeyboardListenerComponent();
expect(
input.onKeyEvent(_mockKeyUp(LogicalKeyboardKey.arrowUp), {}),
isTrue,
);
},
);
});
}