mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-16 04:38:42 +08:00
397 lines
12 KiB
Markdown
397 lines
12 KiB
Markdown
# Input
|
|
|
|
## Gestures
|
|
|
|
Inside `package:flame/gestures.dart` you can find a whole set of `mixin`s which can be included on your game class instance to be able to receive touch input events. Below you can see the full list of these `mixin`s and its methods:
|
|
|
|
|
|
## Touch and mouse detectors
|
|
```
|
|
- TapDetector
|
|
- onTap
|
|
- onTapCancel
|
|
- onTapDown
|
|
- onTapUp
|
|
|
|
- SecondaryTapDetector
|
|
- onSecondaryTapDown
|
|
- onSecondaryTapUp
|
|
- onSecondaryTapCancel
|
|
|
|
- DoubleTapDetector
|
|
- onDoubleTap
|
|
|
|
- LongPressDetector
|
|
- onLongPress
|
|
- onLongPressStart
|
|
- onLongPressMoveUpdate
|
|
- onLongPressUp
|
|
- onLongPressEnd
|
|
|
|
- VerticalDragDetector
|
|
- onVerticalDragDown
|
|
- onVerticalDragStart
|
|
- onVerticalDragUpdate
|
|
- onVerticalDragEnd
|
|
- onVerticalDragCancel
|
|
|
|
- HorizontalDragDetector
|
|
- onHorizontalDragDown
|
|
- onHorizontalDragStart
|
|
- onHorizontalDragUpdate
|
|
- onHorizontalDragEnd
|
|
- onHorizontalDragCancel
|
|
|
|
- ForcePressDetector
|
|
- onForcePressStart
|
|
- onForcePressPeak
|
|
- onForcePressUpdate
|
|
- onForcePressEnd
|
|
|
|
- PanDetector
|
|
- onPanDown
|
|
- onPanStart
|
|
- onPanUpdate
|
|
- onPanEnd
|
|
- onPanCancel
|
|
|
|
- ScaleDetector
|
|
- onScaleStart
|
|
- onScaleUpdate
|
|
- onScaleEnd
|
|
|
|
- MultiTouchTapDetector
|
|
- onTap
|
|
- onTapCancel
|
|
- onTapDown
|
|
- onTapUp
|
|
|
|
- MultiTouchDragDetector
|
|
- onReceiveDrag
|
|
```
|
|
|
|
Mouse only events
|
|
```
|
|
- MouseMovementDetector
|
|
- onMouseMove
|
|
- ScrollDetector
|
|
- onScroll
|
|
```
|
|
|
|
Many of these detectors can conflict with each other. For example, you can't register both Vertical and Horizontal drags, so not all of them can be used together.
|
|
|
|
It is also not possible to mix advanced detectors (`MultiTouch*`) with basic detectors as they will *always win the gesture arena* and the basic detectors will never be triggered. So for example, you can use both `MultiTouchDragDetector` and `MultiTouchDragDetector` together, but if you try to use `MultiTouchTapDetector` and `PanDetector`, no events will be triggered for the later.
|
|
|
|
Flame's GestureApi is provided byt Flutter's Gestures Widgets, including [GestureDetector widget](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html), [RawGestureDetector widget](https://api.flutter.dev/flutter/widgets/RawGestureDetector-class.html) and [MouseRegion widget](https://api.flutter.dev/flutter/widgets/MouseRegion-class.html), you can also read more about Flutter's gestures [here](https://api.flutter.dev/flutter/gestures/gestures-library.html).
|
|
|
|
## Example
|
|
|
|
```dart
|
|
class MyGame extends Game with TapDetector {
|
|
// Other methods omitted
|
|
|
|
@override
|
|
void onTapDown(TapDownDetails details) {
|
|
print("Player tap down on ${details.globalPosition.dx} - ${details.globalPosition.dy}");
|
|
}
|
|
|
|
@override
|
|
void onTapUp(TapUpDetails details) {
|
|
print("Player tap up on ${details.globalPosition.dx} - ${details.globalPosition.dy}");
|
|
}
|
|
}
|
|
```
|
|
You can also check a more complete example [here](https://github.com/flame-engine/flame/tree/master/doc/examples/gestures).
|
|
|
|
## Tapable and Draggable components
|
|
|
|
Any component derived from `BaseComponent` (most components) can add the `Tapable` and/or the `Draggable` mixins to handle taps and drags on the component.
|
|
|
|
All overridden methods return a boolean to control if the event should be passed down further along to components underneath it.
|
|
So say that you only want your top visible component to receive a tap and not the ones underneath it, then your `onTapDown`, `onTapUp` and `onTapCancel` implementations should return `false` and if you want the event to go through more of the components underneath then you should return `true`.
|
|
|
|
The same applies if your component has children, then the event is first sent to the leaves in the children tree and then passed further down until a method returns `false`.
|
|
|
|
### Tapable components
|
|
|
|
By adding the `HasTapableComponents` mixin to your game, and using the mixin `Tapable` on your components, you can override the following methods on your components:
|
|
|
|
```dart
|
|
void onTapCancel() {}
|
|
void onTapDown(TapDownDetails details) {}
|
|
void onTapUp(TapUpDetails details) {}
|
|
```
|
|
|
|
Minimal component example:
|
|
|
|
```dart
|
|
import 'package:flame/components/component.dart';
|
|
import 'package:flame/components/mixins/tapable.dart';
|
|
|
|
class TapableComponent extends PositionComponent with Tapable {
|
|
|
|
// update and render omitted
|
|
|
|
@override
|
|
void onTapUp(TapUpDetails details) {
|
|
print("tap up");
|
|
}
|
|
|
|
@override
|
|
void onTapDown(TapDownDetails details) {
|
|
print("tap down");
|
|
}
|
|
|
|
@override
|
|
void onTapCancel() {
|
|
print("tap cancel");
|
|
}
|
|
}
|
|
|
|
class MyGame extends BaseGame with HasTapableComponents {
|
|
MyGame() {
|
|
add(TapableComponent());
|
|
}
|
|
}
|
|
```
|
|
|
|
Warning: `HasTapableComponents` uses an advanced gesture detector under the hood and as explained further up on this page it shouldn't be used alongside basic detectors.
|
|
|
|
### Draggable components
|
|
|
|
Just like with `Tapable`, Flame offers a mixin for `Draggable`.
|
|
|
|
By adding the `HasDraggableComponents` mixin to your game, and by using the mixin `Draggable` on your components, they can override the simple methods that enable an easy to use drag api on your components.
|
|
|
|
```dart
|
|
void onDragStart(int pointerId, Vector2 startPosition) {}
|
|
void onDragUpdate(int pointerId, DragUpdateDetails details) {}
|
|
void onDragEnd(int pointerId, DragEndDetails details) {}
|
|
void onDragCancel(int pointerId) {}
|
|
```
|
|
|
|
Note that all events take a uniquely generated pointer id so you can, if desired, distinguish between different simultaneous drags.
|
|
|
|
The default implementation provided by `Draggable` will already check:
|
|
|
|
* upon drag start, the component only receives the event if the position is within its bounds; keep track of pointerId.
|
|
* when handling updates/end/cancel, the component only receives the event if the pointerId was tracked (regardless of position).
|
|
* on end/cancel, untrack pointerId
|
|
|
|
Minimal component example (this example ignores pointerId so it wont work well if you try to multi-drag):
|
|
|
|
```dart
|
|
import 'package:flame/components/component.dart';
|
|
import 'package:flame/components/mixins/draggable.dart';
|
|
|
|
class DraggableComponent extends PositionComponent with Draggable {
|
|
|
|
// update and render omitted
|
|
|
|
Vector2 dragDeltaPosition;
|
|
bool get isDragging => dragDeltaPosition != null;
|
|
|
|
@override
|
|
bool onDragStart(int pointerId, Vector2 startPosition) {
|
|
dragDeltaPosition = startPosition - position;
|
|
return false;
|
|
}
|
|
|
|
@override
|
|
bool onDragUpdate(int pointerId, DragUpdateDetails details) {
|
|
final localCoords = gameRef.convertGlobalToLocalCoordinate(
|
|
details.globalPosition.toVector2(),
|
|
);
|
|
position = localCoords - dragDeltaPosition;
|
|
return false;
|
|
}
|
|
|
|
@override
|
|
bool onDragEnd(int pointerId, DragEndDetails details) {
|
|
dragDeltaPosition = null;
|
|
return false;
|
|
}
|
|
|
|
@override
|
|
bool onDragCancel(int pointerId) {
|
|
dragDeltaPosition = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class MyGame extends BaseGame with HasDraggableComponents {
|
|
MyGame() {
|
|
add(DraggableComponent());
|
|
}
|
|
}
|
|
```
|
|
|
|
Warning: `HasDraggableComponents` uses an advanced gesture detector under the hood and as explained further up on this page, shouldn't be used alongside basic detectors.
|
|
|
|
## Hitbox
|
|
The `Hitbox` mixin is used to make detection of gestures on top of your `PositionComponent`s more
|
|
accurate. Say that you have a fairly round rock as a `SpriteComponent` for example, then you don't
|
|
want to register input that is in the corner of the image where the rock is not displayed. Then you
|
|
can use the `Hitbox` mixin to define a more accurate polygon for which the input should be within
|
|
for the event to be counted on your component.
|
|
|
|
An example of you to use it can be seen
|
|
[here](https://github.com/flame-engine/flame/blob/master/doc/examples/gestures/lib/main_tapables_hitbox.dart).
|
|
|
|
## Keyboard
|
|
|
|
Flame provides a simple way to access Flutter's features regarding accessing Keyboard input events.
|
|
|
|
To use it, just add the `KeyboardEvents` mixin to your game class.
|
|
When doing this you will need to implement the `onKeyEvent` method, this method is called every time a keyboard event happens, and it receives an instance of the Flutter class `RawKeyEvent`.
|
|
This event can be used to get information about what occurred, like if it was a key down or key up event, and which key was pressed etc.
|
|
|
|
Minimal example:
|
|
|
|
```dart
|
|
import 'package:flame/game.dart';
|
|
import 'package:flame/keyboard.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
class MyGame extends Game with KeyboardEvents {
|
|
// update and render omitted
|
|
|
|
@override
|
|
void onKeyEvent(e) {
|
|
final bool isKeyDown = e is RawKeyDownEvent;
|
|
print(" Key: ${e.data.keyLabel} - isKeyDown: $isKeyDown");
|
|
}
|
|
}
|
|
```
|
|
|
|
You can also check a more complete example [here](https://github.com/flame-engine/flame/tree/master/doc/examples/keyboard).
|
|
|
|
## Joystick
|
|
|
|
Flame provides a component capable of creating a virtual joystick for taking input for your game. It can be configure with a variaty of combinations, like using two sticks, or only one stick and some pressable buttons, and so on.
|
|
|
|
To use this feature. You need to create a `JoystickComponent`, configure it the way you want, and add it to your game.
|
|
|
|
To receive the inputs from the joystick component, you need to have a class which implements a `JoystickListener`, and that class needs to be added as an observer of the joystick component previous created. That implementer of the the `JoystickListener` could be any class, but one common practice is for it to be your component that will be controlled by the joystick, like a player component for example.
|
|
|
|
Check this example to get a better understanding:
|
|
|
|
```dart
|
|
import 'package:flame/components/joystick/joystick_action.dart';
|
|
import 'package:flame/components/joystick/joystick_component.dart';
|
|
import 'package:flame/components/joystick/joystick_directional.dart';
|
|
import 'package:flame/game.dart';
|
|
import 'package:flame/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
class MyGame extends BaseGame with MultiTouchDragDetector {
|
|
final player = Player();
|
|
final joystick = JoystickComponent(
|
|
componentPriority: 0,
|
|
directional: JoystickDirectional(
|
|
spriteBackgroundDirectional: Sprite('directional_background.png'), // optional
|
|
spriteKnobDirectional: Sprite('directional_knob.png'), // optional
|
|
isFixed: true, // optional
|
|
margin: const EdgeInsets.only(left: 100, bottom: 100), // optional
|
|
size: 80, // optional
|
|
color: Colors.blueGrey, // optional
|
|
opacityBackground: 0.5, // optional
|
|
opacityKnob: 0.8, // optional
|
|
),
|
|
actions: [
|
|
JoystickAction(
|
|
actionId: 1, // required
|
|
sprite: Sprite('action.png'), // optional
|
|
spritePressed: Sprite('action_pressed.png'), // optional
|
|
spriteBackgroundDirection: Sprite('action_direction_background.png'), // optional
|
|
enableDirection: false, // optional
|
|
size: 50, // optional
|
|
sizeFactorBackgroundDirection: 1.5, // optional
|
|
margin: const EdgeInsets.all(50), // optional
|
|
color: Colors.blueGrey, // optional
|
|
align: JoystickActionAlign.BOTTOM_RIGHT, // optional
|
|
opacityBackground: 0.5, // optional
|
|
opacityKnob: 0.8, // optional
|
|
),
|
|
],
|
|
);
|
|
|
|
MyGame() {
|
|
joystick.addObserver(player);
|
|
add(player);
|
|
add(joystick);
|
|
}
|
|
|
|
@override
|
|
void onReceiveDrag(DragEvent drag) {
|
|
joystick.onReceiveDrag(drag);
|
|
super.onReceiveDrag(drag);
|
|
}
|
|
}
|
|
|
|
class Player extends Component implements JoystickListener {
|
|
|
|
@override
|
|
void render(Canvas canvas) {}
|
|
|
|
@override
|
|
void update(double dt) {}
|
|
|
|
@override
|
|
void joystickAction(JoystickActionEvent event) {
|
|
// Do anything when click in action button.
|
|
print('Action: $event');
|
|
}
|
|
|
|
@override
|
|
void joystickChangeDirectional(JoystickDirectionalEvent event) {
|
|
// Do anything when interact with directional.
|
|
print('Directional: $event');
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
### JoystickDirectionalEvent
|
|
|
|
```dart
|
|
|
|
JoystickDirectionalEvent({
|
|
JoystickMoveDirectional directional,
|
|
double intensity = 0.0,
|
|
double radAngle = 0.0,
|
|
});
|
|
|
|
enum JoystickMoveDirectional {
|
|
MOVE_UP,
|
|
MOVE_UP_LEFT,
|
|
MOVE_UP_RIGHT,
|
|
MOVE_RIGHT,
|
|
MOVE_DOWN,
|
|
MOVE_DOWN_RIGHT,
|
|
MOVE_DOWN_LEFT,
|
|
MOVE_LEFT,
|
|
IDLE
|
|
}
|
|
|
|
```
|
|
|
|
### JoystickActionEvent
|
|
|
|
```dart
|
|
|
|
JoystickActionEvent({
|
|
int id,
|
|
double intensity = 0.0,
|
|
double radAngle = 0.0,
|
|
ActionEvent event,
|
|
});
|
|
|
|
enum ActionEvent { DOWN, UP, MOVE, CANCEL }
|
|
|
|
```
|
|
|
|
You can also check a more complete example [here](https://github.com/flame-engine/flame/tree/master/doc/examples/joystick).
|
|
|