diff --git a/README.md b/README.md index 2ac0476ad..3c58b60d8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -# -

flame diff --git a/doc/input.md b/doc/input.md index 3b633d811..4f2eb3ba6 100644 --- a/doc/input.md +++ b/doc/input.md @@ -316,7 +316,7 @@ Minimal example: ```dart import 'package:flame/game.dart'; -import 'package:flame/keyboard.dart'; +import 'package:flame/input.dart'; import 'package:flutter/services.dart'; class MyGame extends Game with KeyboardEvents { @@ -335,58 +335,17 @@ You can also check a more complete example ## Joystick -Flame provides a component capable of creating a virtual joystick for taking input for your game. It -can be configured with a variety 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 +Flame provides a component capable of creating a virtual joystick for taking input for your game. +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 implementation 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. +To receive the inputs from the joystick component, pass your `JoystickComponent` to the component +that you want it to control, or simply act upon input from it in the update-loop of your game. Check this example to get a better understanding: ```dart -import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; -import 'package:flame/joystick/joystick.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 - ), - ], - ); +class MyGame extends BaseGame with HasDraggableComponents { MyGame() { joystick.addObserver(player); @@ -395,77 +354,96 @@ class MyGame extends BaseGame with MultiTouchDragDetector { } @override - void onReceiveDrag(DragEvent drag) { - joystick.onReceiveDrag(drag); - super.onReceiveDrag(drag); + Future onLoad() async { + super.onLoad(); + final image = await images.load('joystick.png'); + final sheet = SpriteSheet.fromColumnsAndRows( + image: image, + columns: 6, + rows: 1, + ); + final joystick = JoystickComponent( + knob: SpriteComponent( + sprite: sheet.getSpriteById(1), + size: Vector2.all(100), + ), + background: SpriteComponent( + sprite: sheet.getSpriteById(0), + size: Vector2.all(150), + ), + margin: const EdgeInsets.only(left: 40, bottom: 40), + ); + + final player = Player(joystick); + add(player); + add(joystick); } } -class Player extends Component implements JoystickListener { +class JoystickPlayer extends SpriteComponent with HasGameRef { + /// Pixels/s + double maxSpeed = 300.0; - @override - void render(Canvas canvas) {} + final JoystickComponent joystick; - @override - void update(double dt) {} - - @override - void joystickAction(JoystickActionEvent event) { - // Do anything when click in action button. - print('Action: $event'); + JoystickPlayer(this.joystick) + : super( + size: Vector2.all(100.0), + ) { + anchor = Anchor.center; } @override - void joystickChangeDirectional(JoystickDirectionalEvent event) { - // Do anything when interact with directional. - print('Directional: $event'); + Future onLoad() async { + super.onLoad(); + sprite = await gameRef.loadSprite('layers/player.png'); + position = gameRef.size / 2; } + @override + void update(double dt) { + super.update(dt); + if (joystick.direction != JoystickDirection.idle) { + position.add(joystick.velocity * maxSpeed * dt); + angle = joystick.delta.screenAngle(); + } + } } - ``` -### JoystickDirectionalEvent +So in this example we create the classes `MyGame` and `Player`. `MyGame` creates a joystick which is +passed to the `Player` when it is created. In the `Player` class we act upon the current state of +the joystick. -```dart +The joystick has a few fields that change depending on what state it is in. +These are the fields that should be used to know the state of the joystick: + - `intensity`: The percentage [0.0, 1.0] that the knob is dragged from the epicenter to the edge of + the joystick (or `knobRadius` if that is set). + - `delta`: The absolute amount (defined as a `Vector2`) that the knob is dragged from its epicenter. + - `velocity`: The percentage, presented as a `Vector2`, and direction that the knob is currently + pulled from its base position to a edge of the joystick. -JoystickDirectionalEvent({ - JoystickMoveDirectional directional, - double intensity = 0.0, - double radAngle = 0.0, -}); +If you want to create buttons to go with your joystick, check out +[`MarginButtonComponent`](#HudButtonComponent). -enum JoystickMoveDirectional { - MOVE_UP, - MOVE_UP_LEFT, - MOVE_UP_RIGHT, - MOVE_RIGHT, - MOVE_DOWN, - MOVE_DOWN_RIGHT, - MOVE_DOWN_LEFT, - MOVE_LEFT, - IDLE -} +A full examples of how to use it can be found +[here](https://github.com/flame-engine/flame/tree/main/examples/lib/stories/controls/joystick). +And it can be seen running [here](https://examples.flame-engine.org/#/Controls_Joystick). -``` +## HudButtonComponent +A `HudButtonComponent` is a button that can be defined with margins to the edge of the `Viewport` +instead of with a position. It takes two `PositionComponent`s. `button` and `buttonDown`, the first +is used for when the button is idle and the second is shown when the button is being pressed. The +second one is optional if you don't want to change the look of the button when it is pressed, or if +you handle this through the `button` component. -### JoystickActionEvent +As the name suggests this button is a hud by default, which means that it will be static on your +screen even if the camera for the game moves around. You can also use this component as a non-hud by +setting `hudButtonComponent.isHud = false;`. -```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/main/examples/lib/stories/controls/joystick.dart). +If you want to act upon the button being pressed (which I guess that you do) you can either pass in +a callback function as the `onPressed` argument, or you extend the component and override +`onTapDown`, `onTapUp` and/or `onTapCancel` and implement your logic in there. ## Gamepad diff --git a/examples/lib/main.dart b/examples/lib/main.dart index 3485556f2..cb0db4775 100644 --- a/examples/lib/main.dart +++ b/examples/lib/main.dart @@ -5,8 +5,8 @@ import 'stories/animations/animations.dart'; import 'stories/camera_and_viewport/camera_and_viewport.dart'; import 'stories/collision_detection/collision_detection.dart'; import 'stories/components/components.dart'; -import 'stories/controls/controls.dart'; import 'stories/effects/effects.dart'; +import 'stories/input/input.dart'; import 'stories/parallax/parallax.dart'; import 'stories/rendering/rendering.dart'; import 'stories/sprites/sprites.dart'; @@ -25,7 +25,7 @@ void main() async { addCollisionDetectionStories(dashbook); addEffectsStories(dashbook); addTileMapStories(dashbook); - addControlsStories(dashbook); + addInputStories(dashbook); addSpritesStories(dashbook); addRenderingStories(dashbook); addUtilsStories(dashbook); diff --git a/examples/lib/stories/animations/animation_group.dart b/examples/lib/stories/animations/animation_group.dart index 2d8583ef0..abb97206b 100644 --- a/examples/lib/stories/animations/animation_group.dart +++ b/examples/lib/stories/animations/animation_group.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'package:flame/components.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; enum RobotState { idle, diff --git a/examples/lib/stories/animations/basic.dart b/examples/lib/stories/animations/basic.dart index 73cc35d5b..c64b6d27e 100644 --- a/examples/lib/stories/animations/basic.dart +++ b/examples/lib/stories/animations/basic.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'package:flame/components.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; class BasicAnimations extends BaseGame with TapDetector { late Image creature; diff --git a/examples/lib/stories/camera_and_viewport/fixed_resolution.dart b/examples/lib/stories/camera_and_viewport/fixed_resolution.dart index 6224069b1..74d7e43e9 100644 --- a/examples/lib/stories/camera_and_viewport/fixed_resolution.dart +++ b/examples/lib/stories/camera_and_viewport/fixed_resolution.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'package:flame/components.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame/palette.dart'; class FixedResolutionGame extends BaseGame with ScrollDetector, ScaleDetector { diff --git a/examples/lib/stories/camera_and_viewport/follow_object.dart b/examples/lib/stories/camera_and_viewport/follow_object.dart index 937c8c768..2692ebb71 100644 --- a/examples/lib/stories/camera_and_viewport/follow_object.dart +++ b/examples/lib/stories/camera_and_viewport/follow_object.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:flame/components.dart'; import 'package:flame/game.dart'; import 'package:flame/geometry.dart'; -import 'package:flame/keyboard.dart'; +import 'package:flame/input.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/examples/lib/stories/camera_and_viewport/zoom.dart b/examples/lib/stories/camera_and_viewport/zoom.dart index 86e96ed74..b9281ea69 100644 --- a/examples/lib/stories/camera_and_viewport/zoom.dart +++ b/examples/lib/stories/camera_and_viewport/zoom.dart @@ -1,6 +1,6 @@ import 'package:flame/components.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; class ZoomGame extends BaseGame with ScrollDetector, ScaleDetector { final Vector2 viewportResolution; diff --git a/examples/lib/stories/collision_detection/circles.dart b/examples/lib/stories/collision_detection/circles.dart index 0151cbbaa..c60605533 100644 --- a/examples/lib/stories/collision_detection/circles.dart +++ b/examples/lib/stories/collision_detection/circles.dart @@ -4,7 +4,7 @@ import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:flame/geometry.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flutter/material.dart' hide Image, Draggable; class MyCollidable extends PositionComponent diff --git a/examples/lib/stories/collision_detection/multiple_shapes.dart b/examples/lib/stories/collision_detection/multiple_shapes.dart index eb42856ee..fa2212003 100644 --- a/examples/lib/stories/collision_detection/multiple_shapes.dart +++ b/examples/lib/stories/collision_detection/multiple_shapes.dart @@ -5,7 +5,7 @@ import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:flame/geometry.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame/palette.dart'; import 'package:flutter/material.dart' hide Image, Draggable; diff --git a/examples/lib/stories/collision_detection/only_shapes.dart b/examples/lib/stories/collision_detection/only_shapes.dart index 5b7c86fdc..b48ced1f4 100644 --- a/examples/lib/stories/collision_detection/only_shapes.dart +++ b/examples/lib/stories/collision_detection/only_shapes.dart @@ -5,7 +5,7 @@ import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:flame/geometry.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame/palette.dart'; import 'package:flutter/material.dart' hide Image, Draggable; diff --git a/examples/lib/stories/components/priority.dart b/examples/lib/stories/components/priority.dart index 49cf4a7d2..6c4e67aa4 100644 --- a/examples/lib/stories/components/priority.dart +++ b/examples/lib/stories/components/priority.dart @@ -4,7 +4,7 @@ import 'dart:ui'; import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame/palette.dart'; class Square extends PositionComponent with HasGameRef, Tappable { diff --git a/examples/lib/stories/controls/advanced_joystick.dart b/examples/lib/stories/controls/advanced_joystick.dart deleted file mode 100644 index 39f78de1a..000000000 --- a/examples/lib/stories/controls/advanced_joystick.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flame/extensions.dart'; -import 'package:flame/game.dart'; -import 'package:flame/joystick.dart'; -import 'package:flutter/material.dart'; - -import 'joystick_player.dart'; - -class AdvancedJoystickGame extends BaseGame with HasDraggableComponents { - Future loadJoystick(int idx) async { - return loadSprite( - 'joystick.png', - srcPosition: Vector2(idx * 16.0, 0), - srcSize: Vector2.all(16), - ); - } - - @override - Future onLoad() async { - final joystick = JoystickComponent( - gameRef: this, - directional: JoystickDirectional( - background: JoystickElement.sprite(await loadJoystick(0)), - knob: JoystickElement.sprite(await loadJoystick(1)), - ), - actions: [ - JoystickAction( - actionId: 1, - margin: const EdgeInsets.all(50), - action: JoystickElement.sprite(await loadJoystick(2)), - actionPressed: JoystickElement.sprite(await loadJoystick(4)), - ), - JoystickAction( - actionId: 2, - action: JoystickElement.sprite(await loadJoystick(3)), - actionPressed: JoystickElement.sprite(await loadJoystick(5)), - margin: const EdgeInsets.only( - right: 50, - bottom: 120, - ), - ), - JoystickAction( - actionId: 3, - margin: const EdgeInsets.only(bottom: 50, right: 120), - enableDirection: true, - color: const Color(0xFFFF00FF), - opacityBackground: 0.1, - opacityKnob: 0.9, - ), - ], - ); - - final player = JoystickPlayer(); - joystick.addObserver(player); - - add(player); - add(joystick); - } -} diff --git a/examples/lib/stories/controls/joystick.dart b/examples/lib/stories/controls/joystick.dart deleted file mode 100644 index fae09ba41..000000000 --- a/examples/lib/stories/controls/joystick.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flame/game.dart'; -import 'package:flame/joystick.dart'; -import 'package:flame/extensions.dart'; -import 'package:flutter/material.dart'; - -import 'joystick_player.dart'; - -class JoystickGame extends BaseGame with HasDraggableComponents { - @override - Future onLoad() async { - final joystick = JoystickComponent( - gameRef: this, - directional: JoystickDirectional(), - actions: [ - JoystickAction( - actionId: 1, - margin: const EdgeInsets.all(50), - color: const Color(0xFF0000FF), - ), - JoystickAction( - actionId: 2, - color: const Color(0xFF00FF00), - margin: const EdgeInsets.only( - right: 50, - bottom: 120, - ), - ), - JoystickAction( - actionId: 3, - margin: const EdgeInsets.only(bottom: 50, right: 120), - enableDirection: true, - ), - ], - ); - - final player = JoystickPlayer(); - joystick.addObserver(player); - - add(player); - add(joystick); - } -} diff --git a/examples/lib/stories/controls/joystick_player.dart b/examples/lib/stories/controls/joystick_player.dart deleted file mode 100644 index 471f94fa1..000000000 --- a/examples/lib/stories/controls/joystick_player.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'dart:math'; - -import 'package:flame/components.dart'; -import 'package:flame/game.dart'; -import 'package:flame/joystick.dart'; -import 'package:flame/extensions.dart'; -import 'package:flame/palette.dart'; -import 'package:flutter/material.dart'; - -final _whitePaint = BasicPalette.white.paint(); -final _bluePaint = BasicPalette.blue.paint(); -final _greenPaint = BasicPalette.green.paint(); - -class JoystickPlayer extends PositionComponent implements JoystickListener { - static const speed = 64.0; - - double currentSpeed = 0; - bool isMoving = false; - Paint paint; - late Rect rect; - - JoystickPlayer() - : paint = _whitePaint, - super( - size: Vector2.all(50.0), - anchor: Anchor.center, - ) { - rect = size.toRect(); - } - - @override - void render(Canvas canvas) { - super.render(canvas); - canvas.drawRect(rect, paint); - } - - @override - void update(double dt) { - super.update(dt); - if (isMoving) { - moveFromAngle(dt); - } - } - - @override - void onGameResize(Vector2 gameSize) { - super.onGameResize(gameSize); - position = gameSize / 2; - } - - @override - void joystickAction(JoystickActionEvent event) { - switch (event.event) { - case ActionEvent.down: - switch (event.id) { - case 1: - paint = paint == _whitePaint ? _bluePaint : _whitePaint; - break; - case 2: - paint = paint == _whitePaint ? _greenPaint : _whitePaint; - break; - } - break; - case ActionEvent.move: - if (event.id == 3) { - angle = event.angle; - } - break; - default: - // Do nothing - } - } - - @override - void joystickChangeDirectional(JoystickDirectionalEvent event) { - isMoving = event.directional != JoystickMoveDirectional.idle; - if (isMoving) { - angle = event.angle; - currentSpeed = speed * event.intensity; - } - } - - void moveFromAngle(double dt) { - final delta = Vector2(cos(angle), sin(angle)) * (currentSpeed * dt); - position.add(delta); - } -} diff --git a/examples/lib/stories/effects/combined_effect.dart b/examples/lib/stories/effects/combined_effect.dart index 4fd5d2256..ad08a088e 100644 --- a/examples/lib/stories/effects/combined_effect.dart +++ b/examples/lib/stories/effects/combined_effect.dart @@ -1,7 +1,7 @@ import 'package:flame/effects.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flutter/material.dart'; import '../../commons/square_component.dart'; diff --git a/examples/lib/stories/effects/infinite_effect.dart b/examples/lib/stories/effects/infinite_effect.dart index b5d7e619b..e27ddc27a 100644 --- a/examples/lib/stories/effects/infinite_effect.dart +++ b/examples/lib/stories/effects/infinite_effect.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:flame/effects.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flutter/material.dart'; import '../../commons/square_component.dart'; diff --git a/examples/lib/stories/effects/move_effect.dart b/examples/lib/stories/effects/move_effect.dart index 00fbc5c61..d0739d045 100644 --- a/examples/lib/stories/effects/move_effect.dart +++ b/examples/lib/stories/effects/move_effect.dart @@ -1,7 +1,7 @@ import 'package:flame/effects.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flutter/material.dart'; import '../../commons/square_component.dart'; diff --git a/examples/lib/stories/effects/rotate_effect.dart b/examples/lib/stories/effects/rotate_effect.dart index 0a19a0cea..b5aa1122a 100644 --- a/examples/lib/stories/effects/rotate_effect.dart +++ b/examples/lib/stories/effects/rotate_effect.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:flame/components.dart'; import 'package:flame/effects.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flutter/material.dart'; import '../../commons/square_component.dart'; diff --git a/examples/lib/stories/effects/scale_effect.dart b/examples/lib/stories/effects/scale_effect.dart index 9f996b26e..507faa53a 100644 --- a/examples/lib/stories/effects/scale_effect.dart +++ b/examples/lib/stories/effects/scale_effect.dart @@ -2,7 +2,7 @@ import 'package:flame/components.dart'; import 'package:flame/effects.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flutter/material.dart'; import '../../commons/square_component.dart'; diff --git a/examples/lib/stories/effects/sequence_effect.dart b/examples/lib/stories/effects/sequence_effect.dart index cc09a0c7a..5ee172d12 100644 --- a/examples/lib/stories/effects/sequence_effect.dart +++ b/examples/lib/stories/effects/sequence_effect.dart @@ -1,7 +1,7 @@ import 'package:flame/effects.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flutter/material.dart'; import '../../commons/square_component.dart'; diff --git a/examples/lib/stories/controls/draggables.dart b/examples/lib/stories/input/draggables.dart similarity index 97% rename from examples/lib/stories/controls/draggables.dart rename to examples/lib/stories/input/draggables.dart index 2914a3c45..fef8e7170 100644 --- a/examples/lib/stories/controls/draggables.dart +++ b/examples/lib/stories/input/draggables.dart @@ -1,7 +1,7 @@ import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flutter/material.dart' show Colors; // Note: this component does not consider the possibility of multiple diff --git a/examples/lib/stories/controls/hoverables.dart b/examples/lib/stories/input/hoverables.dart similarity index 96% rename from examples/lib/stories/controls/hoverables.dart rename to examples/lib/stories/input/hoverables.dart index dd23112e8..5919eff77 100644 --- a/examples/lib/stories/controls/hoverables.dart +++ b/examples/lib/stories/input/hoverables.dart @@ -1,8 +1,8 @@ import 'package:flame/components.dart'; -import 'package:flame/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flame/game.dart'; import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flame/input.dart'; +import 'package:flutter/material.dart'; class HoverableSquare extends PositionComponent with Hoverable { static final Paint _white = Paint()..color = const Color(0xFFFFFFFF); diff --git a/examples/lib/stories/controls/controls.dart b/examples/lib/stories/input/input.dart similarity index 66% rename from examples/lib/stories/controls/controls.dart rename to examples/lib/stories/input/input.dart index 5a1a7502b..260df7b9e 100644 --- a/examples/lib/stories/controls/controls.dart +++ b/examples/lib/stories/input/input.dart @@ -2,10 +2,10 @@ import 'package:dashbook/dashbook.dart'; import 'package:flame/game.dart'; import '../../commons/commons.dart'; -import 'advanced_joystick.dart'; import 'draggables.dart'; import 'hoverables.dart'; import 'joystick.dart'; +import 'joystick_advanced.dart'; import 'keyboard.dart'; import 'mouse_movement.dart'; import 'multitap.dart'; @@ -14,42 +14,42 @@ import 'overlapping_tappables.dart'; import 'scroll.dart'; import 'tappables.dart'; -void addControlsStories(Dashbook dashbook) { - dashbook.storiesOf('Controls') +void addInputStories(Dashbook dashbook) { + dashbook.storiesOf('Input') ..add( 'Keyboard', (_) => GameWidget(game: KeyboardGame()), - codeLink: baseLink('controls/keyboard.dart'), + codeLink: baseLink('input/keyboard.dart'), ) ..add( 'Mouse Movement', (_) => GameWidget(game: MouseMovementGame()), - codeLink: baseLink('controls/mouse_movement.dart'), + codeLink: baseLink('input/mouse_movement.dart'), ) ..add( 'Scroll', (_) => GameWidget(game: ScrollGame()), - codeLink: baseLink('controls/scroll.dart'), + codeLink: baseLink('input/scroll.dart'), ) ..add( 'Multitap', (_) => GameWidget(game: MultitapGame()), - codeLink: baseLink('controls/multitap.dart'), + codeLink: baseLink('input/multitap.dart'), ) ..add( 'Multitap Advanced', (_) => GameWidget(game: MultitapAdvancedGame()), - codeLink: baseLink('controls/multitap_advanced.dart'), + codeLink: baseLink('input/multitap_advanced.dart'), ) ..add( 'Tappables', (_) => GameWidget(game: TappablesGame()), - codeLink: baseLink('controls/tappables.dart'), + codeLink: baseLink('input/tappables.dart'), ) ..add( 'Overlaping Tappables', (_) => GameWidget(game: OverlappingTappablesGame()), - codeLink: baseLink('controls/overlaping_tappables.dart'), + codeLink: baseLink('input/overlaping_tappables.dart'), ) ..add( 'Draggables', @@ -60,22 +60,22 @@ void addControlsStories(Dashbook dashbook) { ), ); }, - codeLink: baseLink('controls/draggables.dart'), + codeLink: baseLink('input/draggables.dart'), ) ..add( 'Hoverables', (_) => GameWidget(game: HoverablesGame()), - codeLink: baseLink('controls/hoverables.dart'), + codeLink: baseLink('input/hoverables.dart'), info: 'Add more squares by clicking. Hover squares to change colors.', ) ..add( 'Joystick', (_) => GameWidget(game: JoystickGame()), - codeLink: baseLink('controls/joystick.dart'), + codeLink: baseLink('input/joystick.dart'), ) ..add( 'Joystick Advanced', - (_) => GameWidget(game: AdvancedJoystickGame()), - codeLink: baseLink('controls/advanced_joystick.dart'), + (_) => GameWidget(game: JoystickAdvancedGame()), + codeLink: baseLink('input/joystick_advanced.dart'), ); } diff --git a/examples/lib/stories/input/joystick.dart b/examples/lib/stories/input/joystick.dart new file mode 100644 index 000000000..e2891d731 --- /dev/null +++ b/examples/lib/stories/input/joystick.dart @@ -0,0 +1,28 @@ +import 'package:flame/components.dart'; +import 'package:flame/game.dart'; +import 'package:flame/geometry.dart'; +import 'package:flame/input.dart'; +import 'package:flame/palette.dart'; +import 'package:flutter/painting.dart'; + +import 'joystick_player.dart'; + +class JoystickGame extends BaseGame with HasDraggableComponents { + late final JoystickPlayer player; + late final JoystickComponent joystick; + + @override + Future onLoad() async { + final knobPaint = BasicPalette.blue.withAlpha(200).paint(); + final backgroundPaint = BasicPalette.blue.withAlpha(100).paint(); + joystick = JoystickComponent( + knob: Circle(radius: 30).toComponent(paint: knobPaint), + background: Circle(radius: 100).toComponent(paint: backgroundPaint), + margin: const EdgeInsets.only(left: 40, bottom: 40), + ); + player = JoystickPlayer(joystick); + + add(player); + add(joystick); + } +} diff --git a/examples/lib/stories/input/joystick_advanced.dart b/examples/lib/stories/input/joystick_advanced.dart new file mode 100644 index 000000000..887e73d44 --- /dev/null +++ b/examples/lib/stories/input/joystick_advanced.dart @@ -0,0 +1,145 @@ +import 'dart:math'; + +import 'package:flame/components.dart'; +import 'package:flame/effects.dart'; +import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flame/geometry.dart'; +import 'package:flame/input.dart'; +import 'package:flame/palette.dart'; +import 'package:flame/sprite.dart'; +import 'package:flutter/animation.dart'; +import 'package:flutter/painting.dart'; + +import 'joystick_player.dart'; + +class JoystickAdvancedGame extends BaseGame + with HasDraggableComponents, HasTappableComponents { + late final JoystickPlayer player; + late final JoystickComponent joystick; + late final TextComponent speedText; + late final TextComponent directionText; + + @override + Future onLoad() async { + final image = await images.load('joystick.png'); + final sheet = SpriteSheet.fromColumnsAndRows( + image: image, + columns: 6, + rows: 1, + ); + joystick = JoystickComponent( + knob: SpriteComponent( + sprite: sheet.getSpriteById(1), + size: Vector2.all(100), + ), + background: SpriteComponent( + sprite: sheet.getSpriteById(0), + size: Vector2.all(150), + ), + margin: const EdgeInsets.only(left: 40, bottom: 40), + ); + player = JoystickPlayer(joystick); + + final buttonSize = Vector2.all(80); + // A button with margin from the edge of the viewport that flips the + // rendering of the player on the X-axis. + final flipButton = HudButtonComponent( + button: SpriteComponent( + sprite: sheet.getSpriteById(2), + size: buttonSize, + ), + buttonDown: SpriteComponent( + sprite: sheet.getSpriteById(4), + size: buttonSize, + ), + margin: const EdgeInsets.only( + right: 80, + bottom: 60, + ), + onPressed: () => player.renderFlipX = !player.renderFlipX, + ); + + // A button with margin from the edge of the viewport that flips the + // rendering of the player on the Y-axis. + final flopButton = HudButtonComponent( + button: SpriteComponent( + sprite: sheet.getSpriteById(3), + size: buttonSize, + ), + buttonDown: SpriteComponent( + sprite: sheet.getSpriteById(5), + size: buttonSize, + ), + margin: const EdgeInsets.only( + right: 160, + bottom: 60, + ), + onPressed: () => player.renderFlipY = !player.renderFlipY, + ); + + final rotateEffect = RotateEffect( + angle: 0, + curve: Curves.bounceOut, + isAlternating: true, + speed: 2, + ); + final rng = Random(); + // A button, created from a shape, that adds a rotation effect to the player + // when it is pressed. + final shapeButton = HudButtonComponent( + button: Circle(radius: 35).toComponent(paint: BasicPalette.white.paint()), + buttonDown: Rectangle(size: buttonSize) + .toComponent(paint: BasicPalette.blue.paint()), + margin: const EdgeInsets.only( + right: 85, + bottom: 150, + ), + onPressed: () => player.addEffect( + rotateEffect..angle = 8 * rng.nextDouble(), + ), + ); + + final _regularTextConfig = TextPaintConfig(color: BasicPalette.white.color); + final _regular = TextPaint(config: _regularTextConfig); + speedText = TextComponent( + 'Speed: 0', + textRenderer: _regular, + )..isHud = true; + directionText = TextComponent( + 'Direction: idle', + textRenderer: _regular, + )..isHud = true; + + final speedWithMargin = HudMarginComponent( + margin: const EdgeInsets.only( + top: 80, + left: 80, + ), + )..addChild(speedText); + + final directionWithMargin = HudMarginComponent( + margin: const EdgeInsets.only( + top: 110, + left: 80, + ), + )..addChild(directionText); + + add(player); + add(joystick); + add(flipButton); + add(flopButton); + add(shapeButton); + add(speedWithMargin); + add(directionWithMargin); + } + + @override + void update(double dt) { + super.update(dt); + speedText.text = 'Speed: ${(joystick.intensity * player.maxSpeed).round()}'; + final direction = + joystick.direction.toString().replaceAll('JoystickDirection.', ''); + directionText.text = 'Direction: $direction'; + } +} diff --git a/examples/lib/stories/input/joystick_player.dart b/examples/lib/stories/input/joystick_player.dart new file mode 100644 index 000000000..182ecb3e4 --- /dev/null +++ b/examples/lib/stories/input/joystick_player.dart @@ -0,0 +1,32 @@ +import 'package:flame/components.dart'; +import 'package:flame/game.dart'; + +class JoystickPlayer extends SpriteComponent with HasGameRef { + /// Pixels/s + double maxSpeed = 300.0; + + final JoystickComponent joystick; + + JoystickPlayer(this.joystick) + : super( + size: Vector2.all(100.0), + ) { + anchor = Anchor.center; + } + + @override + Future onLoad() async { + super.onLoad(); + sprite = await gameRef.loadSprite('layers/player.png'); + position = gameRef.size / 2; + } + + @override + void update(double dt) { + super.update(dt); + if (!joystick.delta.isZero()) { + position.add(joystick.relativeDelta * maxSpeed * dt); + angle = joystick.delta.screenAngle(); + } + } +} diff --git a/examples/lib/stories/controls/keyboard.dart b/examples/lib/stories/input/keyboard.dart similarity index 96% rename from examples/lib/stories/controls/keyboard.dart rename to examples/lib/stories/input/keyboard.dart index c37bae05c..39eafd32a 100644 --- a/examples/lib/stories/controls/keyboard.dart +++ b/examples/lib/stories/input/keyboard.dart @@ -1,7 +1,7 @@ import 'dart:ui'; import 'package:flame/game.dart'; -import 'package:flame/keyboard.dart'; +import 'package:flame/input.dart'; import 'package:flame/palette.dart'; import 'package:flutter/services.dart' show RawKeyDownEvent, RawKeyEvent; diff --git a/examples/lib/stories/controls/mouse_movement.dart b/examples/lib/stories/input/mouse_movement.dart similarity index 96% rename from examples/lib/stories/controls/mouse_movement.dart rename to examples/lib/stories/input/mouse_movement.dart index 45e67da3e..cd20853c9 100644 --- a/examples/lib/stories/controls/mouse_movement.dart +++ b/examples/lib/stories/input/mouse_movement.dart @@ -1,6 +1,6 @@ import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame/palette.dart'; import 'package:flutter/material.dart'; diff --git a/examples/lib/stories/controls/multitap.dart b/examples/lib/stories/input/multitap.dart similarity index 95% rename from examples/lib/stories/controls/multitap.dart rename to examples/lib/stories/input/multitap.dart index b726df043..395fb13a0 100644 --- a/examples/lib/stories/controls/multitap.dart +++ b/examples/lib/stories/input/multitap.dart @@ -1,6 +1,6 @@ import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame/palette.dart'; import 'package:flutter/material.dart'; diff --git a/examples/lib/stories/controls/multitap_advanced.dart b/examples/lib/stories/input/multitap_advanced.dart similarity index 97% rename from examples/lib/stories/controls/multitap_advanced.dart rename to examples/lib/stories/input/multitap_advanced.dart index 85c4f8dcd..35e97a928 100644 --- a/examples/lib/stories/controls/multitap_advanced.dart +++ b/examples/lib/stories/input/multitap_advanced.dart @@ -1,6 +1,6 @@ import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame/palette.dart'; import 'package:flutter/material.dart'; diff --git a/examples/lib/stories/controls/overlapping_tappables.dart b/examples/lib/stories/input/overlapping_tappables.dart similarity index 100% rename from examples/lib/stories/controls/overlapping_tappables.dart rename to examples/lib/stories/input/overlapping_tappables.dart diff --git a/examples/lib/stories/controls/scroll.dart b/examples/lib/stories/input/scroll.dart similarity index 96% rename from examples/lib/stories/controls/scroll.dart rename to examples/lib/stories/input/scroll.dart index 7aaf360e4..25e30a380 100644 --- a/examples/lib/stories/controls/scroll.dart +++ b/examples/lib/stories/input/scroll.dart @@ -1,8 +1,8 @@ -import 'package:flutter/material.dart'; -import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; -import 'package:flame/palette.dart'; import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flame/input.dart'; +import 'package:flame/palette.dart'; +import 'package:flutter/material.dart'; class ScrollGame extends BaseGame with ScrollDetector { static const speed = 2000.0; diff --git a/examples/lib/stories/controls/tappables.dart b/examples/lib/stories/input/tappables.dart similarity index 100% rename from examples/lib/stories/controls/tappables.dart rename to examples/lib/stories/input/tappables.dart diff --git a/examples/lib/stories/tile_maps/isometric_tile_map.dart b/examples/lib/stories/tile_maps/isometric_tile_map.dart index b9c79f807..7fe1c5990 100644 --- a/examples/lib/stories/tile_maps/isometric_tile_map.dart +++ b/examples/lib/stories/tile_maps/isometric_tile_map.dart @@ -3,7 +3,7 @@ import 'dart:ui'; import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame/sprite.dart'; import 'package:flutter/material.dart' hide Image; diff --git a/examples/lib/stories/utils/timer.dart b/examples/lib/stories/utils/timer.dart index dd9056c92..45fc14b4b 100644 --- a/examples/lib/stories/utils/timer.dart +++ b/examples/lib/stories/utils/timer.dart @@ -1,7 +1,7 @@ -import 'package:flutter/material.dart'; import 'package:flame/game.dart'; +import 'package:flame/input.dart'; import 'package:flame/timer.dart'; -import 'package:flame/gestures.dart'; +import 'package:flutter/material.dart'; class TimerGame extends Game with TapDetector { final TextPaint textConfig = TextPaint( diff --git a/examples/lib/stories/utils/timer_component.dart b/examples/lib/stories/utils/timer_component.dart index c59e01558..2dc9e19d5 100644 --- a/examples/lib/stories/utils/timer_component.dart +++ b/examples/lib/stories/utils/timer_component.dart @@ -1,7 +1,7 @@ -import 'package:flutter/material.dart'; import 'package:flame/game.dart'; +import 'package:flame/input.dart'; import 'package:flame/timer.dart'; -import 'package:flame/gestures.dart'; +import 'package:flutter/material.dart'; class RenderedTimeComponent extends TimerComponent { final TextPaint textPaint = TextPaint( diff --git a/examples/lib/stories/widgets/overlay.dart b/examples/lib/stories/widgets/overlay.dart index f9574ca2b..69ba41a99 100644 --- a/examples/lib/stories/widgets/overlay.dart +++ b/examples/lib/stories/widgets/overlay.dart @@ -1,6 +1,6 @@ import 'package:dashbook/dashbook.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame/palette.dart'; import 'package:flutter/material.dart'; diff --git a/packages/flame/CHANGELOG.md b/packages/flame/CHANGELOG.md index 0496b3054..a0a4e89e1 100644 --- a/packages/flame/CHANGELOG.md +++ b/packages/flame/CHANGELOG.md @@ -1,6 +1,10 @@ # CHANGELOG -## [next] +## [Next] + - Reset effects after they are done so that they can be repeated + - Remove integrated joystick buttons + - Add `MarginHudComponent`, used when components need to have a margin to the viewport edge + - Refactor `JoystickComponent` - Add `SpriteAnimationWidget.asset` - Add `SpriteWidget.asset` - Add `SpriteButton.asset` diff --git a/packages/flame/example/lib/main.dart b/packages/flame/example/lib/main.dart index dc2c0f770..4bb2f4a93 100644 --- a/packages/flame/example/lib/main.dart +++ b/packages/flame/example/lib/main.dart @@ -4,7 +4,7 @@ import 'dart:ui'; import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame/palette.dart'; import 'package:flutter/material.dart'; diff --git a/packages/flame/lib/components.dart b/packages/flame/lib/components.dart index e1e89970b..499db8f02 100644 --- a/packages/flame/lib/components.dart +++ b/packages/flame/lib/components.dart @@ -1,8 +1,8 @@ -export 'joystick.dart'; export 'src/anchor.dart'; export 'src/components/base_component.dart'; export 'src/components/component.dart'; export 'src/components/component_set.dart'; +export 'src/components/input/joystick_component.dart'; export 'src/components/isometric_tile_map_component.dart'; export 'src/components/mixins/collidable.dart'; export 'src/components/mixins/draggable.dart'; diff --git a/packages/flame/lib/gestures.dart b/packages/flame/lib/gestures.dart deleted file mode 100644 index c7a15ea6e..000000000 --- a/packages/flame/lib/gestures.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'src/gestures/detectors.dart'; -export 'src/gestures/events.dart'; diff --git a/packages/flame/lib/input.dart b/packages/flame/lib/input.dart new file mode 100644 index 000000000..369f27ad1 --- /dev/null +++ b/packages/flame/lib/input.dart @@ -0,0 +1,7 @@ +export 'src/components/input/hud_button_component.dart'; +export 'src/components/input/hud_margin_component.dart'; +export 'src/components/input/joystick_component.dart'; +export 'src/extensions/vector2.dart'; +export 'src/game/mixins/keyboard.dart'; +export 'src/gestures/detectors.dart'; +export 'src/gestures/events.dart'; diff --git a/packages/flame/lib/joystick.dart b/packages/flame/lib/joystick.dart deleted file mode 100644 index 94d28890d..000000000 --- a/packages/flame/lib/joystick.dart +++ /dev/null @@ -1,6 +0,0 @@ -export 'src/components/joystick/joystick_action.dart'; -export 'src/components/joystick/joystick_component.dart'; -export 'src/components/joystick/joystick_directional.dart'; -export 'src/components/joystick/joystick_element.dart'; -export 'src/components/joystick/joystick_events.dart'; -export 'src/sprite.dart'; diff --git a/packages/flame/lib/keyboard.dart b/packages/flame/lib/keyboard.dart deleted file mode 100644 index 16c5a53d3..000000000 --- a/packages/flame/lib/keyboard.dart +++ /dev/null @@ -1 +0,0 @@ -export 'src/keyboard.dart'; diff --git a/packages/flame/lib/src/components/base_component.dart b/packages/flame/lib/src/components/base_component.dart index e6b0e7fb6..02a2d464e 100644 --- a/packages/flame/lib/src/components/base_component.dart +++ b/packages/flame/lib/src/components/base_component.dart @@ -3,7 +3,7 @@ import 'dart:ui'; import 'package:meta/meta.dart'; import '../../game.dart'; -import '../../gestures.dart'; +import '../../input.dart'; import '../effects/effects.dart'; import '../effects/effects_handler.dart'; import '../extensions/vector2.dart'; diff --git a/packages/flame/lib/src/components/input/hud_button_component.dart b/packages/flame/lib/src/components/input/hud_button_component.dart new file mode 100644 index 000000000..ae994b636 --- /dev/null +++ b/packages/flame/lib/src/components/input/hud_button_component.dart @@ -0,0 +1,66 @@ +import 'package:flutter/rendering.dart' show EdgeInsets, VoidCallback; +import 'package:meta/meta.dart'; + +import '../../../components.dart'; +import '../../../extensions.dart'; +import '../../../input.dart'; +import '../../gestures/events.dart'; + +class HudButtonComponent extends HudMarginComponent with Tappable { + late final PositionComponent button; + late final PositionComponent? buttonDown; + + /// Callback for what should happen when the button is pressed. + /// If you want to interact with [onTapUp] or [onTapCancel] it is recommended + /// to extend [HudButtonComponent]. + VoidCallback? onPressed; + + HudButtonComponent({ + required this.button, + this.buttonDown, + EdgeInsets? margin, + Vector2? position, + Vector2? size, + Anchor anchor = Anchor.center, + this.onPressed, + }) : super( + margin: margin, + position: position, + size: size ?? button.size, + anchor: anchor, + ); + + @override + Future onLoad() async { + await super.onLoad(); + addChild(button); + } + + @override + @mustCallSuper + bool onTapDown(TapDownInfo info) { + if (buttonDown != null) { + children.remove(button); + addChild(buttonDown!); + } + onPressed?.call(); + return false; + } + + @override + @mustCallSuper + bool onTapUp(TapUpInfo info) { + onTapCancel(); + return true; + } + + @override + @mustCallSuper + bool onTapCancel() { + if (buttonDown != null) { + children.remove(buttonDown!); + addChild(button); + } + return false; + } +} diff --git a/packages/flame/lib/src/components/input/hud_margin_component.dart b/packages/flame/lib/src/components/input/hud_margin_component.dart new file mode 100644 index 000000000..f575bfe2a --- /dev/null +++ b/packages/flame/lib/src/components/input/hud_margin_component.dart @@ -0,0 +1,65 @@ +import 'package:flutter/widgets.dart' show EdgeInsets; +import 'package:meta/meta.dart'; + +import '../../../components.dart'; +import '../../../extensions.dart'; + +class HudMarginComponent extends PositionComponent with HasGameRef { + @override + bool isHud = true; + + /// Instead of setting a position of the [HudMarginComponent] a margin + /// from the edges of the viewport can be used instead. + EdgeInsets? margin; + + HudMarginComponent({ + this.margin, + Vector2? position, + Vector2? size, + Anchor anchor = Anchor.topLeft, + }) : assert( + margin != null || position != null, + 'Either margin or position must be defined', + ), + super( + size: size, + position: position, + anchor: anchor, + ); + + @override + @mustCallSuper + Future onLoad() async { + super.onLoad(); + if (margin != null) { + final margin = this.margin!; + final x = margin.left != 0 + ? margin.left + size.x / 2 + : gameRef.viewport.effectiveSize.x - margin.right - size.x / 2; + final y = margin.top != 0 + ? margin.top + size.y / 2 + : gameRef.viewport.effectiveSize.y - margin.bottom - size.y / 2; + position.setValues(x, y); + position = Anchor.center.toOtherAnchorPosition(center, anchor, size); + } else { + final topLeft = gameRef.viewport.effectiveSize - + anchor.toOtherAnchorPosition( + position, + Anchor.topLeft, + size, + ); + final bottomRight = gameRef.viewport.effectiveSize - + anchor.toOtherAnchorPosition( + position, + Anchor.bottomRight, + size, + ); + margin = EdgeInsets.fromLTRB( + topLeft.x, + topLeft.y, + bottomRight.x, + bottomRight.y, + ); + } + } +} diff --git a/packages/flame/lib/src/components/input/joystick_component.dart b/packages/flame/lib/src/components/input/joystick_component.dart new file mode 100644 index 000000000..8909126c1 --- /dev/null +++ b/packages/flame/lib/src/components/input/joystick_component.dart @@ -0,0 +1,154 @@ +import 'dart:math'; + +import 'package:flutter/rendering.dart' show EdgeInsets; + +import '../../../components.dart'; +import '../../../extensions.dart'; +import '../../gestures/events.dart'; +import 'hud_margin_component.dart'; + +enum JoystickDirection { + up, + upLeft, + upRight, + right, + down, + downRight, + downLeft, + left, + idle, +} + +class JoystickComponent extends HudMarginComponent with Draggable { + late final PositionComponent knob; + late final PositionComponent? background; + + /// The percentage [0.0, 1.0] the knob is dragged from the center to the edge. + double intensity = 0.0; + + /// The amount the knob is dragged from the center. + Vector2 delta = Vector2.zero(); + + /// The percentage, presented as a [Vector2], and direction that the knob is + /// currently pulled from its base position to a edge of the joystick. + Vector2 get relativeDelta => delta / knobRadius; + + /// The radius from the center of the knob to the edge of as far as the knob + /// can be dragged. + late double knobRadius; + + /// The position where the knob rests. + late Vector2 _baseKnobPosition; + + JoystickComponent({ + required this.knob, + this.background, + EdgeInsets? margin, + Vector2? position, + double? size, + double? knobRadius, + Anchor anchor = Anchor.center, + }) : assert( + size != null || background != null, + 'Either size or background must be defined', + ), + assert( + knob.position.isZero() && (background?.position.isZero() ?? true), + 'Positions should not be set for the knob or the background', + ), + super( + margin: margin, + position: position, + size: background?.size ?? Vector2.all(size ?? 0), + anchor: anchor, + ) { + this.knobRadius = knobRadius ?? this.size.x / 2; + } + + @override + Future onLoad() async { + await super.onLoad(); + knob.anchor = Anchor.center; + knob.position.add(size / 2); + _baseKnobPosition = knob.position.clone(); + if (background != null) { + addChild(background!); + } + addChild(knob); + } + + @override + void update(double dt) { + super.update(dt); + final knobRadius2 = knobRadius * knobRadius; + if (delta.isZero() && _baseKnobPosition != knob.position) { + knob.position = _baseKnobPosition; + } else if (delta.length2 > knobRadius2) { + delta.scaleTo(knobRadius); + } + if (!delta.isZero()) { + knob.position + ..setFrom(_baseKnobPosition) + ..add(delta); + } + intensity = delta.length2 / knobRadius2; + } + + @override + bool onDragStart(int pointerId, DragStartInfo info) { + return false; + } + + @override + bool onDragUpdate(_, DragUpdateInfo info) { + delta.add(info.delta.global); + return false; + } + + @override + bool onDragEnd(int id, __) { + onDragCancel(id); + return false; + } + + @override + bool onDragCancel(_) { + delta.setZero(); + return false; + } + + static const double _eighthOfPi = pi / 8; + + JoystickDirection get direction { + if (delta.isZero()) { + return JoystickDirection.idle; + } + + var knobAngle = delta.screenAngle(); + // Since screenAngle and angleTo doesn't care about "direction" of the angle + // we have to use angleToSigned and create an only increasing angle by + // removing negative angles from 2*pi. + knobAngle = knobAngle < 0 ? 2 * pi + knobAngle : knobAngle; + if (knobAngle >= 0 && knobAngle <= _eighthOfPi) { + return JoystickDirection.up; + } else if (knobAngle > 1 * _eighthOfPi && knobAngle <= 3 * _eighthOfPi) { + return JoystickDirection.upRight; + } else if (knobAngle > 3 * _eighthOfPi && knobAngle <= 5 * _eighthOfPi) { + return JoystickDirection.right; + } else if (knobAngle > 5 * _eighthOfPi && knobAngle <= 7 * _eighthOfPi) { + return JoystickDirection.downRight; + } else if (knobAngle > 7 * _eighthOfPi && knobAngle <= 9 * _eighthOfPi) { + return JoystickDirection.down; + } else if (knobAngle > 9 * _eighthOfPi && knobAngle <= 11 * _eighthOfPi) { + return JoystickDirection.downLeft; + } else if (knobAngle > 11 * _eighthOfPi && knobAngle <= 13 * _eighthOfPi) { + return JoystickDirection.left; + } else if (knobAngle > 13 * _eighthOfPi && knobAngle <= 15 * _eighthOfPi) { + return JoystickDirection.upLeft; + } else if (knobAngle > 15 * _eighthOfPi) { + return JoystickDirection.up; + } else { + return JoystickDirection.idle; + } + } +} diff --git a/packages/flame/lib/src/components/joystick/joystick_action.dart b/packages/flame/lib/src/components/joystick/joystick_action.dart deleted file mode 100644 index 29b59ff6c..000000000 --- a/packages/flame/lib/src/components/joystick/joystick_action.dart +++ /dev/null @@ -1,228 +0,0 @@ -import 'dart:math'; -import 'dart:ui'; - -import 'package:flutter/material.dart' show Colors; -import 'package:flutter/widgets.dart' show EdgeInsets; - -import '../../../components.dart'; -import '../../../game.dart'; -import '../../extensions/offset.dart'; -import '../../extensions/rect.dart'; -import '../../extensions/vector2.dart'; -import '../../gestures/events.dart'; -import 'joystick_component.dart'; -import 'joystick_element.dart'; -import 'joystick_events.dart'; - -enum JoystickActionAlign { topLeft, bottomLeft, topRight, bottomRight } - -class JoystickAction extends BaseComponent with Draggable, HasGameRef { - @override - bool isHud = true; - - final int actionId; - final double size; - final double _sizeBackgroundDirection; - final EdgeInsets margin; - final JoystickActionAlign align; - final bool enableDirection; - - bool isPressed = false; - bool _dragging = false; - late Vector2 _dragPosition; - late double _tileSize; - - late JoystickElement backgroundDirection; - late JoystickElement action; - late JoystickElement actionPressed; - - JoystickController get joystickController => parent! as JoystickController; - - JoystickAction({ - required this.actionId, - JoystickElement? backgroundDirection, - JoystickElement? action, - JoystickElement? actionPressed, - this.enableDirection = false, - this.size = 50, - this.margin = EdgeInsets.zero, - this.align = JoystickActionAlign.bottomRight, - double sizeFactorBackgroundDirection = 1.5, - Color color = Colors.blueGrey, - double opacityBackground = 0.5, - double opacityKnob = 0.8, - }) : _sizeBackgroundDirection = sizeFactorBackgroundDirection * size { - this.action = action ?? - JoystickElement.paint( - Paint()..color = color.withOpacity(opacityKnob), - ); - this.actionPressed = actionPressed ?? - JoystickElement.paint( - Paint()..color = color.withOpacity(opacityKnob), - ); - this.backgroundDirection = backgroundDirection ?? - JoystickElement.paint( - Paint()..color = color.withOpacity(opacityBackground), - ); - - _tileSize = _sizeBackgroundDirection / 2; - } - - @override - Future onLoad() async { - initialize(gameRef.size); - } - - @override - void onGameResize(Vector2 gameSize) { - super.onGameResize(gameSize); - initialize(gameSize); - } - - void initialize(Vector2 _screenSize) { - final radius = size / 2; - var dx = 0.0, dy = 0.0; - switch (align) { - case JoystickActionAlign.topLeft: - dx = margin.left + radius; - dy = margin.top + radius; - break; - case JoystickActionAlign.bottomLeft: - dx = margin.left + radius; - dy = _screenSize.y - (margin.bottom + radius); - break; - case JoystickActionAlign.topRight: - dx = _screenSize.x - (margin.right + radius); - dy = margin.top + radius; - break; - case JoystickActionAlign.bottomRight: - dx = _screenSize.x - (margin.right + radius); - dy = _screenSize.y - (margin.bottom + radius); - break; - } - final center = Offset(dx, dy); - action.rect = Rect.fromCircle( - center: center, - radius: radius, - ); - actionPressed.rect = action.rect; - backgroundDirection.rect = Rect.fromCircle( - center: center, - radius: _sizeBackgroundDirection / 2, - ); - _dragPosition = action.center; - } - - @override - void render(Canvas c) { - super.render(c); - if (_dragging && enableDirection) { - backgroundDirection.render(c); - } - - (isPressed ? actionPressed : action).render(c); - } - - @override - void update(double dt) { - super.update(dt); - - Vector2 diff; - if (_dragging) { - // Distance between the center of joystick background & drag position - final centerPosition = backgroundDirection.center; - - final atanDiff = _dragPosition - centerPosition; - final angle = atan2(atanDiff.y, atanDiff.x); - - final unboundDist = centerPosition.distanceTo(_dragPosition); - - // The maximum distance for the knob position to the edge of - // the background + half of its own size. The knob can wander in the - // background image, but not outside. - final dist = min(unboundDist, _tileSize); - - // Calculate the knob position - final nextX = cos(angle); - final nextY = sin(angle); - final nextPoint = Vector2(nextX, nextY) * dist; - - diff = backgroundDirection.center + nextPoint - action.center; - - final _intensity = dist / _tileSize; - - _sendEvent(ActionEvent.move, intensity: _intensity, angle: angle); - } else { - diff = _dragPosition - action.center; - } - - action.shift(diff); - actionPressed.rect = action.rect; - } - - @override - bool containsPoint(Vector2 point) { - return action.rect.containsPoint(point); - } - - @override - bool onDragStart(int pointerId, DragStartInfo info) { - if (_dragging) { - return true; - } - - if (enableDirection) { - _dragPosition = info.eventPosition.widget; - _dragging = true; - } - _sendEvent(ActionEvent.down); - tapDown(); - return false; - } - - void tapDown() { - isPressed = true; - } - - void tapUp() { - isPressed = false; - } - - @override - bool onDragUpdate(int pointerId, DragUpdateInfo info) { - if (_dragging) { - _dragPosition = info.eventPosition.game; - return true; - } - return false; - } - - @override - bool onDragEnd(_, __) { - return _finishDrag(ActionEvent.up); - } - - @override - bool onDragCancel(int pointerId) { - return _finishDrag(ActionEvent.cancel); - } - - bool _finishDrag(ActionEvent event) { - _dragging = false; - _dragPosition = backgroundDirection.center; - _sendEvent(event); - tapUp(); - return true; - } - - void _sendEvent(ActionEvent event, {double? intensity, double? angle}) { - joystickController.joystickAction( - JoystickActionEvent( - id: actionId, - event: event, - intensity: intensity ?? 0.0, - angle: angle ?? 0.0, - ), - ); - } -} diff --git a/packages/flame/lib/src/components/joystick/joystick_component.dart b/packages/flame/lib/src/components/joystick/joystick_component.dart deleted file mode 100644 index e42c996cc..000000000 --- a/packages/flame/lib/src/components/joystick/joystick_component.dart +++ /dev/null @@ -1,58 +0,0 @@ -import '../../../components.dart'; -import '../../game/base_game.dart'; -import '../mixins/has_game_ref.dart'; -import 'joystick_action.dart'; -import 'joystick_directional.dart'; -import 'joystick_events.dart'; - -mixin JoystickListener { - void joystickChangeDirectional(JoystickDirectionalEvent event); - void joystickAction(JoystickActionEvent event); -} - -abstract class JoystickController extends BaseComponent - with HasGameRef, Draggable { - @override - bool isHud = true; - - final List _observers = []; - - void joystickChangeDirectional(JoystickDirectionalEvent event) { - _observers.forEach((o) => o.joystickChangeDirectional(event)); - } - - void joystickAction(JoystickActionEvent event) { - _observers.forEach((o) => o.joystickAction(event)); - } - - void addObserver(JoystickListener listener) { - _observers.add(listener); - } -} - -class JoystickComponent extends JoystickController { - @override - int priority; - - JoystickComponent({ - required BaseGame gameRef, - List actions = const [], - JoystickDirectional? directional, - this.priority = 0, - }) { - if (directional != null) { - addChild(directional, gameRef: gameRef); - } - actions.forEach((action) => addChild(action, gameRef: gameRef)); - } - - void addAction(JoystickAction action) { - addChild(action, gameRef: gameRef); - } - - void removeAction(int actionId) { - final action = children - .firstWhere((e) => e is JoystickAction && e.actionId == actionId); - children.remove(action); - } -} diff --git a/packages/flame/lib/src/components/joystick/joystick_directional.dart b/packages/flame/lib/src/components/joystick/joystick_directional.dart deleted file mode 100644 index d5eb5f74a..000000000 --- a/packages/flame/lib/src/components/joystick/joystick_directional.dart +++ /dev/null @@ -1,191 +0,0 @@ -import 'dart:math'; -import 'dart:ui'; - -import 'package:flutter/material.dart' show Colors; -import 'package:flutter/widgets.dart' show EdgeInsets; - -import '../../../components.dart'; -import '../../../extensions.dart'; -import '../../gestures/events.dart'; -import 'joystick_component.dart'; -import 'joystick_element.dart'; -import 'joystick_events.dart'; - -class JoystickDirectional extends BaseComponent with Draggable, HasGameRef { - final double size; - final bool isFixed; - final EdgeInsets margin; - - late JoystickElement background; - late JoystickElement knob; - late double _tileSize; - - bool _dragging = false; - late Vector2 _dragPosition; - Vector2? _screenSize; - - JoystickController get joystickController => parent! as JoystickController; - - JoystickDirectional({ - JoystickElement? background, - JoystickElement? knob, - this.isFixed = true, - this.margin = const EdgeInsets.only(left: 100, bottom: 100), - this.size = 80, - Color color = Colors.blueGrey, - double opacityBackground = 0.5, - double opacityKnob = 0.8, - }) { - this.background = background ?? - JoystickElement.paint( - Paint()..color = color.withOpacity(opacityBackground), - ); - this.knob = knob ?? - JoystickElement.paint( - Paint()..color = color.withOpacity(opacityKnob), - ); - - _tileSize = size / 2; - } - - @override - Future onLoad() async { - initialize(gameRef.size); - } - - @override - void onGameResize(Vector2 gameSize) { - super.onGameResize(gameSize); - initialize(gameSize); - } - - void initialize(Vector2 screenSize) { - _screenSize = screenSize; - - final osBackground = Offset(margin.left, screenSize.y - margin.bottom); - background.rect = Rect.fromCircle(center: osBackground, radius: size / 2); - knob.rect = Rect.fromCircle(center: osBackground, radius: size / 4); - - _dragPosition = osBackground.toVector2(); - } - - @override - void render(Canvas canvas) { - super.render(canvas); - background.render(canvas); - knob.render(canvas); - } - - @override - void update(double dt) { - super.update(dt); - if (_dragging) { - // Distance between the center of joystick background & drag position - final centerPosition = background.center; - - final delta = _dragPosition - centerPosition; - final angle = atan2(delta.y, delta.x); - final angleDegrees = angle * 180 / pi; - - // The maximum distance for the knob position the edge of - // the background + half of its own size. The knob can wander in the - // background image, but not outside. - final dist = min(centerPosition.distanceTo(_dragPosition), _tileSize); - - // Calculation the knob position - final nextX = dist * cos(angle); - final nextY = dist * sin(angle); - final nextPoint = Vector2(nextX, nextY); - - final diff = centerPosition + nextPoint - knob.center; - knob.shift(diff); - - final _intensity = dist / _tileSize; - - JoystickMoveDirectional directional; - - if (_intensity == 0) { - directional = JoystickMoveDirectional.idle; - } else { - directional = JoystickDirectionalEvent.calculateDirectionalByDegrees( - angleDegrees, - ); - } - - joystickController.joystickChangeDirectional(JoystickDirectionalEvent( - directional: directional, - intensity: _intensity, - angle: angle, - )); - } else { - final diff = _dragPosition - knob.center; - knob.shift(diff); - } - } - - @override - bool containsPoint(Vector2 point) { - final directional = background.rect.inflate(50.0); - return directional.containsPoint(point); - } - - @override - bool onDragStart(int pointerId, DragStartInfo info) { - _updateDirectionalRect(info.eventPosition.widget); - if (!_dragging) { - _dragging = true; - _dragPosition = info.eventPosition.widget; - return true; - } - return false; - } - - void _updateDirectionalRect(Vector2 position) { - final screenSize = _screenSize; - if (screenSize != null && - (position.x > screenSize.x / 2 || - position.y < screenSize.y / 2 || - isFixed)) { - return; - } - - background.rect = Rect.fromCircle( - center: position.toOffset(), - radius: size / 2, - ); - - knob.rect = Rect.fromCircle( - center: position.toOffset(), - radius: size / 4, - ); - } - - @override - bool onDragUpdate(_, DragUpdateInfo info) { - if (_dragging) { - _dragPosition = info.eventPosition.game; - return false; - } - return true; - } - - @override - bool onDragEnd(_, __) { - _dragging = false; - _dragPosition = background.center; - joystickController.joystickChangeDirectional(JoystickDirectionalEvent( - directional: JoystickMoveDirectional.idle, - )); - return true; - } - - @override - bool onDragCancel(_) { - _dragging = false; - _dragPosition = background.center; - joystickController.joystickChangeDirectional(JoystickDirectionalEvent( - directional: JoystickMoveDirectional.idle, - )); - return true; - } -} diff --git a/packages/flame/lib/src/components/joystick/joystick_element.dart b/packages/flame/lib/src/components/joystick/joystick_element.dart deleted file mode 100644 index dca10bebc..000000000 --- a/packages/flame/lib/src/components/joystick/joystick_element.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'dart:ui'; - -import '../../../components.dart'; -import '../../../extensions.dart'; - -/// This is an element drawn on the canvas on the position [rect]; -/// -/// It can be either a [sprite] or a [paint] (solid color circle). Not both. -class JoystickElement { - final Sprite? sprite; - final Paint? paint; - - late Rect rect; - - JoystickElement.sprite(this.sprite) : paint = null; - - JoystickElement.paint(this.paint) : sprite = null; - - Vector2 get center => rect.center.toVector2(); - - void shift(Vector2 diff) { - rect = rect.shift(diff.toOffset()); - } - - void render(Canvas c) { - final rect = this.rect; - final sprite = this.sprite; - final paint = this.paint; - - if (sprite == null) { - assert(paint != null, '`paint` must not be `null` if `sprite` is `null`'); - - final radius = rect.width / 2; - c.drawCircle(rect.center, radius, paint!); - } else { - sprite.render( - c, - position: rect.topLeft.toVector2(), - size: rect.size.toVector2(), - ); - } - } -} diff --git a/packages/flame/lib/src/components/joystick/joystick_events.dart b/packages/flame/lib/src/components/joystick/joystick_events.dart deleted file mode 100644 index c4e7a5ea9..000000000 --- a/packages/flame/lib/src/components/joystick/joystick_events.dart +++ /dev/null @@ -1,81 +0,0 @@ -enum JoystickMoveDirectional { - moveUp, - moveUpLeft, - moveUpRight, - moveRight, - moveDown, - moveDownRight, - moveDownLeft, - moveLeft, - idle, -} - -enum ActionEvent { down, up, move, cancel } - -class JoystickDirectionalEvent { - /// The direction the knob was moved towards, converted to a set of 8 - /// cardinal directions. - final JoystickMoveDirectional directional; - - /// How much the knob was moved, from 0 (center) to 1 (edge). - final double intensity; - - /// The direction the knob was moved towards (in radians). - /// - /// It uses the trigonometric circle convention (i.e. start on the - /// positive x-axis and rotates counter-clockwise). - final double angle; - - JoystickDirectionalEvent({ - required this.directional, - this.intensity = 0.0, - this.angle = 0.0, - }); - - static JoystickMoveDirectional calculateDirectionalByDegrees(double degrees) { - if (degrees > -22.5 && degrees <= 22.5) { - return JoystickMoveDirectional.moveRight; - } else if (degrees > 22.5 && degrees <= 67.5) { - return JoystickMoveDirectional.moveDownRight; - } else if (degrees > 67.5 && degrees <= 112.5) { - return JoystickMoveDirectional.moveDown; - } else if (degrees > 112.5 && degrees <= 157.5) { - return JoystickMoveDirectional.moveDownLeft; - } else if ((degrees > 157.5 && degrees <= 180) || - (degrees >= -180 && degrees <= -157.5)) { - return JoystickMoveDirectional.moveLeft; - } else if (degrees > -157.5 && degrees <= -112.5) { - return JoystickMoveDirectional.moveUpLeft; - } else if (degrees > -112.5 && degrees <= -67.5) { - return JoystickMoveDirectional.moveUp; - } else if (degrees > -67.5 && degrees <= -22.5) { - return JoystickMoveDirectional.moveUpRight; - } else { - return JoystickMoveDirectional.idle; - } - } -} - -class JoystickActionEvent { - /// The id of this action as defined in the setup code. - final int id; - - /// What action was performed in this button. - final ActionEvent event; - - /// How much the knob was moved, from 0 (center) to 1 (edge). - final double intensity; - - /// The direction the knob was moved towards (in radians). - /// - /// It uses the trigonometric circle convention (i.e. start on the - /// positive x-axis and rotates counter-clockwise). - final double angle; - - JoystickActionEvent({ - required this.id, - required this.event, - this.intensity = 0.0, - this.angle = 0.0, - }); -} diff --git a/packages/flame/lib/src/components/shape_component.dart b/packages/flame/lib/src/components/shape_component.dart index 6d518fb5a..465af3859 100644 --- a/packages/flame/lib/src/components/shape_component.dart +++ b/packages/flame/lib/src/components/shape_component.dart @@ -12,7 +12,7 @@ class ShapeComponent extends PositionComponent { ShapeComponent( this.shape, this.shapePaint, { - Anchor anchor = Anchor.center, + Anchor anchor = Anchor.topLeft, int? priority, }) : super( position: shape.position, diff --git a/packages/flame/lib/src/effects/effects.dart b/packages/flame/lib/src/effects/effects.dart index 6557d48c5..1743bf40c 100644 --- a/packages/flame/lib/src/effects/effects.dart +++ b/packages/flame/lib/src/effects/effects.dart @@ -36,6 +36,10 @@ abstract class ComponentEffect { int curveDirection = 1; Curve curve; + /// If this is set to true the effect will not be set to its original state + /// once it is done. + bool skipEffectReset = false; + double get iterationTime => peakTime * (isAlternating ? 2 : 1); ComponentEffect( @@ -74,6 +78,7 @@ abstract class ComponentEffect { void dispose() => _isDisposed = true; + /// Whether the effect has completed or not. bool hasCompleted() { return (!isInfinite && !isAlternating && isMax()) || (!isInfinite && isAlternating && isMin()) || @@ -84,14 +89,20 @@ abstract class ComponentEffect { bool isMin() => percentage == null ? false : percentage == 0.0; bool isRootEffect() => component?.effects.contains(this) == true; + /// Resets the effect and the component which the effect was added to. void reset() { + resetEffect(); + setComponentToOriginalState(); + } + + /// Resets the effect to its original state so that it can be re-run. + void resetEffect() { _isDisposed = false; percentage = null; currentTime = 0.0; curveDirection = 1; isInfinite = _initialIsInfinite; isAlternating = _initialIsAlternating; - setComponentToOriginalState(); } // When the time overshoots the max and min it needs to add that time to @@ -107,6 +118,16 @@ abstract class ComponentEffect { } } + /// Called when the effect is removed from the component. + /// Calls the [onComplete] callback if it is defined and sets the effect back + /// to its original state so that it can be re-added. + void onRemove() { + onComplete?.call(); + if (!skipEffectReset) { + resetEffect(); + } + } + void setComponentToOriginalState(); void setComponentToEndState(); } diff --git a/packages/flame/lib/src/effects/effects_handler.dart b/packages/flame/lib/src/effects/effects_handler.dart index a9e218026..2dcbcdd26 100644 --- a/packages/flame/lib/src/effects/effects_handler.dart +++ b/packages/flame/lib/src/effects/effects_handler.dart @@ -16,12 +16,17 @@ class EffectsHandler { void update(double dt) { _effects.addAll(_addLater); _addLater.clear(); - _effects.removeWhere((e) => e.hasCompleted()); + _effects.removeWhere((e) { + final hasCompleted = e.hasCompleted(); + if (hasCompleted) { + e.onRemove(); + } + return hasCompleted; + }); _effects.where((e) => !e.isPaused).forEach((e) { e.update(dt); if (e.hasCompleted()) { e.setComponentToEndState(); - e.onComplete?.call(); } }); } diff --git a/packages/flame/lib/src/extensions/vector2.dart b/packages/flame/lib/src/extensions/vector2.dart index 344b66737..0cfb00c59 100644 --- a/packages/flame/lib/src/extensions/vector2.dart +++ b/packages/flame/lib/src/extensions/vector2.dart @@ -60,9 +60,11 @@ extension Vector2Extension on Vector2 { } } - /// Changes the [length] of the vector to the length provided, without changing direction. + /// Changes the [length] of the vector to the length provided, without + /// changing direction. /// - /// If you try to scale the zero (empty) vector, it will remain unchanged, and no error will be thrown. + /// If you try to scale the zero (empty) vector, it will remain unchanged, and + /// no error will be thrown. void scaleTo(double newLength) { final l = length; if (l != 0) { @@ -93,6 +95,19 @@ extension Vector2Extension on Vector2 { } } + /// Signed angle in a coordinate system where the Y-axis is flipped. + /// + /// Since on a canvas/screen y is smaller the further up you go, instead of + /// larger like on a normal coordinate system, to get an angle that is in that + /// coordinate system we have to flip the Y-axis of the [Vector]. + /// + /// Example: + /// Up: Vector(0.0, -1.0).screenAngle == 0 + /// Down: Vector(0.0, 1.0).screenAngle == +-pi + /// Left: Vector(-1.0, 0.0).screenAngle == -pi/2 + /// Right: Vector(-1.0, 0.0).screenAngle == pi/2 + double screenAngle() => (clone()..y *= -1).angleToSigned(Vector2(0.0, 1.0)); + /// Modulo/Remainder Vector2 operator %(Vector2 mod) => Vector2(x % mod.x, y % mod.y); diff --git a/packages/flame/lib/src/game/game.dart b/packages/flame/lib/src/game/game.dart index 7b106ee7e..f35a33639 100644 --- a/packages/flame/lib/src/game/game.dart +++ b/packages/flame/lib/src/game/game.dart @@ -10,10 +10,10 @@ import '../assets/assets_cache.dart'; import '../assets/images.dart'; import '../extensions/offset.dart'; import '../extensions/vector2.dart'; -import '../keyboard.dart'; import '../sprite.dart'; import '../sprite_animation.dart'; import 'game_render_box.dart'; +import 'mixins/keyboard.dart'; import 'projector.dart'; /// Represents a generic game. diff --git a/packages/flame/lib/src/keyboard.dart b/packages/flame/lib/src/game/mixins/keyboard.dart similarity index 81% rename from packages/flame/lib/src/keyboard.dart rename to packages/flame/lib/src/game/mixins/keyboard.dart index 081d379d1..d49525c30 100644 --- a/packages/flame/lib/src/keyboard.dart +++ b/packages/flame/lib/src/game/mixins/keyboard.dart @@ -1,6 +1,6 @@ import 'package:flutter/services.dart'; -import 'game/game.dart'; +import '../game.dart'; mixin KeyboardEvents on Game { void onKeyEvent(RawKeyEvent event); diff --git a/packages/flame/lib/src/geometry/shape.dart b/packages/flame/lib/src/geometry/shape.dart index 1ea104a96..d7d41535a 100644 --- a/packages/flame/lib/src/geometry/shape.dart +++ b/packages/flame/lib/src/geometry/shape.dart @@ -2,6 +2,7 @@ import 'dart:ui'; import '../../components.dart'; import '../../game.dart'; +import '../../palette.dart'; import '../extensions/vector2.dart'; import 'shape_intersections.dart' as intersection_system; @@ -109,10 +110,23 @@ abstract class Shape { void render(Canvas canvas, Paint paint); - /// Where this Shape has intersection points with another shape + /// Where this [Shape] has intersection points with another shape Set intersections(Shape other) { return intersection_system.intersections(this, other); } + + /// Turns a [Shape] into a [ShapeComponent] + /// + /// Do note that while a [Shape] is defined from the center, a + /// [ShapeComponent] like all other components default to an [Anchor] in the + /// top left corner. + ShapeComponent toComponent({Paint? paint, Anchor anchor = Anchor.topLeft}) { + return ShapeComponent( + this, + paint ?? BasicPalette.white.paint(), + anchor: anchor, + ); + } } mixin HitboxShape on Shape { diff --git a/packages/flame/test/components/draggable_test.dart b/packages/flame/test/components/draggable_test.dart index ffc461ab4..55228c98f 100644 --- a/packages/flame/test/components/draggable_test.dart +++ b/packages/flame/test/components/draggable_test.dart @@ -1,6 +1,6 @@ import 'package:flame/components.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flutter/gestures.dart'; import 'package:test/test.dart'; diff --git a/packages/flame/test/components/joystick_component_test.dart b/packages/flame/test/components/joystick_component_test.dart new file mode 100644 index 000000000..5d8f16e1c --- /dev/null +++ b/packages/flame/test/components/joystick_component_test.dart @@ -0,0 +1,41 @@ +import 'package:flame/components.dart'; +import 'package:flame/game.dart'; +import 'package:flame/geometry.dart'; +import 'package:flame/input.dart'; +import 'package:flutter/widgets.dart'; +import 'package:test/test.dart'; + +class TestGame extends BaseGame with HasDraggableComponents {} + +void main() { + group('JoystickDirection tests', () { + test('Can convert angle to JoystickDirection', () { + final joystick = JoystickComponent( + knob: Circle(radius: 5.0).toComponent(), + size: 20, + margin: const EdgeInsets.only(left: 20, bottom: 20), + ); + final game = TestGame()..onResize(Vector2.all(200)); + game.add(joystick); + game.update(0); + + expect(joystick.direction, JoystickDirection.idle); + joystick.delta = Vector2(1.0, 0.0); + expect(joystick.direction, JoystickDirection.right); + joystick.delta = Vector2(0.0, -1.0); + expect(joystick.direction, JoystickDirection.up); + joystick.delta = Vector2(1.0, -1.0); + expect(joystick.direction, JoystickDirection.upRight); + joystick.delta = Vector2(-1.0, -1.0); + expect(joystick.direction, JoystickDirection.upLeft); + joystick.delta = Vector2(-1.0, 0.0); + expect(joystick.direction, JoystickDirection.left); + joystick.delta = Vector2(0.0, 1.0); + expect(joystick.direction, JoystickDirection.down); + joystick.delta = Vector2(1.0, 1.0); + expect(joystick.direction, JoystickDirection.downRight); + joystick.delta = Vector2(-1.0, 1.0); + expect(joystick.direction, JoystickDirection.downLeft); + }); + }); +} diff --git a/packages/flame/test/effects/combined_effect_test.dart b/packages/flame/test/effects/combined_effect_test.dart index 0d51d1359..d075b5b9b 100644 --- a/packages/flame/test/effects/combined_effect_test.dart +++ b/packages/flame/test/effects/combined_effect_test.dart @@ -33,22 +33,22 @@ void main() { path: path, duration: randomDuration(), isAlternating: hasAlternatingMoveEffect, - ); + )..skipEffectReset = true; final rotate = RotateEffect( angle: argumentAngle, duration: randomDuration(), isAlternating: hasAlternatingRotateEffect, - ); + )..skipEffectReset = true; final scale = ScaleEffect( size: argumentSize, duration: randomDuration(), isAlternating: hasAlternatingScaleEffect, - ); + )..skipEffectReset = true; return CombinedEffect( effects: [move, scale, rotate], isInfinite: isInfinite, isAlternating: isAlternating, - ); + )..skipEffectReset = true; } testWidgets('CombinedEffect can combine', (WidgetTester tester) async { diff --git a/packages/flame/test/effects/effect_test_utils.dart b/packages/flame/test/effects/effect_test_utils.dart index 5ec77ec92..587046b0e 100644 --- a/packages/flame/test/effects/effect_test_utils.dart +++ b/packages/flame/test/effects/effect_test_utils.dart @@ -89,6 +89,7 @@ void effectTest( ); } expect(effect.hasCompleted(), shouldComplete, reason: 'Effect shouldFinish'); + game.update(0.0); expect( callback.isCalled, shouldComplete, diff --git a/packages/flame/test/effects/move_effect_test.dart b/packages/flame/test/effects/move_effect_test.dart index ab825719f..6b8041830 100644 --- a/packages/flame/test/effects/move_effect_test.dart +++ b/packages/flame/test/effects/move_effect_test.dart @@ -18,7 +18,7 @@ void main() { duration: 1 + random.nextInt(100).toDouble(), isInfinite: isInfinite, isAlternating: isAlternating, - ); + )..skipEffectReset = true; } testWidgets('MoveEffect can move', (WidgetTester tester) async { diff --git a/packages/flame/test/effects/rotate_effect_test.dart b/packages/flame/test/effects/rotate_effect_test.dart index 9b6e59400..9f7f39b7b 100644 --- a/packages/flame/test/effects/rotate_effect_test.dart +++ b/packages/flame/test/effects/rotate_effect_test.dart @@ -20,7 +20,7 @@ void main() { isInfinite: isInfinite, isAlternating: isAlternating, isRelative: isRelative, - ); + )..skipEffectReset = true; } testWidgets('RotateEffect can rotate', (WidgetTester tester) async { diff --git a/packages/flame/test/effects/scale_effect_test.dart b/packages/flame/test/effects/scale_effect_test.dart index e97a7fc5f..210335cf3 100644 --- a/packages/flame/test/effects/scale_effect_test.dart +++ b/packages/flame/test/effects/scale_effect_test.dart @@ -18,7 +18,7 @@ void main() { duration: 1 + random.nextInt(100).toDouble(), isInfinite: isInfinite, isAlternating: isAlternating, - ); + )..skipEffectReset = true; } testWidgets('ScaleEffect can scale', (WidgetTester tester) async { diff --git a/packages/flame/test/effects/sequence_effect_test.dart b/packages/flame/test/effects/sequence_effect_test.dart index 452afca23..1c45bc3d4 100644 --- a/packages/flame/test/effects/sequence_effect_test.dart +++ b/packages/flame/test/effects/sequence_effect_test.dart @@ -34,22 +34,22 @@ void main() { path: path, duration: randomDuration(), isAlternating: hasAlternatingMoveEffect, - ); + )..skipEffectReset = true; final rotate = RotateEffect( angle: argumentAngle, duration: randomDuration(), isAlternating: hasAlternatingRotateEffect, - ); + )..skipEffectReset = true; final scale = ScaleEffect( size: argumentSize, duration: randomDuration(), isAlternating: hasAlternatingScaleEffect, - ); + )..skipEffectReset = true; return SequenceEffect( effects: [move, scale, rotate], isInfinite: isInfinite, isAlternating: isAlternating, - ); + )..skipEffectReset = true; } testWidgets('SequenceEffect can sequence', (WidgetTester tester) async { diff --git a/packages/flame/test/extensions/vector2_test.dart b/packages/flame/test/extensions/vector2_test.dart index b2e5b55a8..a409ea1e7 100644 --- a/packages/flame/test/extensions/vector2_test.dart +++ b/packages/flame/test/extensions/vector2_test.dart @@ -194,5 +194,20 @@ void main() { position.rotate(math.pi / 2, center: center); expectVector2(position, Vector2(4.0, -3.0)); }); + + test('screenAngle', () { + // Up + final position = Vector2(0.0, -1.0); + expectDouble(position.screenAngle(), 0.0); + // Down + position.setValues(0.0, 1.0); + expectDouble(position.screenAngle().abs(), math.pi); + // Left + position.setValues(-1.0, 0.0); + expectDouble(position.screenAngle(), -math.pi / 2); + // Right + position.setValues(1.0, 0.0); + expectDouble(position.screenAngle(), math.pi / 2); + }); }); } diff --git a/packages/flame/test/util/mock_gesture_events.dart b/packages/flame/test/util/mock_gesture_events.dart index aec07316d..26f955e0b 100644 --- a/packages/flame/test/util/mock_gesture_events.dart +++ b/packages/flame/test/util/mock_gesture_events.dart @@ -1,7 +1,7 @@ -import 'package:flame/game.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flame/gestures.dart'; import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flame/input.dart'; +import 'package:flutter/gestures.dart'; TapDownInfo createTapDownEvent( Game game, { diff --git a/packages/flame_audio/example/lib/main.dart b/packages/flame_audio/example/lib/main.dart index d378f7c7f..ac12b718c 100644 --- a/packages/flame_audio/example/lib/main.dart +++ b/packages/flame_audio/example/lib/main.dart @@ -1,7 +1,7 @@ import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame/palette.dart'; import 'package:flame_audio/audio_pool.dart'; import 'package:flame_audio/flame_audio.dart'; diff --git a/packages/flame_fire_atlas/example/lib/main.dart b/packages/flame_fire_atlas/example/lib/main.dart index 4bce901b2..65c56af18 100644 --- a/packages/flame_fire_atlas/example/lib/main.dart +++ b/packages/flame_fire_atlas/example/lib/main.dart @@ -1,9 +1,9 @@ -import 'package:flutter/material.dart'; +import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; -import 'package:flame/components.dart'; +import 'package:flame/input.dart'; import 'package:flame_fire_atlas/flame_fire_atlas.dart'; +import 'package:flutter/material.dart'; void main() async { try { diff --git a/packages/flame_forge2d/example/lib/blob_sample.dart b/packages/flame_forge2d/example/lib/blob_sample.dart index 633639bac..64363ec7d 100644 --- a/packages/flame_forge2d/example/lib/blob_sample.dart +++ b/packages/flame_forge2d/example/lib/blob_sample.dart @@ -1,10 +1,11 @@ import 'dart:math' as math; -import 'package:flame_forge2d/body_component.dart'; -import 'package:forge2d/forge2d.dart'; + import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; +import 'package:flame_forge2d/body_component.dart'; import 'package:flame_forge2d/forge2d_game.dart'; +import 'package:forge2d/forge2d.dart'; import 'boundaries.dart'; diff --git a/packages/flame_forge2d/example/lib/camera_sample.dart b/packages/flame_forge2d/example/lib/camera_sample.dart index e1ea54d64..33d3ab005 100644 --- a/packages/flame_forge2d/example/lib/camera_sample.dart +++ b/packages/flame_forge2d/example/lib/camera_sample.dart @@ -1,4 +1,5 @@ -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; + import 'domino_sample.dart'; class CameraSample extends DominoSample { diff --git a/packages/flame_forge2d/example/lib/circle_stress_sample.dart b/packages/flame_forge2d/example/lib/circle_stress_sample.dart index f94dfae74..04572e90d 100644 --- a/packages/flame_forge2d/example/lib/circle_stress_sample.dart +++ b/packages/flame_forge2d/example/lib/circle_stress_sample.dart @@ -1,10 +1,10 @@ import 'dart:math'; +import 'package:flame/input.dart'; import 'package:flame_forge2d/body_component.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:forge2d/forge2d.dart'; -import 'package:flame/gestures.dart'; import 'package:flame_forge2d/forge2d_game.dart'; +import 'package:forge2d/forge2d.dart'; import 'balls.dart'; import 'boundaries.dart'; diff --git a/packages/flame_forge2d/example/lib/contact_callbacks_sample.dart b/packages/flame_forge2d/example/lib/contact_callbacks_sample.dart index 5fbfd36d6..a04b3f519 100644 --- a/packages/flame_forge2d/example/lib/contact_callbacks_sample.dart +++ b/packages/flame_forge2d/example/lib/contact_callbacks_sample.dart @@ -1,8 +1,8 @@ import 'dart:math' as math; -import 'package:forge2d/forge2d.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame_forge2d/forge2d_game.dart'; +import 'package:forge2d/forge2d.dart'; import 'balls.dart'; import 'boundaries.dart'; diff --git a/packages/flame_forge2d/example/lib/domino_sample.dart b/packages/flame_forge2d/example/lib/domino_sample.dart index 6613a44b9..8ee39ab94 100644 --- a/packages/flame_forge2d/example/lib/domino_sample.dart +++ b/packages/flame_forge2d/example/lib/domino_sample.dart @@ -1,11 +1,11 @@ import 'dart:ui'; +import 'package:flame/components.dart'; +import 'package:flame/input.dart'; import 'package:flame_forge2d/body_component.dart'; +import 'package:flame_forge2d/forge2d_game.dart'; import 'package:flame_forge2d/sprite_body_component.dart'; import 'package:forge2d/forge2d.dart'; -import 'package:flame/components.dart'; -import 'package:flame/gestures.dart'; -import 'package:flame_forge2d/forge2d_game.dart'; import 'boundaries.dart'; diff --git a/packages/flame_forge2d/example/lib/draggable_sample.dart b/packages/flame_forge2d/example/lib/draggable_sample.dart index 8158b10b4..c3839f834 100644 --- a/packages/flame_forge2d/example/lib/draggable_sample.dart +++ b/packages/flame_forge2d/example/lib/draggable_sample.dart @@ -1,10 +1,10 @@ -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flutter/material.dart' hide Draggable; -import 'package:forge2d/forge2d.dart'; -import 'package:flame_forge2d/forge2d_game.dart'; import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_forge2d/forge2d_game.dart'; +import 'package:flutter/material.dart' hide Draggable; +import 'package:forge2d/forge2d.dart'; import 'balls.dart'; import 'boundaries.dart'; diff --git a/packages/flame_forge2d/example/lib/mouse_joint_sample.dart b/packages/flame_forge2d/example/lib/mouse_joint_sample.dart index d7343dcbb..50a9ac200 100644 --- a/packages/flame_forge2d/example/lib/mouse_joint_sample.dart +++ b/packages/flame_forge2d/example/lib/mouse_joint_sample.dart @@ -1,7 +1,7 @@ +import 'package:flame/extensions.dart'; +import 'package:flame/input.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/forge2d_game.dart'; -import 'package:flame/gestures.dart'; -import 'package:flame/extensions.dart'; import 'balls.dart'; import 'boundaries.dart'; diff --git a/packages/flame_forge2d/example/lib/position_body_sample.dart b/packages/flame_forge2d/example/lib/position_body_sample.dart index 4aeaf216c..c4458cd61 100644 --- a/packages/flame_forge2d/example/lib/position_body_sample.dart +++ b/packages/flame_forge2d/example/lib/position_body_sample.dart @@ -1,7 +1,7 @@ import 'dart:ui'; import 'package:flame/components.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame_forge2d/forge2d_game.dart'; import 'package:flame_forge2d/position_body_component.dart'; import 'package:forge2d/forge2d.dart'; diff --git a/packages/flame_forge2d/example/lib/sprite_body_sample.dart b/packages/flame_forge2d/example/lib/sprite_body_sample.dart index 4b5c2991a..425e5b038 100644 --- a/packages/flame_forge2d/example/lib/sprite_body_sample.dart +++ b/packages/flame_forge2d/example/lib/sprite_body_sample.dart @@ -1,10 +1,10 @@ import 'dart:ui'; -import 'package:forge2d/forge2d.dart'; import 'package:flame/components.dart'; -import 'package:flame/gestures.dart'; +import 'package:flame/input.dart'; import 'package:flame_forge2d/forge2d_game.dart'; import 'package:flame_forge2d/sprite_body_component.dart'; +import 'package:forge2d/forge2d.dart'; import 'boundaries.dart'; diff --git a/tutorials/2_sprite_animations_gestures/code/lib/main.dart b/tutorials/2_sprite_animations_gestures/code/lib/main.dart index 4fb033fca..64c00a17d 100644 --- a/tutorials/2_sprite_animations_gestures/code/lib/main.dart +++ b/tutorials/2_sprite_animations_gestures/code/lib/main.dart @@ -1,7 +1,7 @@ -import 'package:flame/gestures.dart'; +import 'package:flame/game.dart'; +import 'package:flame/input.dart'; import 'package:flame/sprite.dart'; import 'package:flutter/material.dart'; -import 'package:flame/game.dart'; void main() { final myGame = MyGame();