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
|
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`.
|
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
|
### Controlling focus
|
||||||
|
|
||||||
On the widget level, it is possible to use the
|
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_advanced_example.dart';
|
||||||
import 'package:examples/stories/input/joystick_example.dart';
|
import 'package:examples/stories/input/joystick_example.dart';
|
||||||
import 'package:examples/stories/input/keyboard_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_cursor_example.dart';
|
||||||
import 'package:examples/stories/input/mouse_movement_example.dart';
|
import 'package:examples/stories/input/mouse_movement_example.dart';
|
||||||
import 'package:examples/stories/input/multitap_advanced_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'),
|
codeLink: baseLink('input/keyboard_example.dart'),
|
||||||
info: KeyboardExample.description,
|
info: KeyboardExample.description,
|
||||||
)
|
)
|
||||||
|
..add(
|
||||||
|
'Keyboard (Component)',
|
||||||
|
(_) => GameWidget(game: KeyboardListenerComponentExample()),
|
||||||
|
codeLink: baseLink('input/keyboard_component_example.dart'),
|
||||||
|
info: KeyboardListenerComponentExample.description,
|
||||||
|
)
|
||||||
..add(
|
..add(
|
||||||
'Mouse Movement',
|
'Mouse Movement',
|
||||||
(_) => GameWidget(game: MouseMovementExample()),
|
(_) => 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_component.dart';
|
||||||
export 'src/components/fps_text_component.dart';
|
export 'src/components/fps_text_component.dart';
|
||||||
export 'src/components/input/joystick_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/isometric_tile_map_component.dart';
|
||||||
export 'src/components/mixins/draggable.dart';
|
export 'src/components/mixins/draggable.dart';
|
||||||
export 'src/components/mixins/gesture_hitboxes.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