mirror of
				https://github.com/flame-engine/flame.git
				synced 2025-11-01 01:18:38 +08:00 
			
		
		
		
	 5529869a76
			
		
	
	5529869a76
	
	
	
		
			
			* Adding custom mouse cursors for flame * linting and adding fvm to gitignore * PR suggestions * Apply suggestions from code review Co-authored-by: Luan Nico <luanpotter27@gmail.com> Co-authored-by: Luan Nico <luanpotter27@gmail.com>
		
			
				
	
	
		
			329 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			329 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # Gesture Input
 | |
| 
 | |
| This includes documentation for gesture inputs, which is, mouse and touch pointers.
 | |
| 
 | |
| For other input documents, see also:
 | |
| 
 | |
| - [Keyboard Input](keyboard-input.md): for keystrokes
 | |
| - [Other Inputs](other-inputs.md): For joysticks, game pads, etc.
 | |
| 
 | |
| ## Intro
 | |
| 
 | |
| 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 latter.
 | |
| 
 | |
| Flame's GestureApi is provided by Flutter's Gesture 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).
 | |
| 
 | |
| It is also possible to change the current mouse cursor displayed on the `GameWidget` region. To do
 | |
| so the following code can be used inside the `Game` class
 | |
| 
 | |
| ```dart
 | |
| mouseCursor.value = SystemMouseCursors.move;
 | |
| ```
 | |
| 
 | |
| To already initialize the `GameWidget` with a custom cursor, the `mouseCursor` property can be used
 | |
| 
 | |
| ```dart
 | |
| GameWidget(
 | |
|   game: MouseCursorGame(),
 | |
|   mouseCursor: SystemMouseCursors.move,
 | |
| );
 | |
| ```
 | |
| 
 | |
| ## Event coordinate system
 | |
| 
 | |
| On events that have positions, like for example `Tap*` or `Drag`, you will notice that the `eventPosition`
 | |
| attribute includes 3 fields: `game`, `widget` and `global`. Below you will find a brief explanation
 | |
| about each one of them.
 | |
| 
 | |
| ### global
 | |
| 
 | |
| The position where the event occurred considering the entire screen, same as
 | |
| `globalPosition` in Flutter's native events.
 | |
| 
 | |
| ### widget
 | |
| 
 | |
| The position where the event occurred relative to the `GameWidget` position and size
 | |
| , same as `localPosition` in Flutter's native events.
 | |
| 
 | |
| ### game
 | |
| 
 | |
| The position where the event ocurred relative to the `GameWidget` and with any
 | |
| transformations that the game applied to the game (e.g. camera). If the game doesn't have any
 | |
| transformations, this will be equal to the `widget` attribute.
 | |
| 
 | |
| ## Example
 | |
| 
 | |
| ```dart
 | |
| class MyGame extends Game with TapDetector {
 | |
|   // Other methods omitted
 | |
| 
 | |
|   @override
 | |
|   void onTapDown(TapDownInfo event) {
 | |
|     print("Player tap down on ${event.eventPosition.game}");
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void onTapUp(TapUpInfo event) {
 | |
|     print("Player tap up on ${event.eventPosition.game}");
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| You can also check more complete examples
 | |
| [here](https://github.com/flame-engine/flame/tree/main/examples/lib/stories/controls/).
 | |
| 
 | |
| ## Tappable, Draggable and Hoverable components
 | |
| 
 | |
| Any component derived from `BaseComponent` (most components) can add the `Tappable`, the
 | |
| `Draggable`, and/or the `Hoverable` mixins to handle taps, drags and hovers 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`.
 | |
| 
 | |
| ### Tappable components
 | |
| 
 | |
| By adding the `HasTappableComponents` mixin to your game, and using the mixin `Tappable` on your
 | |
| components, you can override the following methods on your components:
 | |
| 
 | |
| ```dart
 | |
| void onTapCancel() {}
 | |
| void onTapDown(TapDownInfo event) {}
 | |
| void onTapUp(TapUpInfo event) {}
 | |
| ```
 | |
| 
 | |
| Minimal component example:
 | |
| 
 | |
| ```dart
 | |
| import 'package:flame/components.dart';
 | |
| 
 | |
| class TappableComponent extends PositionComponent with Tappable {
 | |
| 
 | |
|   // update and render omitted
 | |
| 
 | |
|   @override
 | |
|   void onTapUp(TapUpInfo event) {
 | |
|     print("tap up");
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void onTapDown(TapDownInfo event) {
 | |
|     print("tap down");
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void onTapCancel() {
 | |
|     print("tap cancel");
 | |
|   }
 | |
| }
 | |
| 
 | |
| class MyGame extends BaseGame with HasTappableComponents {
 | |
|   MyGame() {
 | |
|     add(TappableComponent());
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| **Note**: `HasTappableComponents` 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 `Tappable`, 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, DragUpdateInfo event) {}
 | |
|   void onDragEnd(int pointerId, DragEndInfo event) {}
 | |
|   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, stop tracking pointerId.
 | |
| 
 | |
| Minimal component example (this example ignores pointerId so it wont work well if you try to
 | |
| multi-drag):
 | |
| 
 | |
| ```dart
 | |
| import 'package:flame/components.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, DragUpdateInfo event) {
 | |
|     final localCoords = event.eventPosition.game;
 | |
|     position = localCoords - dragDeltaPosition;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   bool onDragEnd(int pointerId, DragEndInfo event) {
 | |
|     dragDeltaPosition = null;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   bool onDragCancel(int pointerId) {
 | |
|     dragDeltaPosition = null;
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| class MyGame extends BaseGame with HasDraggableComponents {
 | |
|   MyGame() {
 | |
|     add(DraggableComponent());
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| **Note**: `HasDraggableComponents` uses an advanced gesture detector under the hood and as explained
 | |
| further up on this page, shouldn't be used alongside basic detectors.
 | |
| 
 | |
| ### Hoverable components
 | |
| 
 | |
| Just like the others, this mixin allows for easy wiring of your component to listen to hover states
 | |
| and events.
 | |
| 
 | |
| By adding the `HasHoverableComponents` mixin to your base game, and by using the mixin `Hoverable` on
 | |
| your components, they get an `isHovered` field and a couple of methods (`onHoverStart`, `onHoverEnd`) that
 | |
| you can override if you want to listen to the events.
 | |
| 
 | |
| ```dart
 | |
|   bool isHovered = false;
 | |
|   void onHoverEnter(PointerHoverInfo event) {}
 | |
|   void onHoverLeave(PointerHoverInfo event) {}
 | |
| ```
 | |
| 
 | |
| The provided event info is from the mouse move that triggered the action (entering or leaving).
 | |
| While the mouse movement is kept inside or outside, no events are fired and those mouse move events are
 | |
| not propagated. Only when the state is changed the handlers are triggered.
 | |
| 
 | |
| ## 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/tree/main/examples/lib/stories/).
 | |
| 
 | |
| 
 |