mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 10:38:17 +08:00
feat: adding KeyboardListenerComponent (#1594)
This commit is contained in:
@ -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
|
||||
|
||||
@ -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()),
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user