diff --git a/.github/.cspell/gamedev_dictionary.txt b/.github/.cspell/gamedev_dictionary.txt index c3d28c3843a..2ba081901a5 100644 --- a/.github/.cspell/gamedev_dictionary.txt +++ b/.github/.cspell/gamedev_dictionary.txt @@ -27,7 +27,6 @@ hitbox # the collision box around objects for the purposes of collision detectio hitboxes # plural of hitbox ints # short for integers jank # stutter or inconsistent gap or timing -janky # quality of expressing jank lerp # short for linear interpolation LTRB # left top right bottom LTRBR # left top right bottom radius diff --git a/.github/.cspell/people_usernames.txt b/.github/.cspell/people_usernames.txt index b25ac9e5356..0be443c4bad 100644 --- a/.github/.cspell/people_usernames.txt +++ b/.github/.cspell/people_usernames.txt @@ -5,8 +5,7 @@ erayzesen # erayzesen.itch.io erickzanardo # github.com/erickzanardo feroult # github.com/feroult Klingsbo # github.com/spydon -luan # github.com/luanpotter -luanpotter # github.com/luanpotter +luanpotter # github.com/luanpotter Lukas # github.com/spydon pgainullin # github.com/pgainullin Schnurber # github.com/schnurber diff --git a/doc/flame/camera_and_viewport.md b/doc/flame/camera_and_viewport.md deleted file mode 100644 index 7fd5a975375..00000000000 --- a/doc/flame/camera_and_viewport.md +++ /dev/null @@ -1,229 +0,0 @@ -# Camera and Viewport (Deprecated) - -```{note} -This document describes the deprecated Camera API. The new `CameraComponent` approach -is described in [](camera_component.md). -``` - -When rendering on Flutter, the regular coordinate space used are logical pixels. That means one -pixel for Flutter is already not necessarily one real pixel on the device, because of the [device's -pixel ratio](https://api.flutter.dev/flutter/widgets/MediaQueryData/devicePixelRatio.html). When it -gets to the Flame level, we always consider the most fundamental level to be logical pixels, so all -the device specific complexity is abstracted away. - -However, that still leaves you with arbitrarily shaped and sized screens. And it's very likely that -your game has some sort of game world with an intrinsic coordinate system that does not map to -screen coordinates. Flame adds two distinct concepts to help transform coordinate spaces. For the -former, we have the Viewport class. And for the later, we have the Camera class. - - -## Viewport - -The Viewport is an attempt to unify multiple screen (or, rather, game widget) sizes into a single -configuration for your game by translating and resizing the canvas. - -The `Viewport` interface has multiple implementations and can be used from scratch on your `Game` -or, if you are using `FlameGame` instead, it's already built-in (with a default no-op viewport). - -These are the viewports available to pick from (or you can implement the interface yourself to suit -your needs): - -- `DefaultViewport`: this is the no-op viewport that is associated by default with any `FlameGame`. -- `FixedResolutionViewport`: this viewport transforms your Canvas so that, from the game - perspective, the dimensions are always set to a fixed pre-defined value. This means it will scale - the game as much as possible and add black bars if needed. - -When using `FlameGame`, the operations performed by the viewport are done automatically to every -render operation, and the `size` property in the game, instead of the logical widget size, becomes -the size as seen through the viewport together with the zoom of the camera. If for some reason you -need to access the original real logical pixel size, you can use `canvasSize`. For a more in depth -description on what each `Viewport` does and how it operates, check the documentation on its class. - - -## Camera - -Unlike the `Viewport`, the `Camera` is a more dynamic `Canvas` transformation that is normally -dependent on: - -- World coordinates that do not match screen coordinates 1:1. -- Centering or following the player around the game world (if the world is bigger than the screen). -- User controlled zooming in and out. - -There is only one Camera implementation but it allows for many different configurations. Again, you -can use it standalone on your `Game` but it's already included and wired into `FlameGame`. - -One important thing to note about the Camera is that since (unlike the Viewport) it's intended to be -dynamic, most camera movements won't immediately happen. Instead, the camera has a configurable -speed and is updated on the game loop. If you want to immediately move your camera (like on your -first camera setup at game start) you can use the `snap` function. Calling snap during the game can -lead to jarring or unnatural camera movements though, so avoid that unless desired (say for a map -transition, for example). Carefully check the docs for each method for more details about how it -affects the camera movement. - -Another important note is that the camera is applied after the viewport, and only to non-HUD -components. So screen size here is considering the effective size after Viewport transformations. - -There are two types of transformations that the Camera can apply to the Canvas. The first and most -complex one is translation. That can be applied by several factors: - -- nothing: by default the camera won't apply any transformation, so it's optional to use it. -- relative offset: you can configure this to decide "where the center of the camera should be on - the screen". By default it's the top left corner, meaning that the centered coordinate or object - will always be on the top left corner of the screen. You can smoothly change the relative offset - during gameplay (that can be used to apply a dialogue or item pickup temporary camera transition - for example). -- moveTo: if you want to ad-hoc move your camera you can use this method; it will smoothly - transition the camera to a new position, ignoring follows but respecting relative offset and - world bounds. This can be reset by `resetMovement` if used in conjunction to follow so that the - followed object starts being considered again. -- follow: you can use this method so that your camera continuously "follow" an object (for example, - a `PositionComponent`). This is not smooth because the movement of the followed object itself is - assumed to already be smooth (i.e. if your character teleport the camera will also immediately - teleport). -- world bounds: when using follow, you can optionally define the bounds of the world. If that is - done, the camera will stop following/moving so that out-of-bounds areas are not shown (as long as - the world is bigger than the screen). - -Finally the second transformation that the camera applies is scaling. That allows for dynamic -zooming, and it's controlled by the `zoom` field. There is no zoom speed, that must be controlled by -you when changing. The `zoom` variable is immediately applied. - -When dealing with input events, it is imperative to convert screen coordinates to world coordinates -(or, for some reasons, you might want to do the reverse). The Camera provides two functions, -`screenToWorld` and `worldToScreen` to easily convert between these coordinate spaces. - - -### Camera.followVector2 - -Immediately snaps the camera to start following a `Vector2`. - -This means that the camera will move so that the position vector is in a fixed position on the -screen. That position is determined by a fraction of screen size defined by the `relativeOffset` -argument (defaults to the center). The `worldBounds` argument can be optionally set to add -boundaries to how far the camera is allowed to move. - -Example: - -```dart -class MyGame extends FlameGame { - final someVector = Vector2(100, 100); - @override - void onLoad() { - oldCamera.followVector2(someVector); - } -} - -``` - - -### Camera.followComponent - -Immediately snaps the camera to start following a `PositionComponent`. - -This means that the camera will move so that the position vector of the component is in a fixed -position on the screen. That position is determined by a fraction of screen size defined by the -`relativeOffset` argument (defaults to the center). -The `worldBounds` argument can be optionally set to add boundaries to how far the camera is allowed -to move. - -The component is "grabbed" by its anchor (default top left). -So for example if you want the center of the object to be at the fixed position, set the components -anchor to center. - -Example: - -```dart -class MyGame extends FlameGame { - @override - Future onLoad() async { - final sprite = await loadSprite('pizza.png'); - final player = SpriteComponent( - sprite: sprite, - size: size, - anchor: Anchor.center, - ); - add(player); - - camera.followComponent(player); - } -} -``` - - -### Using the camera with the Game class - -If you are not using `FlameGame`, but instead are using the `Game` mixin, then you need to manage -calling certain camera methods yourself. Let's say we have the following game structure, and we -want to add the camera functionality: - -```dart -class YourGame extends Game { - Camera? camera; - - @override - void onLoad() {} - - @override - void render(Canvas canvas) {} - - @override - void update(double dt) {} -} -``` - -We first create a new camera instance on load and assign our game as the reference: - -```dart - @override - void onLoad() { - camera = Camera(); - - // This is required for the camera to work. - camera?.gameRef = this; - - // Not required but recommend to set it now or when you set the follow target. - camera?.worldBounds = yourWorldBounds; - - // Rest of your on load code. - } -``` - -The camera can also be made aware of which position to follow, this is an optional feature as you -can also use the camera for just moving,snapping or shaking. - -To do this the `Camera` class provides multiple methods for it but let's showcase the simplest one -and that is the `followVector2`: - -```dart - // Somewhere in your code: - camera?.followVector2( - yourPositionToFollow, - // Optional to pass, it will overwrite the previous bounds. - worldBounds: yourWorldBounds, - ); -``` - -Now that the camera is created and it is aware of both the world bounds and the position it should -follow, it can be used to translate the canvas in the render method: - -```dart - @override - void render(Canvas canvas) { - // This will apply the camera transformation. - camera?.apply(canvas); - - // Rest of your rendering code. - } -``` - -The only thing left to do is to call the `update` method on the `Camera` so it can smoothly follow -your given position: - -```dart - @override - void update(double dt) { - camera?.update(dt); - - // Rest of your update code. - } -``` diff --git a/doc/flame/camera_component.md b/doc/flame/camera_component.md index 0ac65912eb8..68e69e3771f 100644 --- a/doc/flame/camera_component.md +++ b/doc/flame/camera_component.md @@ -258,17 +258,3 @@ if (!camera.canSee(component)) { component.removeFromParent(); // Cull the component } ``` - - -## Comparison to the deprecated camera - -Compared to the deprecated [Camera](camera_and_viewport.md), the `CameraComponent` -has several advantages: - -- Multiple cameras can be added to the game at the same time; -- More flexibility in choosing the placement and the size of the viewport; -- Switching camera from one world to another can happen instantaneously, - without having to unmount one world and then mount another; -- Support rotation of the world view; -- Effects can be applied either to the viewport, or to the viewfinder; -- More flexible camera controllers. diff --git a/doc/flame/flame.md b/doc/flame/flame.md index ba7b401f609..217a5070331 100644 --- a/doc/flame/flame.md +++ b/doc/flame/flame.md @@ -14,7 +14,6 @@ - [Layout](layout/layout.md) - [Overlays](overlays.md) - [Other](other/other.md) -- [Camera & Viewport (deprecated)](camera_and_viewport.md) ```{toctree} :hidden: diff --git a/doc/flame/inputs/drag_events.md b/doc/flame/inputs/drag_events.md index e8675f3215b..43f970b3654 100644 --- a/doc/flame/inputs/drag_events.md +++ b/doc/flame/inputs/drag_events.md @@ -1,10 +1,5 @@ # Drag Events -```{note} -This document describes the new drag events API. The old (legacy) approach, -which is still supported, is described in [](gesture_input.md). -``` - **Drag events** occur when the user moves their finger across the screen of the device, or when they move the mouse while holding its button down. @@ -141,23 +136,3 @@ class MyComponent extends PositionComponent with DragCallbacks { } } ``` - - -### HasDraggablesBridge - -This marker mixin can be used to indicate that the game has both the "new-style" components that -use the `DragCallbacks` mixin, and the "old-style" components that use the `Draggable` mixin. In -effect, every drag event will be propagated twice through the system: first trying to reach the -components with `DragCallbacks` mixin, and then components with `Draggable`. - -```dart -class MyGame extends FlameGame with HasDraggablesBridge { - // ... -} -``` - -The purpose of this mixin is to ease the transition from the old event delivery system to the -new one. With this mixin, you can transition your `Draggable` components into using `DragCallbacks` -one by one, verifying that your game continues to work at every step. - -Use of this mixin for any new project is highly discouraged. diff --git a/doc/flame/inputs/gesture_input.md b/doc/flame/inputs/gesture_input.md index 671b76ff290..11da14b9c7e 100644 --- a/doc/flame/inputs/gesture_input.md +++ b/doc/flame/inputs/gesture_input.md @@ -1,6 +1,8 @@ # Gesture Input -This includes documentation for gesture inputs, which is, mouse and touch pointers. +This is documentation for gesture inputs attached directly on the game class, most of the time you +want to detect input on your components instead, see for example the [TapCallbacks](tap_events.md) +and [DragCallbacks](drag_events.md) for that. For other input documents, see also: @@ -143,7 +145,7 @@ on scale events: if (!currentScale.isIdentity()) { camera.zoom = startZoom * currentScale.y; } else { - camera.translateBy(-info.delta.game); + camera.translateBy(-info.delta.global); camera.snap(); } } @@ -177,9 +179,9 @@ GameWidget( ## 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. +On events that have positions, like for example `Tap*` or `Drag`, you will notice that the +`eventPosition` attribute includes 2 fields: `game` and `widget`. Below you will find a brief +explanation about each of them. ### global @@ -190,32 +192,25 @@ The position where the event occurred considering the entire screen, same as ### 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. +The position where the event occurred relative to the `GameWidget` position and size, same as +`localPosition` in Flutter's native events. ## Example ```dart -class MyGame extends Game with TapDetector { +class MyGame extends FlameGame with TapDetector { // Other methods omitted @override bool onTapDown(TapDownInfo info) { - print("Player tap down on ${info.eventPosition.game}"); + print("Player tap down on ${info.eventPosition.widget}"); return true; } @override bool onTapUp(TapUpInfo info) { - print("Player tap up on ${info.eventPosition.game}"); + print("Player tap up on ${info.eventPosition.widget}"); return true; } } @@ -225,279 +220,6 @@ You can also check more complete examples [here](https://github.com/flame-engine/flame/tree/main/examples/lib/stories/input/). -## Tappable, Draggable and Hoverable components - -Any component derived from `Component` (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 `HasTappables` mixin to your game, and using the mixin `Tappable` on your -components, you can override the following methods on your components: - -```dart -bool onTapCancel(); -bool onTapDown(TapDownInfo info); -bool onLongTapDown(TapDownInfo info); -bool onTapUp(TapUpInfo info); -``` - -Minimal component example: - -```dart -import 'package:flame/components.dart'; - -class TappableComponent extends PositionComponent with Tappable { - - // update and render omitted - - @override - bool onTapUp(TapUpInfo info) { - print("tap up"); - return true; - } - - @override - bool onTapDown(TapDownInfo info) { - print("tap down"); - return true; - } - - @override - bool onTapCancel() { - print("tap cancel"); - return true; - } -} - -class MyGame extends FlameGame with HasTappables { - MyGame() { - add(TappableComponent()); - } -} -``` - -**Note**: `HasTappables` uses an advanced gesture detector under the hood and as explained -further up on this page it shouldn't be used alongside basic detectors. - -To recognize whether a `Tappable` added to the game handled an event, the `handled` field can be set -to true in the event can be checked in the corresponding method in the game class, or further down -the chain if you let the event continue to propagate. - -In the following example it can be seen how it is used with `onTapDown`, the same technique can also -be applied to `onTapUp`. - -```dart -class MyComponent extends PositionComponent with Tappable{ - @override - bool onTapDown(TapDownInfo info) { - info.handled = true; - return true; - } -} - -class MyGame extends FlameGame with HasTappables { - @override - void onTapDown(int pointerId, TapDownInfo info) { - if (info.handled) { - // Do something if a child handled the event - } - } -} -``` - -The event `onLongTapDown` will be triggered on a component after the user "holds" it for a certain -minimum amount of time. By default, that time is 300ms, but it can be adjusted by overriding the -`longTapDelay` field of the `HasTappables` mixin. - - -### Draggable components - -Just like with `Tappable`, Flame offers a mixin for `Draggable`. - -By adding the `HasDraggables` 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 - bool onDragStart(DragStartInfo info); - bool onDragUpdate(DragUpdateInfo info); - bool onDragEnd(DragEndInfo info); - bool onDragCancel(); -``` - -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; - - bool onDragStart(DragStartInfo startPosition) { - dragDeltaPosition = startPosition.eventPosition.game - position; - return false; - } - - @override - bool onDragUpdate(DragUpdateInfo event) { - if (isDragging) { - final localCoords = event.eventPosition.game; - position = localCoords - dragDeltaPosition!; - } - return false; - } - - @override - bool onDragEnd(DragEndInfo event) { - dragDeltaPosition = null; - return false; - } - - @override - bool onDragCancel() { - dragDeltaPosition = null; - return false; - } -} - -class MyGame extends FlameGame with HasDraggables { - MyGame() { - add(DraggableComponent()); - } -} -``` - -To recognize whether a `Draggable` added to the game handled an event, the `handled` field can be -set to true in the event can be checked in the corresponding method in the game class, or further -down the chain if you let the event continue to propagate. - -In the following example it can be seen how it is used with `onDragStart`, the same technique can -also be applied to `onDragUpdate` and `onDragEnd`. - -```dart -class MyComponent extends PositionComponent with Draggable { - @override - bool onDragStart(DragStartInfo info) { - info.handled = true; - return true; - } -} - -class MyGame extends FlameGame with HasDraggables { - @override - void onDragStart(int pointerId, DragStartInfo info) { - if (info.handled) { - // Do something if a child handled the event - } - } -} -``` - - -### 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 `HasHoverables` 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; - bool onHoverEnter(PointerHoverInfo info) { - print("hover enter"); - return true; - } - bool onHoverLeave(PointerHoverInfo info) { - print("hover leave"); - return true; - } -``` - -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. - -To recognize whether a `Hoverable` added to the game handled an event, the `handled` field can be -set to true in the event can be checked in the corresponding method in the game class, or further -down the chain if you let the event continue to propagate. - -In the following example it can be seen how it is used with `onHoverEnter`, the same technique can -also be applied to `onHoverLeave`. - -```dart -class MyComponent extends PositionComponent with Hoverable { - @override - bool onHoverEnter(PointerHoverInfo info) { - info.handled = true; - return true; - } -} - -class MyGame extends FlameGame with HasHoverables { - @override - void onHoverEnter(PointerHoverInfo info) { - if (info.handled) { - // Do something if a child handled the event - } - } -} -``` - - -### DoubleTapCallbacks - -Flame also offers a mixin named `DoubleTapCallbacks` to receive a double-tap event from the -component. To start receiving double tap events in a component, add the -`DoubleTapCallbacks` mixin to your `PositionComponent`. - -```dart -class MyComponent extends PositionComponent with DoubleTapCallbacks { - @override - void onDoubleTapUp(DoubleTapEvent event) { - /// Do something - } - - @override - void onDoubleTapCancel(DoubleTapCancelEvent event) { - /// Do something - } - - @override - void onDoubleTapDown(DoubleTapDownEvent event) { - /// Do something - } -``` - - ### GestureHitboxes The `GestureHitboxes` mixin is used to more accurately recognize gestures on top of your diff --git a/doc/flame/inputs/tap_events.md b/doc/flame/inputs/tap_events.md index 35ab4f20560..a2fc7121833 100644 --- a/doc/flame/inputs/tap_events.md +++ b/doc/flame/inputs/tap_events.md @@ -167,54 +167,48 @@ class MyComponent extends Component with TapCallbacks { ``` -### HasTappablesBridge +### DoubleTapCallbacks -This marker mixin can be used to indicate that the game has both the "new-style" components that -use the `TapCallbacks` mixin, and the "old-style" components that use the `Tappable` mixin. In -effect, every tap event will be propagated twice through the system: first trying to reach the -components with `TapCallbacks` mixin, and then components with `Tappable`. +Flame also offers a mixin named `DoubleTapCallbacks` to receive a double-tap event from the +component. To start receiving double tap events in a component, add the +`DoubleTapCallbacks` mixin to your `PositionComponent`. ```dart -class MyGame extends FlameGame with HasTappablesBridge { - // ... -} -``` +class MyComponent extends PositionComponent with DoubleTapCallbacks { + @override + void onDoubleTapUp(DoubleTapEvent event) { + /// Do something + } -The purpose of this mixin is to ease the transition from the old event delivery system to the -new one. With this mixin, you can transition your `Tappable` components into using `TapCallbacks` -one by one, verifying that your game continues to work at every step. + @override + void onDoubleTapCancel(DoubleTapCancelEvent event) { + /// Do something + } -Use of this mixin for any new project is highly discouraged. + @override + void onDoubleTapDown(DoubleTapDownEvent event) { + /// Do something + } +``` ## Migration -If you have an existing game that uses `Tappable`/`HasTappables` mixins, then this section will +If you have an existing game that uses `Tappable`/`Draggable` mixins, then this section will describe how to transition to the new API described in this document. Here's what you need to do: -1. Replace the `HasTappables` mixin with the `HasTappablesBridge` mixin on your game. - Verify that your game continues to run as before. - -2. Pick any of your components that uses `Tappable`, and replace that mixin with `TapCallbacks`. - The methods `onTapDown`, `onTapUp`, `onTapCancel` and `onLongTapDown` will need to be adjusted - for the new API: - - - The argument pair such as `(int pointerId, TapDownDetails details)` was replaced with a single - event object `TapDownEvent event`. - - There is no return value anymore, but if you need to make a component to pass-through the taps - to the components below, then set `event.continuePropagation` to true. This is only needed for - `onTapDown` events -- all other events will pass-through automatically. - - If your component needs to know the coordinates of the point of touch, use - `event.localPosition` instead of computing it manually. Properties `event.canvasPosition` and - `event.devicePosition` are also available. - - If the component is a `PositionComponent`, then make sure its size is set correctly (for - example by turning on the debug mode). If the component does not derive from - `PositionComponent` then make sure it implements the method `containsLocalPoint()`. - - If the component is not attached to the root of the game, then make sure its ancestors also - have correct size or implement `containsLocalPoint()`. - -3. Run the game to verify that it works as before. - -4. Repeat step 2 until you have converted all `Tappable` mixins into `TapCallbacks`. - -5. Remove the `HasTappablesBridge` mixin from your top-level game. +Take all of your components that uses these mixins, and replace them with +`TapCallbacks`/`DragCallbacks`. +The methods `onTapDown`, `onTapUp`, `onTapCancel` and `onLongTapDown` will need to be adjusted +for the new API: + +- The argument pair such as `(int pointerId, TapDownDetails details)` was replaced with a single + event object `TapDownEvent event`. +- There is no return value anymore, but if you need to make a component to pass-through the taps + to the components below, then set `event.continuePropagation` to true. This is only needed for + `onTapDown` events -- all other events will pass-through automatically. +- If your component needs to know the coordinates of the point of touch, use + `event.localPosition` instead of computing it manually. Properties `event.canvasPosition` and + `event.devicePosition` are also available. +- If the component is attached to a custom ancestor then make sure that ancestor also have the + correct size or implement `containsLocalPoint()`. diff --git a/doc/tutorials/space_shooter/app/lib/step2/main.dart b/doc/tutorials/space_shooter/app/lib/step2/main.dart index f57576c2263..74a85d151b8 100644 --- a/doc/tutorials/space_shooter/app/lib/step2/main.dart +++ b/doc/tutorials/space_shooter/app/lib/step2/main.dart @@ -1,4 +1,5 @@ import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; import 'package:flutter/material.dart'; @@ -12,16 +13,13 @@ class SpaceShooterGame extends FlameGame with PanDetector { @override Future onLoad() async { - await super.onLoad(); - player = Player(); - add(player); } @override void onPanUpdate(DragUpdateInfo info) { - player.move(info.delta.game); + player.move(info.delta.global); } } diff --git a/examples/games/rogue_shooter/lib/rogue_shooter_game.dart b/examples/games/rogue_shooter/lib/rogue_shooter_game.dart index 7870e0acd6d..3c73f4f36bd 100644 --- a/examples/games/rogue_shooter/lib/rogue_shooter_game.dart +++ b/examples/games/rogue_shooter/lib/rogue_shooter_game.dart @@ -66,7 +66,7 @@ class RogueShooterGame extends FlameGame @override void onPanUpdate(DragUpdateInfo info) { - player.position += info.delta.game; + player.position += info.delta.global; } void increaseScore() { diff --git a/examples/games/trex/lib/trex_game.dart b/examples/games/trex/lib/trex_game.dart index 2cd10dd3fba..8f2006684e9 100644 --- a/examples/games/trex/lib/trex_game.dart +++ b/examples/games/trex/lib/trex_game.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/flame.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; diff --git a/examples/lib/stories/animations/basic_animation_example.dart b/examples/lib/stories/animations/basic_animation_example.dart index d86dbbda7e9..b699a03f0ea 100644 --- a/examples/lib/stories/animations/basic_animation_example.dart +++ b/examples/lib/stories/animations/basic_animation_example.dart @@ -2,48 +2,28 @@ import 'dart:ui'; import 'package:examples/commons/ember.dart'; import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/game.dart'; -import 'package:flame/input.dart'; -class BasicAnimationsExample extends FlameGame with TapDetector { +class BasicAnimationsExample extends FlameGame { static const description = ''' - Basic example of `SpriteAnimation`s use in Flame's `FlameGame`\n\n - - The snippet shows how an animation can be loaded and added to the game - ``` - class MyGame extends FlameGame { - @override - Future onLoad() async { - final animation = await loadSpriteAnimation( - 'animations/chopper.png', - SpriteAnimationData.sequenced( - amount: 4, - textureSize: Vector2.all(48), - stepTime: 0.15, - ), - ); - - final animationComponent = SpriteAnimationComponent( - animation: animation, - size: Vector2.all(100.0), - ); - - add(animationComponent); - } - } - ``` + Basic example of how to use `SpriteAnimation`s in Flame's. - On this example, click or touch anywhere on the screen to dynamically add + In this example, click or touch anywhere on the screen to dynamically add animations. '''; + BasicAnimationsExample() : super(world: BasicAnimationsWorld()); +} + +class BasicAnimationsWorld extends World with TapCallbacks, HasGameReference { late Image creature; @override Future onLoad() async { - creature = await images.load('animations/creature.png'); + creature = await game.images.load('animations/creature.png'); - final animation = await loadSpriteAnimation( + final animation = await game.loadSpriteAnimation( 'animations/chopper.png', SpriteAnimationData.sequenced( amount: 4, @@ -55,24 +35,25 @@ class BasicAnimationsExample extends FlameGame with TapDetector { final spriteSize = Vector2.all(100.0); final animationComponent = SpriteAnimationComponent( animation: animation, + position: Vector2(-spriteSize.x, 0), size: spriteSize, + anchor: Anchor.center, ); - animationComponent.x = size.x / 2 - spriteSize.x; - animationComponent.y = spriteSize.y; final reversedAnimationComponent = SpriteAnimationComponent( animation: animation.reversed(), + position: Vector2(spriteSize.x, 0), size: spriteSize, + anchor: Anchor.center, ); - reversedAnimationComponent.x = size.x / 2; - reversedAnimationComponent.y = spriteSize.y; add(animationComponent); add(reversedAnimationComponent); - add(Ember()..position = size / 2); + add(Ember()); } - void addAnimation(Vector2 position) { + @override + void onTapDown(TapDownEvent event) { final size = Vector2(291, 178); final animationComponent = SpriteAnimationComponent.fromFrameData( @@ -84,16 +65,12 @@ class BasicAnimationsExample extends FlameGame with TapDetector { stepTime: 0.15, loop: false, ), + position: event.localPosition, + anchor: Anchor.center, size: size, removeOnFinish: true, ); - animationComponent.position = position - size / 2; add(animationComponent); } - - @override - void onTapDown(TapDownInfo info) { - addAnimation(info.eventPosition.game); - } } diff --git a/examples/lib/stories/animations/benchmark_example.dart b/examples/lib/stories/animations/benchmark_example.dart index 7f141592c3f..a6421f8d367 100644 --- a/examples/lib/stories/animations/benchmark_example.dart +++ b/examples/lib/stories/animations/benchmark_example.dart @@ -2,20 +2,21 @@ import 'dart:math'; import 'package:examples/commons/ember.dart'; import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/game.dart'; -import 'package:flame/input.dart'; -class BenchmarkExample extends FlameGame with TapDetector { +class BenchmarkExample extends FlameGame { static const description = ''' See how many SpriteAnimationComponent's your platform can handle before it starts to drop in FPS, this is without any sprite batching and such. 100 animation components are added per tap. '''; + BenchmarkExample() : super(world: BenchmarkWorld()); + final emberSize = Vector2.all(20); late final TextComponent emberCounter; final counterPrefix = 'Animations: '; - final Random random = Random(); @override Future onLoad() async { @@ -40,17 +41,26 @@ starts to drop in FPS, this is without any sprite batching and such. emberCounter.text = '$counterPrefix ${world.children.query().length}'; } +} + +class BenchmarkWorld extends World + with TapCallbacks, HasGameReference { + final Random random = Random(); @override - void onTapDown(TapDownInfo info) { - world.addAll( + void onTapDown(TapDownEvent event) { + addAll( List.generate( 100, (_) => Ember( - size: emberSize, + size: game.emberSize, position: Vector2( - (size.x / 2) * random.nextDouble() * (random.nextBool() ? 1 : -1), - (size.y / 2) * random.nextDouble() * (random.nextBool() ? 1 : -1), + (game.size.x / 2) * + random.nextDouble() * + (random.nextBool() ? 1 : -1), + (game.size.y / 2) * + random.nextDouble() * + (random.nextBool() ? 1 : -1), ), ), ), diff --git a/examples/lib/stories/bridge_libraries/audio/basic_audio_example.dart b/examples/lib/stories/bridge_libraries/audio/basic_audio_example.dart index ffcb6997a09..38500d828d3 100644 --- a/examples/lib/stories/bridge_libraries/audio/basic_audio_example.dart +++ b/examples/lib/stories/bridge_libraries/audio/basic_audio_example.dart @@ -6,7 +6,7 @@ import 'package:flame/palette.dart'; import 'package:flame_audio/flame_audio.dart'; import 'package:flutter/painting.dart'; -class BasicAudioExample extends FlameGame with TapDetector { +class BasicAudioExample extends FlameGame { static const String description = ''' This example showcases the most basic Flame Audio functionalities. @@ -19,8 +19,11 @@ class BasicAudioExample extends FlameGame with TapDetector { static final Paint black = BasicPalette.black.paint(); static final Paint gray = const PaletteEntry(Color(0xFFCCCCCC)).paint(); - static final TextPaint text = TextPaint( - style: TextStyle(color: BasicPalette.white.color), + static final TextPaint topTextPaint = TextPaint( + style: TextStyle(color: BasicPalette.lightBlue.color), + ); + static final TextPaint bottomTextPaint = TextPaint( + style: TextStyle(color: BasicPalette.black.color), ); late AudioPool pool; @@ -33,10 +36,44 @@ class BasicAudioExample extends FlameGame with TapDetector { maxPlayers: 4, ); startBgmMusic(); + final firstButtonSize = Vector2(size.x - 40, size.y * (4 / 5)); + final secondButtonSize = Vector2(size.x - 40, size.y / 5); + addAll( + [ + ButtonComponent( + position: Vector2(20, 20), + size: firstButtonSize, + button: RectangleComponent(paint: black, size: firstButtonSize), + onPressed: fireOne, + children: [ + TextComponent( + text: 'Click here for 1', + textRenderer: topTextPaint, + position: firstButtonSize / 2, + anchor: Anchor.center, + priority: 1, + ), + ], + ), + ButtonComponent( + position: Vector2(20, size.y - size.y / 5), + size: secondButtonSize, + button: RectangleComponent(paint: gray, size: secondButtonSize), + onPressed: fireTwo, + children: [ + TextComponent( + text: 'Click here for 2', + textRenderer: bottomTextPaint, + position: secondButtonSize / 2, + anchor: Anchor.center, + priority: 1, + ), + ], + ), + ], + ); } - Rect get button => Rect.fromLTWH(20, size.y - 300, size.x - 40, 200); - void startBgmMusic() { FlameAudio.bgm.initialize(); FlameAudio.bgm.play('music/bg_music.ogg'); @@ -50,37 +87,6 @@ class BasicAudioExample extends FlameGame with TapDetector { pool.start(); } - @override - void render(Canvas canvas) { - super.render(canvas); - canvas.drawRect(size.toRect(), black); - - text.render( - canvas, - '(click anywhere for 1)', - Vector2(size.x / 2, 200), - anchor: Anchor.topCenter, - ); - - canvas.drawRect(button, gray); - - text.render( - canvas, - 'click here for 2', - Vector2(size.x / 2, size.y - 200), - anchor: Anchor.bottomCenter, - ); - } - - @override - void onTapDown(TapDownInfo info) { - if (button.containsPoint(info.eventPosition.game)) { - fireTwo(); - } else { - fireOne(); - } - } - @override void onRemove() { FlameAudio.bgm.dispose(); diff --git a/examples/lib/stories/bridge_libraries/flame_forge2d/raycast_example.dart b/examples/lib/stories/bridge_libraries/flame_forge2d/raycast_example.dart index f0ef4bd92a4..8ab66f23352 100644 --- a/examples/lib/stories/bridge_libraries/flame_forge2d/raycast_example.dart +++ b/examples/lib/stories/bridge_libraries/flame_forge2d/raycast_example.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:examples/stories/bridge_libraries/flame_forge2d/utils/boundaries.dart'; import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/input.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart' show Colors, Paint, Canvas; diff --git a/examples/lib/stories/camera_and_viewport/camera_component_example.dart b/examples/lib/stories/camera_and_viewport/camera_component_example.dart index 76f4ae92158..f9e87cc7dd8 100644 --- a/examples/lib/stories/camera_and_viewport/camera_component_example.dart +++ b/examples/lib/stories/camera_and_viewport/camera_component_example.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:flame/camera.dart'; import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/extensions.dart' show OffsetExtension; import 'package:flame/game.dart'; import 'package:flame/geometry.dart'; diff --git a/examples/lib/stories/camera_and_viewport/camera_component_properties_example.dart b/examples/lib/stories/camera_and_viewport/camera_component_properties_example.dart index 17d9c0732cd..0c9b22b3cb3 100644 --- a/examples/lib/stories/camera_and_viewport/camera_component_properties_example.dart +++ b/examples/lib/stories/camera_and_viewport/camera_component_properties_example.dart @@ -3,7 +3,7 @@ import 'dart:ui'; import 'package:flame/camera.dart'; import 'package:flame/components.dart'; import 'package:flame/events.dart'; -import 'package:flame/game.dart' hide Viewport; +import 'package:flame/game.dart'; class CameraComponentPropertiesExample extends FlameGame { static const description = ''' diff --git a/examples/lib/stories/camera_and_viewport/coordinate_systems_example.dart b/examples/lib/stories/camera_and_viewport/coordinate_systems_example.dart index d9337d13db5..c9294b56b94 100644 --- a/examples/lib/stories/camera_and_viewport/coordinate_systems_example.dart +++ b/examples/lib/stories/camera_and_viewport/coordinate_systems_example.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; import 'package:flame/palette.dart'; @@ -120,17 +121,17 @@ class CoordinateSystemsExample extends FlameGame name, 'Global: ${info.eventPosition.global}', 'Widget: ${info.eventPosition.widget}', - 'Game: ${info.eventPosition.game}', + 'World: ${camera.globalToLocal(info.eventPosition.global)}', 'Camera: ${camera.viewfinder.position}', if (info is DragUpdateInfo) ...[ 'Delta', 'Global: ${info.delta.global}', - 'Game: ${info.delta.game}', + 'World: ${info.delta.global / camera.viewfinder.zoom}', ], if (info is PointerScrollInfo) ...[ 'Scroll Delta', 'Global: ${info.scrollDelta.global}', - 'Game: ${info.scrollDelta.game}', + 'World: ${info.scrollDelta.global / camera.viewfinder.zoom}', ], ].join('\n'); } diff --git a/examples/lib/stories/camera_and_viewport/static_components_example.dart b/examples/lib/stories/camera_and_viewport/static_components_example.dart index b14e21fb104..3b0fb1fd713 100644 --- a/examples/lib/stories/camera_and_viewport/static_components_example.dart +++ b/examples/lib/stories/camera_and_viewport/static_components_example.dart @@ -36,17 +36,10 @@ class StaticComponentsExample extends FlameGame myParallax, TextComponent( text: 'Center backdrop Component', - position: camera.viewport.size / 2 + Vector2(0, 30), + position: camera.viewport.virtualSize / 2 + Vector2(0, 30), anchor: Anchor.center, ), ]); - camera.viewfinder.addAll([ - TextComponent( - text: 'Corner Viewfinder Component', - position: camera.viewport.size - Vector2.all(10), - anchor: Anchor.bottomRight, - ), - ]); camera.viewport.addAll( [ TextComponent( @@ -55,7 +48,7 @@ class StaticComponentsExample extends FlameGame ), TextComponent( text: 'Center Viewport Component', - position: camera.viewport.size / 2, + position: camera.viewport.virtualSize / 2, anchor: Anchor.center, ), ], diff --git a/examples/lib/stories/camera_and_viewport/zoom_example.dart b/examples/lib/stories/camera_and_viewport/zoom_example.dart index fd5237d1e33..ce37978415f 100644 --- a/examples/lib/stories/camera_and_viewport/zoom_example.dart +++ b/examples/lib/stories/camera_and_viewport/zoom_example.dart @@ -1,4 +1,5 @@ import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; @@ -37,7 +38,8 @@ class ZoomExample extends FlameGame with ScrollDetector, ScaleDetector { @override void onScroll(PointerScrollInfo info) { - camera.viewfinder.zoom += info.scrollDelta.game.y.sign * zoomPerScrollUnit; + camera.viewfinder.zoom += + info.scrollDelta.global.y.sign * zoomPerScrollUnit; clampZoom(); } @@ -55,7 +57,7 @@ class ZoomExample extends FlameGame with ScrollDetector, ScaleDetector { camera.viewfinder.zoom = startZoom * currentScale.y; clampZoom(); } else { - final delta = info.delta.game; + final delta = info.delta.global; camera.viewfinder.position.translate(-delta.x, -delta.y); } } diff --git a/examples/lib/stories/collision_detection/circles_example.dart b/examples/lib/stories/collision_detection/circles_example.dart index bd71f71eaad..f3e8b7de19d 100644 --- a/examples/lib/stories/collision_detection/circles_example.dart +++ b/examples/lib/stories/collision_detection/circles_example.dart @@ -1,5 +1,6 @@ import 'package:flame/collisions.dart'; import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; import 'package:flutter/material.dart' hide Image, Draggable; @@ -18,7 +19,7 @@ class CirclesExample extends FlameGame with HasCollisionDetection, TapDetector { @override void onTapDown(TapDownInfo info) { - add(MyCollidable(info.eventPosition.game)); + add(MyCollidable(info.eventPosition.widget)); } } diff --git a/examples/lib/stories/collision_detection/quadtree_example.dart b/examples/lib/stories/collision_detection/quadtree_example.dart index df28345315c..8be1859df93 100644 --- a/examples/lib/stories/collision_detection/quadtree_example.dart +++ b/examples/lib/stories/collision_detection/quadtree_example.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:flame/collisions.dart'; import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; @@ -191,7 +192,7 @@ Press T button to toggle player to collide with other objects. @override void onScroll(PointerScrollInfo info) { - camera.viewfinder.zoom += info.scrollDelta.game.y.sign * 0.08; + camera.viewfinder.zoom += info.scrollDelta.global.y.sign * 0.08; camera.viewfinder.zoom = camera.viewfinder.zoom.clamp(0.05, 5.0); } } diff --git a/examples/lib/stories/collision_detection/raycast_light_example.dart b/examples/lib/stories/collision_detection/raycast_light_example.dart index 4152ce562c1..b6f8864b647 100644 --- a/examples/lib/stories/collision_detection/raycast_light_example.dart +++ b/examples/lib/stories/collision_detection/raycast_light_example.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:flame/collisions.dart'; import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/game.dart'; import 'package:flame/geometry.dart'; import 'package:flame/input.dart'; @@ -89,14 +90,14 @@ with with mouse. @override void onTapDown(TapDownInfo info) { super.onTapDown(info); - final origin = info.eventPosition.game; + final origin = info.eventPosition.widget; isTapOriginCasted = origin == tapOrigin; tapOrigin = origin; } @override void onMouseMove(PointerHoverInfo info) { - final origin = info.eventPosition.game; + final origin = info.eventPosition.widget; isOriginCasted = origin == this.origin; this.origin = origin; } diff --git a/examples/lib/stories/collision_detection/raytrace_example.dart b/examples/lib/stories/collision_detection/raytrace_example.dart index 741816f1579..5115ed8bc60 100644 --- a/examples/lib/stories/collision_detection/raytrace_example.dart +++ b/examples/lib/stories/collision_detection/raytrace_example.dart @@ -129,7 +129,7 @@ bounce on will appear. @override void onMouseMove(PointerHoverInfo info) { - final origin = info.eventPosition.game; + final origin = info.eventPosition.widget; isOriginCasted = origin == this.origin; this.origin = origin; } diff --git a/examples/lib/stories/components/clip_component_example.dart b/examples/lib/stories/components/clip_component_example.dart index 28e0de87e5e..84f7bb8274e 100644 --- a/examples/lib/stories/components/clip_component_example.dart +++ b/examples/lib/stories/components/clip_component_example.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:flame/components.dart'; import 'package:flame/effects.dart'; +import 'package:flame/events.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; import 'package:flutter/material.dart' hide Gradient; @@ -62,7 +63,7 @@ class ClipComponentExample extends FlameGame with TapDetector { @override void onTapUp(TapUpInfo info) { - final position = info.eventPosition.game; + final position = info.eventPosition.widget; final hit = children .whereType() .where( diff --git a/examples/lib/stories/components/spawn_component_example.dart b/examples/lib/stories/components/spawn_component_example.dart index da4211d5439..a4830a11e67 100644 --- a/examples/lib/stories/components/spawn_component_example.dart +++ b/examples/lib/stories/components/spawn_component_example.dart @@ -1,5 +1,6 @@ import 'package:examples/commons/ember.dart'; import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/experimental.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; @@ -10,19 +11,23 @@ class SpawnComponentExample extends FlameGame with TapDetector { static const String description = 'Tap on the screen to start spawning Embers within different shapes.'; + SpawnComponentExample() : super(world: SpawnComponentWorld()); +} + +class SpawnComponentWorld extends World with TapCallbacks { @override - void onTapDown(TapDownInfo info) { + void onTapDown(TapDownEvent info) { final shapeType = Shapes.values.random(); final Shape shape; - final position = info.eventPosition.game; + final position = info.localPosition; switch (shapeType) { case Shapes.rectangle: shape = Rectangle.fromCenter( - center: info.eventPosition.game, + center: position, size: Vector2.all(200), ); case Shapes.circle: - shape = Circle(info.eventPosition.game, 150); + shape = Circle(position, 150); case Shapes.polygon: shape = Polygon( [ diff --git a/examples/lib/stories/effects/rotate_effect_example.dart b/examples/lib/stories/effects/rotate_effect_example.dart index b4586e7b9d2..2de51f70a99 100644 --- a/examples/lib/stories/effects/rotate_effect_example.dart +++ b/examples/lib/stories/effects/rotate_effect_example.dart @@ -22,7 +22,7 @@ class RotateEffectExample extends FlameGame { camera: CameraComponent.withFixedResolution( width: 400, height: 600, - )..viewfinder.anchor = Anchor.topLeft, + ), world: _RotateEffectWorld(), ); } diff --git a/examples/lib/stories/input/mouse_cursor_example.dart b/examples/lib/stories/input/mouse_cursor_example.dart index 1ef1a2ddad4..c352d5dcce3 100644 --- a/examples/lib/stories/input/mouse_cursor_example.dart +++ b/examples/lib/stories/input/mouse_cursor_example.dart @@ -1,3 +1,4 @@ +import 'package:flame/events.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; @@ -23,7 +24,7 @@ class MouseCursorExample extends FlameGame with MouseMovementDetector { @override void onMouseMove(PointerHoverInfo info) { - target = info.eventPosition.game; + target = info.eventPosition.widget; } Rect _toRect() => position.toPositionedRect(objSize); diff --git a/examples/lib/stories/input/mouse_movement_example.dart b/examples/lib/stories/input/mouse_movement_example.dart index 5799ca11802..7864dba6d1b 100644 --- a/examples/lib/stories/input/mouse_movement_example.dart +++ b/examples/lib/stories/input/mouse_movement_example.dart @@ -1,3 +1,4 @@ +import 'package:flame/events.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; @@ -23,7 +24,7 @@ class MouseMovementExample extends FlameGame with MouseMovementDetector { @override void onMouseMove(PointerHoverInfo info) { - target = info.eventPosition.game; + target = info.eventPosition.widget; } Rect _toRect() => position.toPositionedRect(objSize); diff --git a/examples/lib/stories/input/multitap_advanced_example.dart b/examples/lib/stories/input/multitap_advanced_example.dart index d82ca0a7614..55cc834df00 100644 --- a/examples/lib/stories/input/multitap_advanced_example.dart +++ b/examples/lib/stories/input/multitap_advanced_example.dart @@ -1,3 +1,4 @@ +import 'package:flame/events.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; @@ -23,7 +24,7 @@ class MultitapAdvancedExample extends FlameGame @override void onTapDown(int pointerId, TapDownInfo info) { - taps[pointerId] = info.eventPosition.game.toPositionedRect(tapSize); + taps[pointerId] = info.eventPosition.widget.toPositionedRect(tapSize); } @override @@ -46,12 +47,12 @@ class MultitapAdvancedExample extends FlameGame @override void onDragStart(int pointerId, DragStartInfo info) { end = null; - start = info.eventPosition.game; + start = info.eventPosition.widget; } @override void onDragUpdate(int pointerId, DragUpdateInfo info) { - end = info.eventPosition.game; + end = info.eventPosition.widget; } @override diff --git a/examples/lib/stories/input/multitap_example.dart b/examples/lib/stories/input/multitap_example.dart index 66e8bc1679f..1ce89a0514b 100644 --- a/examples/lib/stories/input/multitap_example.dart +++ b/examples/lib/stories/input/multitap_example.dart @@ -1,3 +1,4 @@ +import 'package:flame/events.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; @@ -18,7 +19,7 @@ class MultitapExample extends FlameGame with MultiTouchTapDetector { @override void onTapDown(int pointerId, TapDownInfo info) { - taps[pointerId] = info.eventPosition.game.toPositionedRect(tapSize); + taps[pointerId] = info.eventPosition.widget.toPositionedRect(tapSize); } @override diff --git a/examples/lib/stories/input/scroll_example.dart b/examples/lib/stories/input/scroll_example.dart index cea8e2886e7..a3edccd483f 100644 --- a/examples/lib/stories/input/scroll_example.dart +++ b/examples/lib/stories/input/scroll_example.dart @@ -1,3 +1,4 @@ +import 'package:flame/events.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; @@ -19,7 +20,7 @@ class ScrollExample extends FlameGame with ScrollDetector { @override void onScroll(PointerScrollInfo info) { - target = position + info.scrollDelta.game * 5; + target = position + info.scrollDelta.global * 5; } @override diff --git a/examples/lib/stories/rendering/isometric_tile_map_example.dart b/examples/lib/stories/rendering/isometric_tile_map_example.dart index 26c2538b97b..3fad87cf0ce 100644 --- a/examples/lib/stories/rendering/isometric_tile_map_example.dart +++ b/examples/lib/stories/rendering/isometric_tile_map_example.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; @@ -72,7 +73,7 @@ class IsometricTileMapExample extends FlameGame with MouseMovementDetector { @override void onMouseMove(PointerHoverInfo info) { - final screenPosition = info.eventPosition.game; + final screenPosition = info.eventPosition.widget; final block = base.getBlock(screenPosition); selector.show = base.containsBlock(block); selector.position.setFrom(topLeft + base.getBlockRenderPosition(block)); diff --git a/examples/lib/stories/rendering/particles_interactive_example.dart b/examples/lib/stories/rendering/particles_interactive_example.dart index b08ff2a2240..d491fd67b2f 100644 --- a/examples/lib/stories/rendering/particles_interactive_example.dart +++ b/examples/lib/stories/rendering/particles_interactive_example.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; import 'package:flame/particles.dart'; @@ -32,7 +33,7 @@ class ParticlesInteractiveExample extends FlameGame with PanDetector { void onPanUpdate(DragUpdateInfo info) { add( ParticleSystemComponent( - position: info.eventPosition.game, + position: info.eventPosition.widget, particle: Particle.generate( count: 40, generator: (i) { diff --git a/packages/flame/lib/components.dart b/packages/flame/lib/components.dart index a0352d8f7ae..c5a3b1b0408 100644 --- a/packages/flame/lib/components.dart +++ b/packages/flame/lib/components.dart @@ -20,7 +20,6 @@ export 'src/components/input/toggle_button_component.dart'; export 'src/components/isometric_tile_map_component.dart'; export 'src/components/mixins/component_viewport_margin.dart'; export 'src/components/mixins/coordinate_transform.dart'; -export 'src/components/mixins/draggable.dart'; export 'src/components/mixins/gesture_hitboxes.dart'; export 'src/components/mixins/has_ancestor.dart'; export 'src/components/mixins/has_decorator.dart' show HasDecorator; @@ -30,13 +29,11 @@ export 'src/components/mixins/has_paint.dart'; export 'src/components/mixins/has_time_scale.dart'; export 'src/components/mixins/has_visibility.dart'; export 'src/components/mixins/has_world.dart'; -export 'src/components/mixins/hoverable.dart'; export 'src/components/mixins/keyboard_handler.dart'; export 'src/components/mixins/notifier.dart'; export 'src/components/mixins/parent_is_a.dart'; export 'src/components/mixins/single_child_particle.dart'; export 'src/components/mixins/snapshot.dart'; -export 'src/components/mixins/tappable.dart'; export 'src/components/nine_tile_box_component.dart'; export 'src/components/parallax_component.dart'; export 'src/components/particle_system_component.dart'; diff --git a/packages/flame/lib/events.dart b/packages/flame/lib/events.dart index b56bd7d0899..b76e180db49 100644 --- a/packages/flame/lib/events.dart +++ b/packages/flame/lib/events.dart @@ -5,10 +5,6 @@ export 'src/events/component_mixins/hover_callbacks.dart' show HoverCallbacks; export 'src/events/component_mixins/pointer_move_callbacks.dart' show PointerMoveCallbacks; export 'src/events/component_mixins/tap_callbacks.dart' show TapCallbacks; -export 'src/events/flame_game_mixins/has_draggables_bridge.dart' - show HasDraggablesBridge; -export 'src/events/flame_game_mixins/has_tappables_bridge.dart' - show HasTappablesBridge; export 'src/events/game_mixins/multi_touch_drag_detector.dart' show MultiTouchDragDetector; export 'src/events/game_mixins/multi_touch_tap_detector.dart' @@ -29,12 +25,6 @@ export 'src/events/messages/pointer_move_event.dart' show PointerMoveEvent; export 'src/events/messages/tap_cancel_event.dart' show TapCancelEvent; export 'src/events/messages/tap_down_event.dart' show TapDownEvent; export 'src/events/messages/tap_up_event.dart' show TapUpEvent; -// ignore: deprecated_member_use_from_same_package -export 'src/game/mixins/has_draggables.dart' show HasDraggables; -// ignore: deprecated_member_use_from_same_package -export 'src/game/mixins/has_hoverables.dart' show HasHoverables; -// ignore: deprecated_member_use_from_same_package -export 'src/game/mixins/has_tappables.dart' show HasTappables; export 'src/game/mixins/keyboard.dart' show HasKeyboardHandlerComponents, KeyboardEvents; export 'src/gestures/detectors.dart' @@ -63,6 +53,7 @@ export 'src/gestures/events.dart' LongPressStartInfo, PointerHoverInfo, PointerScrollInfo, + PositionInfo, ScaleEndInfo, ScaleStartInfo, ScaleUpdateInfo, diff --git a/packages/flame/lib/experimental.dart b/packages/flame/lib/experimental.dart index ec805562502..dc06a6fa217 100644 --- a/packages/flame/lib/experimental.dart +++ b/packages/flame/lib/experimental.dart @@ -9,8 +9,6 @@ /// After the components lived here for some time, and when we gain more /// confidence in their robustness, they will be moved out into the main Flame /// library. -export 'src/experimental/fixed_integer_resolution_viewport.dart' - show FixedIntegerResolutionViewport; export 'src/experimental/geometry/shapes/circle.dart' show Circle; export 'src/experimental/geometry/shapes/polygon.dart' show Polygon; export 'src/experimental/geometry/shapes/rectangle.dart' show Rectangle; diff --git a/packages/flame/lib/game.dart b/packages/flame/lib/game.dart index 6b8de0c629f..76108a6a968 100644 --- a/packages/flame/lib/game.dart +++ b/packages/flame/lib/game.dart @@ -6,14 +6,9 @@ export 'src/components/route.dart' show Route; export 'src/components/router_component.dart' show RouterComponent; export 'src/components/value_route.dart' show ValueRoute; export 'src/extensions/vector2.dart'; -export 'src/game/camera/camera.dart'; -export 'src/game/camera/viewport.dart'; export 'src/game/flame_game.dart'; export 'src/game/game.dart'; export 'src/game/game_widget/game_widget.dart'; -export 'src/game/mixins/has_draggables.dart'; -export 'src/game/mixins/has_hoverables.dart'; -export 'src/game/mixins/has_tappables.dart'; export 'src/game/mixins/single_game_instance.dart'; export 'src/game/notifying_vector2.dart'; export 'src/game/projector.dart'; diff --git a/packages/flame/lib/input.dart b/packages/flame/lib/input.dart index 37f47ce0c20..47cc3a9e498 100644 --- a/packages/flame/lib/input.dart +++ b/packages/flame/lib/input.dart @@ -10,4 +10,3 @@ export 'src/events/game_mixins/multi_touch_tap_detector.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/src/camera/viewfinder.dart b/packages/flame/lib/src/camera/viewfinder.dart index a380a4c28f9..4b18c29cc16 100644 --- a/packages/flame/lib/src/camera/viewfinder.dart +++ b/packages/flame/lib/src/camera/viewfinder.dart @@ -4,7 +4,6 @@ import 'package:flame/extensions.dart'; import 'package:flame/src/anchor.dart'; import 'package:flame/src/camera/camera_component.dart'; import 'package:flame/src/components/core/component.dart'; -import 'package:flame/src/components/mixins/parent_is_a.dart'; import 'package:flame/src/effects/provider_interfaces.dart'; import 'package:flame/src/game/transform2d.dart'; import 'package:meta/meta.dart'; @@ -19,21 +18,17 @@ import 'package:meta/meta.dart'; /// If you add children to the [Viewfinder] they will appear like HUDs i.e. /// statically in front of the world. class Viewfinder extends Component - with ParentIsA implements AnchorProvider, AngleProvider, PositionProvider, ScaleProvider { - /// Internal transform matrix used by the viewfinder. - final Transform2D _transform = Transform2D(); - - @internal - Transform2D get transform => _transform; + /// Transform matrix used by the viewfinder. + final Transform2D transform = Transform2D(); /// The game coordinates of a point that is to be positioned at the center /// of the viewport. @override - Vector2 get position => -_transform.offset; + Vector2 get position => -transform.offset; @override set position(Vector2 value) { - _transform.offset = -value; + transform.offset = -value; visibleRect = null; } @@ -46,10 +41,10 @@ class Viewfinder extends Component /// the game world will appear further away and smaller in size. /// /// See also: [visibleGameSize] for setting the zoom level dynamically. - double get zoom => _transform.scale.x; + double get zoom => transform.scale.x; set zoom(double value) { assert(value > 0, 'zoom level must be positive: $value'); - _transform.scale = Vector2.all(value); + transform.scale = Vector2.all(value); visibleRect = null; } @@ -57,10 +52,10 @@ class Viewfinder extends Component /// /// The rotation is around the axis that is perpendicular to the screen. @override - double get angle => -_transform.angle; + double get angle => -transform.angle; @override set angle(double value) { - _transform.angle = -value; + transform.angle = -value; visibleRect = null; } @@ -83,7 +78,7 @@ class Viewfinder extends Component } /// Reference to the parent camera. - CameraComponent get camera => parent; + CameraComponent get camera => parent! as CameraComponent; /// Convert a point from the global coordinate system to the viewfinder's /// coordinate system. @@ -149,18 +144,15 @@ class Viewfinder extends Component @protected Rect computeVisibleRect() { final viewportSize = camera.viewport.size; - final currentTransform = transform; - final topLeft = currentTransform.globalToLocal(Vector2.zero()); - final bottomRight = currentTransform.globalToLocal(viewportSize); + final topLeft = transform.globalToLocal(Vector2.zero()); + final bottomRight = transform.globalToLocal(viewportSize); var minX = min(topLeft.x, bottomRight.x); var minY = min(topLeft.y, bottomRight.y); var maxX = max(topLeft.x, bottomRight.x); var maxY = max(topLeft.y, bottomRight.y); if (angle != 0) { - final topRight = - currentTransform.globalToLocal(Vector2(viewportSize.x, 0)); - final bottomLeft = - currentTransform.globalToLocal(Vector2(0, viewportSize.y)); + final topRight = transform.globalToLocal(Vector2(viewportSize.x, 0)); + final bottomLeft = transform.globalToLocal(Vector2(0, viewportSize.y)); minX = min(minX, min(topRight.x, bottomLeft.x)); minY = min(minY, min(topRight.y, bottomLeft.y)); maxX = max(maxX, max(topRight.x, bottomLeft.x)); @@ -188,10 +180,12 @@ class Viewfinder extends Component /// Called by the viewport when its size changes. @internal void onViewportResize() { - final viewportSize = camera.viewport.virtualSize; - _transform.position.x = viewportSize.x * _anchor.x; - _transform.position.y = viewportSize.y * _anchor.y; - visibleRect = null; + if (parent != null) { + final viewportSize = camera.viewport.virtualSize; + transform.position.x = viewportSize.x * _anchor.x; + transform.position.y = viewportSize.y * _anchor.y; + visibleRect = null; + } } @mustCallSuper @@ -205,6 +199,10 @@ class Viewfinder extends Component @mustCallSuper @override void onMount() { + assert( + parent! is CameraComponent, + 'Viewfinder can only be mounted to a CameraComponent', + ); super.onMount(); updateTransform(); } @@ -218,7 +216,7 @@ class Viewfinder extends Component /// [ScaleProvider]'s API. @internal @override - Vector2 get scale => _transform.scale; + Vector2 get scale => transform.scale; @internal @override set scale(Vector2 value) { @@ -227,7 +225,7 @@ class Viewfinder extends Component 'Non-uniform scale cannot be applied to a Viewfinder: $value', ); assert(value.x > 0, 'Zoom must be positive: ${value.x}'); - _transform.scale = value; + transform.scale = value; visibleRect = null; } } diff --git a/packages/flame/lib/src/camera/viewport.dart b/packages/flame/lib/src/camera/viewport.dart index 51d966fcd16..dbd17a4dafb 100644 --- a/packages/flame/lib/src/camera/viewport.dart +++ b/packages/flame/lib/src/camera/viewport.dart @@ -3,8 +3,8 @@ import 'dart:ui'; import 'package:flame/game.dart'; import 'package:flame/src/anchor.dart'; import 'package:flame/src/camera/camera_component.dart'; +import 'package:flame/src/camera/viewports/fixed_resolution_viewport.dart'; import 'package:flame/src/components/core/component.dart'; -import 'package:flame/src/components/mixins/parent_is_a.dart'; import 'package:flame/src/effects/provider_interfaces.dart'; import 'package:meta/meta.dart'; @@ -22,7 +22,6 @@ import 'package:meta/meta.dart'; /// A viewport establishes its own local coordinate system, with the origin at /// the top left corner of the viewport's bounding box. abstract class Viewport extends Component - with ParentIsA implements AnchorProvider, PositionProvider, SizeProvider { Viewport({super.children}); @@ -80,7 +79,7 @@ abstract class Viewport extends Component ); _size.setFrom(value); _isInitialized = true; - if (isLoaded) { + if (parent != null) { camera.viewfinder.onViewportResize(); } onViewportResize(); @@ -90,7 +89,7 @@ abstract class Viewport extends Component } /// Reference to the parent camera. - CameraComponent get camera => parent; + CameraComponent get camera => parent! as CameraComponent; /// Apply clip mask to the [canvas]. /// diff --git a/packages/flame/lib/src/components/core/component.dart b/packages/flame/lib/src/components/core/component.dart index 38f34e441a5..87182cde377 100644 --- a/packages/flame/lib/src/components/core/component.dart +++ b/packages/flame/lib/src/components/core/component.dart @@ -7,7 +7,6 @@ import 'package:flame/src/components/core/component_tree_root.dart'; import 'package:flame/src/effects/provider_interfaces.dart'; import 'package:flame/src/game/flame_game.dart'; import 'package:flame/src/game/game.dart'; -import 'package:flame/src/gestures/events.dart'; import 'package:flutter/painting.dart'; import 'package:meta/meta.dart'; @@ -982,35 +981,6 @@ class Component { void renderDebugMode(Canvas canvas) {} //#endregion - - //#region Legacy component placement overrides - - /// What coordinate system this component should respect (i.e. should it - /// observe camera, viewport, or use the raw canvas). - /// - /// Do note that this currently only works if the component is added directly - /// to the root `FlameGame`. - @Deprecated(''' - Use the CameraComponent and add your component to the viewport with - cameraComponent.viewport.add(yourHudComponent) instead. - This will be removed in Flame v1.10.0. - ''') - PositionType positionType = PositionType.game; - - @Deprecated('To be removed in Flame v1.10.0') - @protected - Vector2 eventPosition(PositionInfo info) { - switch (positionType) { - case PositionType.game: - return info.eventPosition.game; - case PositionType.viewport: - return info.eventPosition.viewport; - case PositionType.widget: - return info.eventPosition.widget; - } - } - - //#endregion } typedef ComponentSetFactory = ComponentSet Function(); diff --git a/packages/flame/lib/src/components/input/hud_button_component.dart b/packages/flame/lib/src/components/input/hud_button_component.dart index f3d9d27259d..57dcb9d9a1a 100644 --- a/packages/flame/lib/src/components/input/hud_button_component.dart +++ b/packages/flame/lib/src/components/input/hud_button_component.dart @@ -8,7 +8,7 @@ import 'package:flutter/rendering.dart' show EdgeInsets; /// Note: You have to set the [button] in [onLoad] if you are not passing it in /// through the constructor. class HudButtonComponent extends ButtonComponent - with HasGameRef, ComponentViewportMargin { + with HasGameReference, ComponentViewportMargin { HudButtonComponent({ super.button, super.buttonDown, diff --git a/packages/flame/lib/src/components/mixins/component_viewport_margin.dart b/packages/flame/lib/src/components/mixins/component_viewport_margin.dart index 94cdf170191..1edea21dca4 100644 --- a/packages/flame/lib/src/components/mixins/component_viewport_margin.dart +++ b/packages/flame/lib/src/components/mixins/component_viewport_margin.dart @@ -1,11 +1,13 @@ +import 'package:flame/camera.dart'; import 'package:flame/components.dart'; -import 'package:flame/game.dart'; +import 'package:flame/effects.dart'; import 'package:flutter/widgets.dart' show EdgeInsets; import 'package:meta/meta.dart'; /// The [ComponentViewportMargin] positions itself by a margin to the edge of -/// the [Viewport] instead of by an absolute position on the screen or on the -/// game, so if the game is resized the component will move to keep its margin. +/// the [Viewport] (or another parent with a `size`) instead of by an absolute +/// position on the screen or on the game, so if the game is resized the +/// component will move to keep its margin. /// /// Note that the margin is calculated to the [Anchor], not to the edge of the /// component. @@ -16,13 +18,10 @@ import 'package:meta/meta.dart'; /// /// Do note that this only works with the old style camera and not the /// [CameraComponent]. -mixin ComponentViewportMargin on PositionComponent, HasGameRef { - // TODO(Lukas): Don't use PositionType here and use CameraComponent. - @override - PositionType positionType = PositionType.viewport; - +// TODO(Lukas): Rename this since it isn't necessarily related to the viewport. +mixin ComponentViewportMargin on PositionComponent, HasGameReference { /// Instead of setting a position of the [PositionComponent] that uses - /// [ComponentViewportMargin] a margin from the edges of the viewport can be + /// [ComponentViewportMargin] a margin from the edges of the parent can be /// used instead. EdgeInsets? margin; @@ -30,15 +29,18 @@ mixin ComponentViewportMargin on PositionComponent, HasGameRef { @mustCallSuper Future onLoad() async { super.onLoad(); + assert(parent is ReadOnlySizeProvider, 'The parent must provide a size.'); // If margin is not null we will update the position `onGameResize` instead if (margin == null) { - final screenSize = game.size; + final bounds = parent is Viewport + ? (parent! as Viewport).virtualSize + : (parent! as ReadOnlySizeProvider).size; final topLeft = anchor.toOtherAnchorPosition( position, Anchor.topLeft, scaledSize, ); - final bottomRight = screenSize - + final bottomRight = bounds - anchor.toOtherAnchorPosition( position, Anchor.bottomRight, @@ -66,17 +68,16 @@ mixin ComponentViewportMargin on PositionComponent, HasGameRef { } void _updateMargins() { - final screenSize = positionType == PositionType.viewport - // ignore: deprecated_member_use_from_same_package - ? game.oldCamera.viewport.effectiveSize - : game.canvasSize; + final bounds = parent is Viewport + ? (parent! as Viewport).virtualSize + : (parent! as ReadOnlySizeProvider).size; final margin = this.margin!; final x = margin.left != 0 ? margin.left + scaledSize.x / 2 - : screenSize.x - margin.right - scaledSize.x / 2; + : bounds.x - margin.right - scaledSize.x / 2; final y = margin.top != 0 ? margin.top + scaledSize.y / 2 - : screenSize.y - margin.bottom - scaledSize.y / 2; + : bounds.y - margin.bottom - scaledSize.y / 2; position.setValues(x, y); position = Anchor.center.toOtherAnchorPosition( position, diff --git a/packages/flame/lib/src/components/mixins/draggable.dart b/packages/flame/lib/src/components/mixins/draggable.dart deleted file mode 100644 index 14380996a09..00000000000 --- a/packages/flame/lib/src/components/mixins/draggable.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flame/events.dart'; -import 'package:meta/meta.dart'; - -@Deprecated( - 'Will be removed in Flame v1.10.0, use the DragCallbacks mixin instead.', -) -mixin Draggable on Component { - bool _isDragged = false; - bool get isDragged => _isDragged; - - /// Override this to handle the start of a drag/pan gesture that is within the - /// boundaries (determined by [Component.containsPoint]) of the component that - /// this mixin is used on. - /// Return `true` if you want this event to continue to be passed on to - /// components underneath (lower priority) this component. - bool onDragStart(DragStartInfo info) { - return true; - } - - /// Override this to handle the update of a drag/pan gesture that is within - /// the boundaries (determined by [Component.containsPoint]) of the component - /// that this mixin is used on. - /// Return `true` if you want this event to continue to be passed on to - /// components underneath (lower priority) this component. - bool onDragUpdate(DragUpdateInfo info) { - return true; - } - - /// Override this to handle the end of a drag/pan gesture that is within - /// the boundaries (determined by [Component.containsPoint]) of the component - /// that this mixin is used on. - /// Return `true` if you want this event to continue to be passed on to - /// components underneath (lower priority) this component. - bool onDragEnd(DragEndInfo info) { - return true; - } - - /// Override this to handle if a drag/pan gesture is cancelled that was - /// previously started on the component that this mixin is used on. - /// Return `true` if you want this event to continue to be passed on to - /// components underneath (lower priority) this component. - /// - /// This event is not that common, it can happen for example when the user - /// is interrupted by a system-modal dialog in the middle of the drag. - bool onDragCancel() { - return true; - } - - final List _currentPointerIds = []; - bool _checkPointerId(int pointerId) => _currentPointerIds.contains(pointerId); - - bool handleDragStart(int pointerId, DragStartInfo info) { - if (containsPoint(eventPosition(info))) { - _isDragged = true; - _currentPointerIds.add(pointerId); - return onDragStart(info); - } - return true; - } - - bool handleDragUpdated(int pointerId, DragUpdateInfo info) { - if (_checkPointerId(pointerId)) { - return onDragUpdate(info); - } - return true; - } - - bool handleDragEnded(int pointerId, DragEndInfo info) { - if (_checkPointerId(pointerId)) { - _isDragged = false; - _currentPointerIds.remove(pointerId); - return onDragEnd(info); - } - return true; - } - - bool handleDragCanceled(int pointerId) { - if (_checkPointerId(pointerId)) { - _isDragged = false; - _currentPointerIds.remove(pointerId); - return onDragCancel(); - } - return true; - } - - @override - @mustCallSuper - void onMount() { - super.onMount(); - assert( - (() { - final game = findGame()!; - // ignore: deprecated_member_use_from_same_package - return game is HasDraggables || game is HasDraggablesBridge; - })(), - 'Draggable Components can only be added to a FlameGame with ' - 'HasDraggables or HasDraggablesBridge', - ); - } -} diff --git a/packages/flame/lib/src/components/mixins/hoverable.dart b/packages/flame/lib/src/components/mixins/hoverable.dart deleted file mode 100644 index c9ff7948c34..00000000000 --- a/packages/flame/lib/src/components/mixins/hoverable.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flame/src/game/mixins/has_hoverables.dart'; -import 'package:flame/src/gestures/events.dart'; -import 'package:meta/meta.dart'; - -@Deprecated( - 'Will be removed in Flame v1.10.0, use the HoverCallbacks mixin instead.', -) -mixin Hoverable on Component { - bool _isHovered = false; - bool get isHovered => _isHovered; - bool onHoverEnter(PointerHoverInfo info) { - return true; - } - - bool onHoverLeave(PointerHoverInfo info) { - return true; - } - - @nonVirtual - bool handleMouseMovement(PointerHoverInfo info) { - if (containsPoint(eventPosition(info))) { - if (!_isHovered) { - _isHovered = true; - return onHoverEnter(info); - } - } else { - if (_isHovered) { - _isHovered = false; - return onHoverLeave(info); - } - } - return true; - } - - @override - @mustCallSuper - void onMount() { - super.onMount(); - assert( - findGame()! is HasHoverables, - 'Hoverable Components can only be added to a FlameGame with ' - 'HasHoverables', - ); - } -} diff --git a/packages/flame/lib/src/components/mixins/tappable.dart b/packages/flame/lib/src/components/mixins/tappable.dart deleted file mode 100644 index b3a828d9699..00000000000 --- a/packages/flame/lib/src/components/mixins/tappable.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flame/src/events/flame_game_mixins/has_tappables_bridge.dart'; -import 'package:flame/src/game/mixins/has_tappables.dart'; -import 'package:flame/src/gestures/events.dart'; -import 'package:flutter/gestures.dart'; -import 'package:meta/meta.dart'; - -/// Mixin that can be added to any [Component] allowing it to receive tap -/// events. -/// -/// See [MultiTapGestureRecognizer] for the description of each individual -/// event. -@Deprecated( - 'Will be removed in Flame v1.10.0, use the TapCallbacks mixin instead.', -) -mixin Tappable on Component { - bool onTapDown(TapDownInfo info) => true; - bool onLongTapDown(TapDownInfo info) => true; - bool onTapUp(TapUpInfo info) => true; - bool onTapCancel() => true; - - int? _currentPointerId; - - bool _checkPointerId(int pointerId) => _currentPointerId == pointerId; - - bool handleTapDown(int pointerId, TapDownInfo info) { - if (containsPoint(eventPosition(info))) { - _currentPointerId = pointerId; - return onTapDown(info); - } - return true; - } - - bool handleTapUp(int pointerId, TapUpInfo info) { - if (_checkPointerId(pointerId) && containsPoint(eventPosition(info))) { - _currentPointerId = null; - return onTapUp(info); - } - return true; - } - - bool handleTapCancel(int pointerId) { - if (_checkPointerId(pointerId)) { - _currentPointerId = null; - return onTapCancel(); - } - return true; - } - - bool handleLongTapDown(int pointerId, TapDownInfo info) { - if (_checkPointerId(pointerId) && containsPoint(eventPosition(info))) { - return onLongTapDown(info); - } - return true; - } - - @override - @mustCallSuper - void onMount() { - super.onMount(); - assert( - (() { - final game = findGame()!; - // ignore: deprecated_member_use_from_same_package - return game is HasTappables || game is HasTappablesBridge; - })(), - 'Tappable components can only be added to a FlameGame with HasTappables ' - 'or HasTappablesBridge', - ); - } -} diff --git a/packages/flame/lib/src/components/parallax_component.dart b/packages/flame/lib/src/components/parallax_component.dart index bf0979405c6..b65e346af7e 100644 --- a/packages/flame/lib/src/components/parallax_component.dart +++ b/packages/flame/lib/src/components/parallax_component.dart @@ -55,9 +55,6 @@ extension ParallaxComponentExtension on FlameGame { /// to be static to the rest of the game. class ParallaxComponent extends PositionComponent with HasGameReference { - @override - PositionType positionType = PositionType.viewport; - bool isFullscreen = true; Parallax? _parallax; @@ -93,7 +90,7 @@ class ParallaxComponent extends PositionComponent } final newSize = parent is ReadOnlySizeProvider ? (parent! as ReadOnlySizeProvider).size - : game.camera.viewport.size; + : game.size; this.size.setFrom(newSize); parallax?.resize(newSize); } diff --git a/packages/flame/lib/src/events/flame_game_mixins/has_draggables_bridge.dart b/packages/flame/lib/src/events/flame_game_mixins/has_draggables_bridge.dart deleted file mode 100644 index 89d4733775e..00000000000 --- a/packages/flame/lib/src/events/flame_game_mixins/has_draggables_bridge.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:flame/src/events/component_mixins/drag_callbacks.dart'; - -/// Mixin that can be added to a game to indicate that is has Draggable -/// components (in addition to components with [DragCallbacks]). -/// -/// This is a temporary mixin to facilitate the transition between the old and -/// the new event system. In the future it will be deprecated. -mixin HasDraggablesBridge {} diff --git a/packages/flame/lib/src/events/flame_game_mixins/has_tappables_bridge.dart b/packages/flame/lib/src/events/flame_game_mixins/has_tappables_bridge.dart deleted file mode 100644 index cac516ed38d..00000000000 --- a/packages/flame/lib/src/events/flame_game_mixins/has_tappables_bridge.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:flame/src/events/component_mixins/tap_callbacks.dart'; - -/// Mixin that can be added to a game to indicate that is has Tappable -/// components (in addition to components with [TapCallbacks]). -/// -/// This is a temporary mixin to facilitate the transition between the old and -/// the new event system. In the future it will be deprecated. -mixin HasTappablesBridge {} diff --git a/packages/flame/lib/src/events/flame_game_mixins/multi_drag_dispatcher.dart b/packages/flame/lib/src/events/flame_game_mixins/multi_drag_dispatcher.dart index 6ac641bc46f..10a70fc053e 100644 --- a/packages/flame/lib/src/events/flame_game_mixins/multi_drag_dispatcher.dart +++ b/packages/flame/lib/src/events/flame_game_mixins/multi_drag_dispatcher.dart @@ -1,9 +1,7 @@ import 'package:flame/src/components/core/component.dart'; import 'package:flame/src/components/core/component_key.dart'; -import 'package:flame/src/components/mixins/draggable.dart'; import 'package:flame/src/events/component_mixins/drag_callbacks.dart'; import 'package:flame/src/events/flame_drag_adapter.dart'; -import 'package:flame/src/events/flame_game_mixins/has_draggables_bridge.dart'; import 'package:flame/src/events/interfaces/multi_drag_listener.dart'; import 'package:flame/src/events/messages/drag_cancel_event.dart'; import 'package:flame/src/events/messages/drag_end_event.dart'; @@ -56,15 +54,6 @@ class MultiDragDispatcher extends Component implements MultiDragListener { component.onDragStart(event); }, ); - // ignore: deprecated_member_use_from_same_package - if (game is HasDraggablesBridge) { - final info = event.asInfo(game)..handled = event.handled; - // ignore: deprecated_member_use_from_same_package - game.propagateToChildren( - (c) => c.handleDragStart(event.pointerId, info), - ); - event.handled = info.handled; - } } /// Called continuously during the drag as the user moves their finger. @@ -92,15 +81,6 @@ class MultiDragDispatcher extends Component implements MultiDragListener { record.component.onDragUpdate(event); } } - // ignore: deprecated_member_use_from_same_package - if (game is HasDraggablesBridge) { - final info = event.asInfo(game)..handled = event.handled; - // ignore: deprecated_member_use_from_same_package - game.propagateToChildren( - (c) => c.handleDragUpdated(event.pointerId, info), - ); - event.handled = info.handled; - } } /// Called when the drag gesture finishes. @@ -117,15 +97,6 @@ class MultiDragDispatcher extends Component implements MultiDragListener { } return false; }); - // ignore: deprecated_member_use_from_same_package - if (game is HasDraggablesBridge) { - final info = event.asInfo(game)..handled = event.handled; - // ignore: deprecated_member_use_from_same_package - game.propagateToChildren( - (c) => c.handleDragEnded(event.pointerId, info), - ); - event.handled = info.handled; - } } @mustCallSuper @@ -137,13 +108,6 @@ class MultiDragDispatcher extends Component implements MultiDragListener { } return false; }); - // ignore: deprecated_member_use_from_same_package - if (game is HasDraggablesBridge) { - // ignore: deprecated_member_use_from_same_package - game.propagateToChildren( - (c) => c.handleDragCanceled(event.pointerId), - ); - } } //#region MultiDragListener API diff --git a/packages/flame/lib/src/events/flame_game_mixins/multi_tap_dispatcher.dart b/packages/flame/lib/src/events/flame_game_mixins/multi_tap_dispatcher.dart index 91075c801a7..5688553b385 100644 --- a/packages/flame/lib/src/events/flame_game_mixins/multi_tap_dispatcher.dart +++ b/packages/flame/lib/src/events/flame_game_mixins/multi_tap_dispatcher.dart @@ -1,6 +1,5 @@ import 'package:flame/components.dart'; import 'package:flame/src/events/component_mixins/tap_callbacks.dart'; -import 'package:flame/src/events/flame_game_mixins/has_tappables_bridge.dart'; import 'package:flame/src/events/interfaces/multi_tap_listener.dart'; import 'package:flame/src/events/messages/tap_cancel_event.dart'; import 'package:flame/src/events/messages/tap_down_event.dart'; @@ -48,15 +47,6 @@ class MultiTapDispatcher extends Component implements MultiTapListener { component.onTapDown(event); }, ); - // ignore: deprecated_member_use_from_same_package - if (game is HasTappablesBridge) { - final info = event.asInfo(game)..handled = event.handled; - // ignore: deprecated_member_use_from_same_package - game.propagateToChildren( - (c) => c.handleTapDown(event.pointerId, info), - ); - event.handled = info.handled; - } } /// Called after the user has been touching the screen for [longTapDelay] @@ -77,15 +67,6 @@ class MultiTapDispatcher extends Component implements MultiTapListener { }, deliverToAll: true, ); - // ignore: deprecated_member_use_from_same_package - if (game is HasTappablesBridge) { - final info = event.asInfo(game)..handled = event.handled; - // ignore: deprecated_member_use_from_same_package - game.propagateToChildren( - (c) => c.handleLongTapDown(event.pointerId, info), - ); - event.handled = info.handled; - } } /// Called when the user stops touching the device screen within the game @@ -110,16 +91,6 @@ class MultiTapDispatcher extends Component implements MultiTapListener { }, deliverToAll: true, ); - _tapCancelImpl(TapCancelEvent(event.pointerId)); - // ignore: deprecated_member_use_from_same_package - if (game is HasTappablesBridge) { - final info = event.asInfo(game)..handled = event.handled; - // ignore: deprecated_member_use_from_same_package - game.propagateToChildren( - (c) => c.handleTapUp(event.pointerId, info), - ); - event.handled = info.handled; - } } /// Called when there was an [onTapDown] event previously, but the [onTapUp] @@ -133,13 +104,6 @@ class MultiTapDispatcher extends Component implements MultiTapListener { @mustCallSuper void onTapCancel(TapCancelEvent event) { _tapCancelImpl(event); - // ignore: deprecated_member_use_from_same_package - if (game is HasTappablesBridge) { - // ignore: deprecated_member_use_from_same_package - game.propagateToChildren( - (c) => c.handleTapCancel(event.pointerId), - ); - } } void _tapCancelImpl(TapCancelEvent event) { diff --git a/packages/flame/lib/src/events/game_mixins/multi_touch_drag_detector.dart b/packages/flame/lib/src/events/game_mixins/multi_touch_drag_detector.dart index a398756b3b6..c3d37835a8f 100644 --- a/packages/flame/lib/src/events/game_mixins/multi_touch_drag_detector.dart +++ b/packages/flame/lib/src/events/game_mixins/multi_touch_drag_detector.dart @@ -1,7 +1,6 @@ +import 'package:flame/events.dart'; import 'package:flame/src/events/flame_drag_adapter.dart'; -import 'package:flame/src/events/interfaces/multi_drag_listener.dart'; import 'package:flame/src/game/game.dart'; -import 'package:flame/src/gestures/events.dart'; import 'package:flutter/gestures.dart'; /// Mixin that can be added to a [Game] allowing it to receive drag events. @@ -33,7 +32,7 @@ mixin MultiTouchDragDetector on Game implements MultiDragListener { @override void handleDragEnd(int pointerId, DragEndDetails details) { - onDragEnd(pointerId, DragEndInfo.fromDetails(this, details)); + onDragEnd(pointerId, DragEndInfo.fromDetails(details)); } @override diff --git a/packages/flame/lib/src/events/game_mixins/multi_touch_tap_detector.dart b/packages/flame/lib/src/events/game_mixins/multi_touch_tap_detector.dart index cc23730cdf6..ca510d81629 100644 --- a/packages/flame/lib/src/events/game_mixins/multi_touch_tap_detector.dart +++ b/packages/flame/lib/src/events/game_mixins/multi_touch_tap_detector.dart @@ -1,6 +1,5 @@ -import 'package:flame/src/events/interfaces/multi_tap_listener.dart'; +import 'package:flame/events.dart'; import 'package:flame/src/game/game.dart'; -import 'package:flame/src/gestures/events.dart'; import 'package:flutter/gestures.dart'; /// Mixin that can be added to a [Game] allowing it to receive tap events. diff --git a/packages/flame/lib/src/events/messages/drag_end_event.dart b/packages/flame/lib/src/events/messages/drag_end_event.dart index eea602dd894..1f114dcd3cc 100644 --- a/packages/flame/lib/src/events/messages/drag_end_event.dart +++ b/packages/flame/lib/src/events/messages/drag_end_event.dart @@ -1,7 +1,5 @@ -import 'package:flame/events.dart'; import 'package:flame/extensions.dart'; import 'package:flame/src/events/messages/event.dart'; -import 'package:flame/src/game/flame_game.dart'; import 'package:flutter/gestures.dart'; class DragEndEvent extends Event { @@ -12,15 +10,6 @@ class DragEndEvent extends Event { final Vector2 velocity; - DragEndInfo asInfo(FlameGame game) { - return DragEndInfo.fromDetails( - game, - DragEndDetails( - velocity: Velocity(pixelsPerSecond: velocity.toOffset()), - ), - ); - } - @override String toString() => 'DragEndEvent(pointerId: $pointerId, velocity: $velocity)'; diff --git a/packages/flame/lib/src/events/messages/drag_start_event.dart b/packages/flame/lib/src/events/messages/drag_start_event.dart index 3b2bd59840f..b6998adc31d 100644 --- a/packages/flame/lib/src/events/messages/drag_start_event.dart +++ b/packages/flame/lib/src/events/messages/drag_start_event.dart @@ -2,8 +2,6 @@ import 'package:flame/extensions.dart'; import 'package:flame/src/events/messages/drag_end_event.dart'; import 'package:flame/src/events/messages/drag_update_event.dart'; import 'package:flame/src/events/messages/position_event.dart'; -import 'package:flame/src/game/flame_game.dart'; -import 'package:flame/src/gestures/events.dart'; import 'package:flutter/gestures.dart'; /// The event propagated through the Flame engine when the user starts a drag @@ -26,18 +24,6 @@ class DragStartEvent extends PositionEvent { final PointerDeviceKind deviceKind; - /// Converts this event into the legacy [DragStartInfo] representation. - DragStartInfo asInfo(FlameGame game) { - return DragStartInfo.fromDetails( - game, - DragStartDetails( - globalPosition: devicePosition.toOffset(), - localPosition: canvasPosition.toOffset(), - kind: deviceKind, - ), - ); - } - @override String toString() => 'DragStartEvent(canvasPosition: $canvasPosition, ' 'devicePosition: $devicePosition, ' diff --git a/packages/flame/lib/src/events/messages/drag_update_event.dart b/packages/flame/lib/src/events/messages/drag_update_event.dart index 68ffff8ac46..a8dcae3dd3a 100644 --- a/packages/flame/lib/src/events/messages/drag_update_event.dart +++ b/packages/flame/lib/src/events/messages/drag_update_event.dart @@ -1,7 +1,5 @@ -import 'package:flame/events.dart'; import 'package:flame/extensions.dart'; import 'package:flame/src/events/messages/position_event.dart'; -import 'package:flame/src/game/flame_game.dart'; import 'package:flutter/gestures.dart'; class DragUpdateEvent extends PositionEvent { @@ -23,19 +21,6 @@ class DragUpdateEvent extends PositionEvent { return renderingTrace.isEmpty ? _nanPoint : renderingTrace.last; } - /// Converts this event into the legacy [DragStartInfo] representation. - DragUpdateInfo asInfo(FlameGame game) { - return DragUpdateInfo.fromDetails( - game, - DragUpdateDetails( - sourceTimeStamp: timestamp, - globalPosition: devicePosition.toOffset(), - localPosition: canvasPosition.toOffset(), - delta: delta.toOffset(), - ), - ); - } - @override String toString() => 'DragUpdateEvent(devicePosition: $devicePosition, ' 'canvasPosition: $canvasPosition, ' diff --git a/packages/flame/lib/src/events/messages/tap_down_event.dart b/packages/flame/lib/src/events/messages/tap_down_event.dart index acc3a2ae224..6cad4a3c43e 100644 --- a/packages/flame/lib/src/events/messages/tap_down_event.dart +++ b/packages/flame/lib/src/events/messages/tap_down_event.dart @@ -3,8 +3,6 @@ import 'package:flame/src/events/component_mixins/tap_callbacks.dart'; import 'package:flame/src/events/messages/position_event.dart'; import 'package:flame/src/events/messages/tap_cancel_event.dart'; import 'package:flame/src/events/messages/tap_up_event.dart'; -import 'package:flame/src/game/game.dart'; -import 'package:flame/src/gestures/events.dart'; import 'package:flutter/gestures.dart'; /// The event propagated through the Flame engine when the user starts a touch @@ -30,18 +28,6 @@ class TapDownEvent extends PositionEvent { final PointerDeviceKind deviceKind; - /// Converts this event into the legacy [TapDownInfo] representation. - TapDownInfo asInfo(Game game) { - return TapDownInfo.fromDetails( - game, - TapDownDetails( - globalPosition: devicePosition.toOffset(), - localPosition: canvasPosition.toOffset(), - kind: deviceKind, - ), - ); - } - @override String toString() => 'TapDownEvent(canvasPosition: $canvasPosition, ' 'devicePosition: $devicePosition, ' diff --git a/packages/flame/lib/src/events/messages/tap_up_event.dart b/packages/flame/lib/src/events/messages/tap_up_event.dart index 5391ae952b5..93ed748285f 100644 --- a/packages/flame/lib/src/events/messages/tap_up_event.dart +++ b/packages/flame/lib/src/events/messages/tap_up_event.dart @@ -1,8 +1,6 @@ import 'package:flame/extensions.dart'; import 'package:flame/src/events/messages/position_event.dart'; import 'package:flame/src/events/messages/tap_down_event.dart'; -import 'package:flame/src/game/game.dart'; -import 'package:flame/src/gestures/events.dart'; import 'package:flutter/gestures.dart'; /// The event propagated through the Flame engine when the user stops touching @@ -24,18 +22,6 @@ class TapUpEvent extends PositionEvent { final PointerDeviceKind deviceKind; - /// Converts this event into the legacy [TapUpInfo] representation. - TapUpInfo asInfo(Game game) { - return TapUpInfo.fromDetails( - game, - TapUpDetails( - globalPosition: devicePosition.toOffset(), - localPosition: canvasPosition.toOffset(), - kind: deviceKind, - ), - ); - } - @override String toString() => 'TapUpEvent(canvasPosition: $canvasPosition, ' 'devicePosition: $devicePosition, ' diff --git a/packages/flame/lib/src/experimental/fixed_integer_resolution_viewport.dart b/packages/flame/lib/src/experimental/fixed_integer_resolution_viewport.dart deleted file mode 100644 index 8079b23b176..00000000000 --- a/packages/flame/lib/src/experimental/fixed_integer_resolution_viewport.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'dart:math' as math; - -import 'package:flame/extensions.dart'; -import 'package:flame/game.dart'; - -/// This viewport is a very similar to [FixedResolutionViewport], but allows -/// for better handling of viewing pixel art. The main point is it ensures -/// sprites align to the physical pixel grid and only scales at integer -/// intervals. This prevents artifacts or distortion happening to scaled up -/// pixel art as discussed in https://github.com/flame-engine/flame/issues/1152. -/// Remember to set [devicePixelRatio] before using the viewport; a good place -/// to update it is in handleResize of your game, directly from -/// WidgetBindings.instance.window.devicePixelRatio. -class FixedIntegerResolutionViewport extends Viewport { - /// By default, this viewport will clip anything rendered outside. - /// Use this variable to control that behavior. - bool clip; - double devicePixelRatio = 1.0; - - @override - late Vector2 effectiveSize; - - final Vector2 _scaledSize = Vector2.zero(); - Vector2 get scaledSize => _scaledSize.clone(); - - final Vector2 _resizeOffset = Vector2.zero(); - Vector2 get resizeOffset => - _resizeOffset.clone()..scale(1 / devicePixelRatio); - - late double _scale; - double get scale => _scale / devicePixelRatio; - - /// The matrix used for scaling and translating the canvas - final Matrix4 _transform = Matrix4.identity(); - - /// The Rect that is used to clip the canvas - late Rect _clipRect; - - FixedIntegerResolutionViewport(this.effectiveSize, {this.clip = true}); - - @override - void resize(Vector2 newCanvasSize) { - canvasSize = newCanvasSize.clone(); - final devicePixels = canvasSize!..scale(devicePixelRatio); - - _scale = math - .min( - devicePixels.x / effectiveSize.x, - devicePixels.y / effectiveSize.y, - ) - .ceil() - .toDouble(); - - _scaledSize - ..setFrom(effectiveSize) - ..scale(_scale); - _resizeOffset - ..setFrom(devicePixels) - ..sub(_scaledSize) - ..scale(0.5) - ..round(); - - _clipRect = _resizeOffset & _scaledSize; - - _transform.setIdentity(); - _transform.translate(resizeOffset.x, resizeOffset.y); - _transform.scale(scale, scale, 1); - } - - @override - void apply(Canvas c) { - if (clip) { - c.clipRect(_clipRect); - } - c.transform(_transform.storage); - } - - @override - Vector2 projectVector(Vector2 worldCoordinates) { - return (worldCoordinates * scale)..add(resizeOffset); - } - - @override - Vector2 unprojectVector(Vector2 screenCoordinates) { - return (screenCoordinates - resizeOffset)..scale(1 / scale); - } - - @override - Vector2 scaleVector(Vector2 worldCoordinates) { - return worldCoordinates * scale; - } - - @override - Vector2 unscaleVector(Vector2 screenCoordinates) { - return screenCoordinates / scale; - } -} diff --git a/packages/flame/lib/src/game/camera/camera.dart b/packages/flame/lib/src/game/camera/camera.dart deleted file mode 100644 index 29fd7a6aae6..00000000000 --- a/packages/flame/lib/src/game/camera/camera.dart +++ /dev/null @@ -1,437 +0,0 @@ -import 'dart:math' as math; - -import 'package:flame/components.dart'; -import 'package:flame/extensions.dart'; -import 'package:flame/game.dart'; - -/// A camera translates your game coordinate system; this is useful when your -/// world is not 1:1 with your screen size. -/// -/// A camera always has a current [position], however you cannot set it -/// directly. You must use some methods to ensure that the camera moves smoothly -/// as the game runs. Smoothly here means that sudden snaps should be avoided, -/// as they feel jarring to the player. -/// -/// There are three major factors that determine the camera position: -/// -/// * Follow -/// If you want, you can call [followComponent] at the beginning of your -/// stage/world/level, and provided a [PositionComponent]. -/// The camera will follow this component making sure its position is fixed -/// on the screen. -/// You can set the relative position of the screen you want the follow -/// object to stay in (normally the center), and you can even change that -/// and get a smooth transition. -/// -/// * Move -/// You can alternatively move the camera to a specific world coordinate. -/// This will set the top left of the camera and will ignore any existing follow -/// rules and move the camera smoothly until it reaches the desired destination. -/// -/// * Shake -/// Regardless of the the previous rules, you can additionally add a shake -/// effect for a brief period of time on top of the current coordinate. -/// The shake adds a random immediate delta to each tick to simulate the shake -/// effect. -/// -/// Note: in the context of the FlameGame, the camera effectively translates -/// the position where components are rendered with relation to the Viewport. -/// Components marked as `positionType = PositionType.viewport;` are -/// always rendered in screen coordinates, bypassing the camera altogether. -/// -/// Note: beware of using very large numbers with the camera (like coordinates -/// spanning the dozens of millions). Due to the required matrix operations -/// performed by the Camera, using such large numbers can cause performance -/// issues. Consider breaking down huge maps into manageable chunks. -class Camera extends Projector { - Camera() : _viewport = DefaultViewport() { - _combinedProjector = Projector.compose([this, _viewport]); - } - - Viewport get viewport => _viewport; - Viewport _viewport; - set viewport(Viewport value) { - _viewport = value; - if (_canvasSize != null) { - _viewport.resize(canvasSize); - } - _combinedProjector = Projector.compose([this, _viewport]); - } - - // camera movement speed, in pixels/s - static const defaultSpeed = 50.0; - - /// If set, this bypasses follow and moves the camera to a specific point - /// in the world. - /// - /// You can use this if you are not using follow but have a few different - /// camera positions or if you are using follow but you want to highlight a - /// spot in the world during an animation. - Vector2? _currentCameraDelta; - Vector2? _targetCameraDelta; - - /// Remaining time in seconds for the camera shake. - double _shakeTimer = 0.0; - - /// The intensity of the current shake action. - double _shakeIntensity = 0.0; - - /// The matrix used for scaling and translating the canvas - final Matrix4 _transform = Matrix4.identity(); - - // Configurable parameters - - double speed = defaultSpeed; - double defaultShakeIntensity = 75.0; // in pixels - double defaultShakeDuration = 0.3; // in seconds - - /// This is the current position of the camera, ie the world coordinate that - /// is rendered on the top left of the screen (origin of the screen space). - /// - /// Zero means no translation is applied. - /// You can't change this directly; the camera will handle all ongoing - /// movements so they smoothly transition. - /// If you want to immediately snap the camera to a new place, you can do: - /// ``` - /// camera.snapTo(newPosition); - /// ``` - Vector2 get position => _internalPosition.clone(); - - /// Do not change this directly since it bypasses [onPositionUpdate] - final Vector2 _internalPosition = Vector2.zero(); - - Vector2 get _position => _internalPosition; - set _position(Vector2 position) { - _internalPosition.setFrom(position); - onPositionUpdate(_internalPosition); - } - - /// If set, the camera will "follow" this vector, making sure that this - /// vector is always rendered in a fixed position in the screen, by - /// immediately moving the camera to "focus" on the where the vector is. - /// - /// You might want to set it to the player component by using the - /// [followComponent] method. - /// Note that this is not smooth because the movement of the followed vector - /// is assumed to be smooth. - Vector2? follow; - - /// Where in the screen the follow object should be. - /// - /// This is a fractional value relating to the screen size. - /// Changing this will smoothly move the camera to the new position - /// (unless you use the followObject method that immediately sets - /// up the camera for the new parameters). - Vector2 get relativeOffset => _currentRelativeOffset; - - final Vector2 _currentRelativeOffset = Vector2.zero(); - final Vector2 _targetRelativeOffset = Vector2.zero(); - - /// If set, this determines boundaries for the camera movement. - /// - /// The camera will never move such that a region outside the world boundaries - /// is shown, meaning it will stop following when the object gets close to the - /// edges. - /// - /// Changing this value can immediately snap the camera if it is a wrong - /// position, but other than that it's just prevent movement so should not - /// add any non-smooth movement. - Rect? worldBounds; - - /// If set, the camera will zoom by this ratio. This can be greater than 1 - /// (zoom in) or smaller (zoom out), but should always be greater than zero. - /// - /// Note: do not confuse this with the zoom applied by the viewport. The - /// viewport applies a (normally) fixed zoom to adapt multiple screens into - /// one aspect ratio. The zoom might be different per dimension depending - /// on the Viewport implementation. Also, if used with the default - /// FlameGame implementation, it will apply to all components. - /// The zoom from the camera is only for components that respect camera, - /// and is applied after the viewport is set. It exists to be used if there - /// is any kind of user configurable camera on your game. - double zoom = 1.0; - - Vector2 get gameSize => _viewport.effectiveSize / zoom; - - /// Use this method to transform the canvas using the current rules provided - /// by this camera object. - /// - /// If you are using FlameGame, this will be done for you for all non-HUD - /// components. - /// When using this method you are responsible for saving/restoring canvas - /// state to avoid leakage. - void apply(Canvas canvas) { - canvas.transform(_transformMatrix().storage); - } - - Vector2? _canvasSize; - Vector2 get canvasSize { - assert( - _canvasSize != null, - 'Property `canvasSize` cannot be accessed before the layout stage', - ); - return _canvasSize!; - } - - void handleResize(Vector2 canvasSize) { - _canvasSize = canvasSize.clone(); - _viewport.resize(canvasSize); - } - - Matrix4 _transformMatrix() { - final translateX = -_position.x * zoom; - final translateY = -_position.y * zoom; - if (_transform.m11 == zoom && - _transform.m22 == zoom && - _transform.m33 == zoom && - _transform.m41 == translateX && - _transform.m42 == translateY) { - return _transform; - } - _transform.setIdentity(); - _transform.translate(translateX, translateY); - _transform.scale(zoom, zoom, 1); - return _transform; - } - - // TODO(st-pasha): replace with the transform matrix - late Projector _combinedProjector; - Projector get combinedProjector => _combinedProjector; - - /// This smoothly updates the camera for an amount of time [dt]. - /// - /// This should be called by the Game class during the update cycle. - void update(double dt) { - final ds = speed * dt; - final shake = _shakeDelta(); - - _currentRelativeOffset.moveToTarget(_targetRelativeOffset, ds); - if (_targetCameraDelta != null && _currentCameraDelta != null) { - _currentCameraDelta?.moveToTarget(_targetCameraDelta!, ds); - } - _position = _target()..add(shake); - - if (shaking) { - _shakeTimer -= dt; - if (_shakeTimer < 0.0) { - _shakeTimer = 0.0; - } - } - } - - /// Use this to immediately "snap" the camera to where it should be right - /// now. This bypasses any currently smooth transitions and might be janky, - /// but can be used to setup after a new world transition for example. - void snap() { - if (_targetCameraDelta != null && _currentCameraDelta != null) { - _currentCameraDelta!.setFrom(_targetCameraDelta!); - } - _currentRelativeOffset.setFrom(_targetRelativeOffset); - update(0); - } - - // Coordinates - - @override - Vector2 unprojectVector(Vector2 screenCoordinates) { - return _position + (screenCoordinates / zoom); - } - - @override - Vector2 projectVector(Vector2 worldCoordinates) { - return (worldCoordinates - _position) * zoom; - } - - @override - Vector2 unscaleVector(Vector2 screenCoordinates) { - return screenCoordinates / zoom; - } - - @override - Vector2 scaleVector(Vector2 worldCoordinates) { - return worldCoordinates * zoom; - } - - /// Takes coordinates in the screen space and returns their counter-part in - /// the world space. - Vector2 screenToWorld(Vector2 screenCoordinates) { - return unprojectVector(screenCoordinates); - } - - /// Takes coordinates in the world space and returns their counter-part in - /// the screen space. - Vector2 worldToScreen(Vector2 worldCoordinates) { - return projectVector(worldCoordinates); - } - - /// This is the (current) absolute target of the camera, i.e., the - /// coordinate that should with `relativeOffset` taken into consideration but - /// regardless of world boundaries or shake. - Vector2 absoluteTarget() { - return _currentCameraDelta ?? follow ?? Vector2.zero(); - } - - // Follow - - /// Immediately snaps the camera to start following the [component]. - /// - /// This means that the camera will move so that the position vector of the - /// component is in a fixed position on the screen. - /// That position is determined by a fraction of screen size defined by - /// [relativeOffset] (default to the center). - /// [worldBounds] can be optionally set to add boundaries to how far the - /// camera is allowed to move. - /// The component is "grabbed" by its anchor (default top left). - /// So for example if you want the center of the object to be at the fixed - /// position, set the components anchor to center. - void followComponent( - PositionComponent component, { - Anchor relativeOffset = Anchor.center, - Rect? worldBounds, - }) { - followVector2( - component.position, - relativeOffset: relativeOffset, - worldBounds: worldBounds, - ); - } - - /// Immediately snaps the camera to start following [vector2]. - /// - /// This means that the camera will move so that the position vector is in a - /// fixed position on the screen. - /// That position is determined by a fraction of screen size defined by - /// [relativeOffset] (default to the center). - /// [worldBounds] can be optionally set to add boundaries to how far the - /// camera is allowed to move. - void followVector2( - Vector2 vector2, { - Anchor relativeOffset = Anchor.center, - Rect? worldBounds, - }) { - follow = vector2; - if (worldBounds != null) { - this.worldBounds = worldBounds; - } - _targetRelativeOffset.setFrom(relativeOffset.toVector2()); - _currentRelativeOffset.setFrom(_targetRelativeOffset); - } - - /// This will trigger a smooth transition to a new relative offset. - /// - /// You can use this for example to change camera modes in your game, maybe - /// you have two different options for the player to choose or your have a - /// "dialog" camera that puts the player in a better place to show the - /// dialog UI. - void setRelativeOffset(Anchor newRelativeOffset) { - _targetRelativeOffset.setFrom(newRelativeOffset.toVector2()); - } - - Vector2 _screenDelta() { - return gameSize.clone()..multiply(_currentRelativeOffset); - } - - Vector2 _target() { - final target = absoluteTarget(); - final attemptedTarget = target - _screenDelta(); - - final bounds = worldBounds; - if (bounds != null) { - if (bounds.width > gameSize.x) { - final cameraLeftEdge = attemptedTarget.x; - final cameraRightEdge = attemptedTarget.x + gameSize.x; - if (cameraLeftEdge < bounds.left) { - attemptedTarget.x = bounds.left; - } else if (cameraRightEdge > bounds.right) { - attemptedTarget.x = bounds.right - gameSize.x; - } - } else { - attemptedTarget.x = (gameSize.x - bounds.width) / 2; - } - - if (bounds.height > gameSize.y) { - final cameraTopEdge = attemptedTarget.y; - final cameraBottomEdge = attemptedTarget.y + gameSize.y; - if (cameraTopEdge < bounds.top) { - attemptedTarget.y = bounds.top; - } else if (cameraBottomEdge > bounds.bottom) { - attemptedTarget.y = bounds.bottom - gameSize.y; - } - } else { - attemptedTarget.y = (gameSize.y - bounds.height) / 2; - } - } - - return attemptedTarget; - } - - // Movement - - /// Moves the camera by a given [displacement] (delta). This is the same as - /// [moveTo] but instead of providing an absolute end position, you can - /// provide a desired translation vector. - void translateBy(Vector2 displacement) { - moveTo(absoluteTarget() + displacement); - } - - /// Applies an ad-hoc movement to the camera towards the target, bypassing - /// follow. Once it arrives the camera will not move until [resetMovement] - /// is called. - /// - /// The camera will be smoothly transitioned to this position. - /// This will replace any previous targets. - void moveTo(Vector2 position) { - _currentCameraDelta = _position + _screenDelta(); - _targetCameraDelta = position.clone(); - } - - /// Instantly moves the camera to the target, bypassing follow. - /// This will replace any previous targets. - void snapTo(Vector2 position) { - moveTo(position); - snap(); - } - - /// Smoothly resets any moveTo targets. - void resetMovement() { - _currentCameraDelta = null; - _targetCameraDelta = null; - } - - // Shake - - /// Applies a shaking effect to the camera for [duration] seconds and with - /// [intensity] expressed in pixels. - void shake({double? duration, double? intensity}) { - _shakeTimer += duration ?? defaultShakeDuration; - _shakeIntensity = intensity ?? defaultShakeIntensity; - } - - /// Whether the camera is currently shaking or not. - bool get shaking => _shakeTimer > 0.0; - - /// Buffer to re-use for the shake delta. - final _shakeBuffer = Vector2.zero(); - - /// The random number generator to use for shaking - final _shakeRng = math.Random(); - - /// Generates one value between [-1, 1] * [_shakeIntensity] used once for each - /// of the axis in the shake delta. - double _shakeValue() => (_shakeRng.nextDouble() - 0.5) * 2 * _shakeIntensity; - - /// Generates a random [Vector2] of displacement applied to the camera. - /// This will be a random [Vector2] every tick causing a shakiness effect. - Vector2 _shakeDelta() { - if (shaking) { - _shakeBuffer.setValues(_shakeValue(), _shakeValue()); - } else if (!_shakeBuffer.isZero()) { - _shakeBuffer.setZero(); - } - return _shakeBuffer; - } - - /// If you need updated on when the position of the camera is updated you - /// can override this. - void onPositionUpdate(Vector2 position) {} -} diff --git a/packages/flame/lib/src/game/camera/camera_wrapper.dart b/packages/flame/lib/src/game/camera/camera_wrapper.dart deleted file mode 100644 index 339fc933d80..00000000000 --- a/packages/flame/lib/src/game/camera/camera_wrapper.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'dart:ui'; - -import 'package:flame/components.dart'; -import 'package:flame/src/game/camera/camera.dart'; -import 'package:meta/meta.dart'; - -/// This class encapsulates FlameGame's rendering functionality. It will be -/// converted into a proper Component in a future release, but until then -/// using it in any code other than the FlameGame class is unsafe and -/// not recommended. -@internal -@Deprecated('Will be removed in Flame v1.10') -class CameraWrapper { - @Deprecated('Will be removed in Flame v1.10') - CameraWrapper(this.camera, this.world); - - final Camera camera; - final ComponentSet world; - - void update(double dt) { - camera.update(dt); - } - - void render(Canvas canvas) { - PositionType? previousType; - canvas.save(); - world.forEach((component) { - final sameType = component.positionType == previousType; - if (!sameType) { - if (previousType != null && previousType != PositionType.widget) { - canvas.restore(); - canvas.save(); - } - switch (component.positionType) { - case PositionType.game: - camera.viewport.apply(canvas); - camera.apply(canvas); - break; - case PositionType.viewport: - camera.viewport.apply(canvas); - break; - case PositionType.widget: - } - } - component.renderTree(canvas); - previousType = component.positionType; - }); - - if (previousType != PositionType.widget) { - canvas.restore(); - } - } -} diff --git a/packages/flame/lib/src/game/camera/viewport.dart b/packages/flame/lib/src/game/camera/viewport.dart deleted file mode 100644 index c4612695ec5..00000000000 --- a/packages/flame/lib/src/game/camera/viewport.dart +++ /dev/null @@ -1,206 +0,0 @@ -import 'dart:math' as math; - -import 'package:flame/extensions.dart'; -import 'package:flame/game.dart'; - -/// A viewport is a class that potentially translates and resizes the screen. -/// The reason you might want to have a viewport is to make sure you handle any -/// screen size and resolution correctly depending on your needs. -/// -/// Not only screens can have endless configurations of width and height with -/// different ratios, you can also embed games as widgets within a Flutter app. -/// In fact, the size of the game can even change dynamically (if the layout -/// changes or in desktop, for example). -/// -/// For some simple games, that is not an issue. The game will just adapt -/// to fit the screen, so if the game world is 1:1 with screen it will just -/// be bigger or smaller. But if you want a consistent experience across -/// platforms and players, you should use a viewport. -/// -/// When using a viewport, [resize] should be called by the engine with -/// the raw canvas size (on startup and subsequent resizes) and that will -/// configure [effectiveSize] and [canvasSize]. -/// -/// The Viewport can also apply an offset to render and clip the canvas adding -/// borders (clipping) when necessary. -/// When rendering, call [render] and put all your rendering inside the lambda -/// so that the correct transformations are applied. -/// -/// You can think of a Viewport as mechanism to watch a wide-screen movie on a -/// square monitor. You can stretch the movie to fill the square, but the width -/// and height will be stretched by different amounts, causing distortion. You -/// can fill in the smallest dimension and crop the biggest (that causes -/// cropping). Or you can fill in the biggest and add black bars to cover the -/// unused space on the smallest (this is the [FixedResolutionViewport]). -/// -/// The other option is to not use a viewport ([DefaultViewport]) and have -/// your game dynamically render itself to fill in the existing space (basically -/// this means generating either a wide-screen or a square movie on the fly). -/// The disadvantage is that different players on different devices will see -/// different games. For example a hidden door because it's too far away to -/// render in Screen 1 might be visible on Screen 2. Specially if it's an -/// online/competitive game, it can give unfair advantages to users with certain -/// screen resolutions. If you want to "play director" and know exactly what -/// every player is seeing at every time, you should use a Viewport. -abstract class Viewport extends Projector { - /// This configures the viewport with a new raw canvas size. - /// It should immediately affect [effectiveSize] and [canvasSize]. - /// This must be called by the engine at startup and also whenever the - /// size changes. - void resize(Vector2 newCanvasSize); - - /// Applies to the Canvas all necessary transformations to apply this - /// viewport. - void apply(Canvas c); - - /// This transforms the canvas so that the coordinate system is viewport- - /// -aware. All your rendering logic should be put inside the lambda. - void render(Canvas c, void Function(Canvas) renderGame) { - c.save(); - apply(c); - renderGame(c); - c.restore(); - } - - /// This returns the effective size, after viewport transformation. - /// This is not the game widget size but for all intents and purposes, - /// inside your game, this size should be used as the real one. - Vector2 get effectiveSize; - - /// This returns the real widget size (well actually the logical Flutter - /// size of your widget). This is the raw canvas size as it would be without - /// any viewport. - /// - /// You probably don't need to care about this if you are using a viewport. - Vector2? canvasSize; -} - -/// This is the default viewport if you want no transformation. -/// The raw canvasSize is just propagated to the effective size and no -/// translation is applied. -/// This basically no-ops the viewport. -class DefaultViewport extends Viewport { - @override - void apply(Canvas c) {} - - @override - void resize(Vector2 newCanvasSize) { - canvasSize = newCanvasSize.clone(); - } - - @override - Vector2 get effectiveSize => canvasSize!; - - @override - Vector2 projectVector(Vector2 worldCoordinates) => worldCoordinates; - - @override - Vector2 unprojectVector(Vector2 screenCoordinates) => screenCoordinates; - - @override - Vector2 scaleVector(Vector2 worldCoordinates) => worldCoordinates; - - @override - Vector2 unscaleVector(Vector2 screenCoordinates) => screenCoordinates; -} - -/// This is the most common viewport if you want to have full control of what -/// the game looks like. Basically this viewport makes sure the ratio between -/// width and height is *always* the same in your game, no matter the platform. -/// -/// To accomplish this you choose a virtual size that will always match the -/// effective size. -/// -/// Under the hood, the Viewport will try to expand (or contract) the virtual -/// size so that it fits the most of the screen as it can. So for example, -/// if the viewport happens to be the same ratio of the screen, it will resize -/// to fit 100%. But if they are different ratios, it will resize the most it -/// can and then will add black (color is configurable) borders. -/// -/// Then, inside your game, you can always assume the game size is the fixed -/// dimension that you provided. -/// -/// Normally you can pick a virtual size that has the same ratio as the most -/// used device for your game (like a pretty standard mobile ratio if you -/// are doing a mobile game) and then in most cases this will apply no -/// transformation whatsoever, and if the a device with a different ratio is -/// used it will try to adapt the best as possible. -class FixedResolutionViewport extends Viewport { - /// By default, this viewport will clip anything rendered outside. - /// Use this variable to control that behavior. - bool clip; - - @override - late Vector2 effectiveSize; - - final Vector2 _scaledSize = Vector2.zero(); - Vector2 get scaledSize => _scaledSize.clone(); - - final Vector2 _resizeOffset = Vector2.zero(); - Vector2 get resizeOffset => _resizeOffset.clone(); - - late double _scale; - double get scale => _scale; - - /// The matrix used for scaling and translating the canvas - final Matrix4 _transform = Matrix4.identity(); - - /// The Rect that is used to clip the canvas - late Rect _clipRect; - - FixedResolutionViewport(this.effectiveSize, {this.clip = true}); - - @override - void resize(Vector2 newCanvasSize) { - canvasSize = newCanvasSize.clone(); - - _scale = math.min( - canvasSize!.x / effectiveSize.x, - canvasSize!.y / effectiveSize.y, - ); - - _scaledSize - ..setFrom(effectiveSize) - ..scale(_scale); - _resizeOffset - ..setFrom(canvasSize!) - ..sub(_scaledSize) - ..scale(0.5); - - _clipRect = _resizeOffset & _scaledSize; - - _transform.setIdentity(); - _transform.translate(_resizeOffset.x, _resizeOffset.y); - _transform.scale(_scale, _scale, 1); - } - - @override - void apply(Canvas c) { - if (clip) { - c.clipRect(_clipRect); - } - c.transform(_transform.storage); - } - - @override - // ignore: avoid_renaming_method_parameters - Vector2 projectVector(Vector2 viewportCoordinates) { - return (viewportCoordinates * _scale)..add(_resizeOffset); - } - - @override - Vector2 unprojectVector(Vector2 screenCoordinates) { - return (screenCoordinates - _resizeOffset)..scale(1 / _scale); - } - - @override - // ignore: avoid_renaming_method_parameters - Vector2 scaleVector(Vector2 viewportCoordinates) { - return viewportCoordinates * scale; - } - - @override - Vector2 unscaleVector(Vector2 screenCoordinates) { - return screenCoordinates / scale; - } -} diff --git a/packages/flame/lib/src/game/flame_game.dart b/packages/flame/lib/src/game/flame_game.dart index 59ca8bf5cdc..b98d4e97f09 100644 --- a/packages/flame/lib/src/game/flame_game.dart +++ b/packages/flame/lib/src/game/flame_game.dart @@ -1,15 +1,10 @@ -// ignore_for_file: deprecated_member_use_from_same_package - import 'dart:async'; import 'dart:ui'; import 'package:flame/components.dart'; import 'package:flame/src/components/core/component_tree_root.dart'; import 'package:flame/src/effects/provider_interfaces.dart'; -import 'package:flame/src/game/camera/camera.dart'; -import 'package:flame/src/game/camera/camera_wrapper.dart'; import 'package:flame/src/game/game.dart'; -import 'package:flame/src/game/projector.dart'; import 'package:meta/meta.dart'; /// This is a more complete and opinionated implementation of [Game]. @@ -26,7 +21,6 @@ class FlameGame extends ComponentTreeRoot super.children, W? world, CameraComponent? camera, - Camera? oldCamera, }) : assert( world != null || W == World, 'The generics type $W does not conform to the type of ' @@ -39,7 +33,6 @@ class FlameGame extends ComponentTreeRoot '$this instantiated, while another game ${Component.staticGameInstance} ' 'declares itself to be a singleton', ); - _cameraWrapper = CameraWrapper(oldCamera ?? Camera(), children); _camera.world = _world; add(_camera); add(_world); @@ -83,38 +76,17 @@ class FlameGame extends ComponentTreeRoot CameraComponent _camera; - late final CameraWrapper _cameraWrapper; - @internal late final List notifiers = []; - /// The camera translates the coordinate space after the viewport is applied. - @Deprecated(''' - In the future (maybe as early as v1.10.0) this camera will be removed, - please use the CameraComponent instead, which has a default camera at - `FlameGame.camera`. - - This is the simplest way of using the CameraComponent: - 1. Instead of adding the root components directly to your game with `add`, - add them to the world. - - world.add(yourComponent); - - 2. (Optional) If you want to add a HUD component, instead of using - PositionType, add the component as a child of the viewport. - - camera.viewport.add(yourHudComponent); - ''') - Camera get oldCamera => _cameraWrapper.camera; - /// This is overwritten to consider the viewport transformation. /// /// Which means that this is the logical size of the game screen area as - /// exposed to the canvas after viewport transformations and camera zooming. + /// exposed to the canvas after viewport transformations. /// /// This does not match the Flutter widget size; for that see [canvasSize]. @override - Vector2 get size => oldCamera.gameSize; + Vector2 get size => camera.viewport.virtualSize; @override @internal @@ -140,8 +112,12 @@ class FlameGame extends ComponentTreeRoot @override void renderTree(Canvas canvas) { - // Don't call super.renderTree, since the tree is rendered by the camera - _cameraWrapper.render(canvas); + if (parent != null) { + renderTree(canvas); + } + for (final component in children) { + component.renderTree(canvas); + } } @override @@ -150,7 +126,6 @@ class FlameGame extends ComponentTreeRoot if (parent == null) { updateTree(dt); } - _cameraWrapper.update(dt); } @override @@ -159,7 +134,9 @@ class FlameGame extends ComponentTreeRoot if (parent != null) { update(dt); } - children.forEach((c) => c.updateTree(dt)); + for (final component in children) { + component.updateTree(dt); + } processRebalanceEvents(); } @@ -171,14 +148,15 @@ class FlameGame extends ComponentTreeRoot /// components and other methods. /// You can override it further to add more custom behavior, but you should /// seriously consider calling the super implementation as well. - /// This implementation also uses the current [oldCamera] in order to - /// transform the coordinate system appropriately for those using the old - /// camera. @override @mustCallSuper void onGameResize(Vector2 size) { - oldCamera.handleResize(size); super.onGameResize(size); + // This work-around is needed since the camera has the highest priority and + // [size] uses [viewport.virtualSize], so the viewport needs to be updated + // first since users will be using `game.size` in their [onGameResize] + // methods. + camera.viewport.onGameResize(size); // [onGameResize] is declared both in [Component] and in [Game]. Since // there is no way to explicitly call the [Component]'s implementation, // we propagate the event to [FlameGame]'s children manually. @@ -208,8 +186,11 @@ class FlameGame extends ComponentTreeRoot /// Whether a point is within the boundaries of the visible part of the game. @override - bool containsLocalPoint(Vector2 p) { - return p.x >= 0 && p.y >= 0 && p.x < size.x && p.y < size.y; + bool containsLocalPoint(Vector2 point) { + return point.x >= 0 && + point.y >= 0 && + point.x < canvasSize.x && + point.y < canvasSize.y; } /// Returns the current time in seconds with microseconds precision. @@ -220,12 +201,6 @@ class FlameGame extends ComponentTreeRoot Duration.microsecondsPerSecond; } - @override - Projector get viewportProjector => oldCamera.viewport; - - @override - Projector get projector => oldCamera.combinedProjector; - /// Returns a [ComponentsNotifier] for the given type [W]. /// /// This method handles duplications, so there will never be diff --git a/packages/flame/lib/src/game/game.dart b/packages/flame/lib/src/game/game.dart index 3aaa6ce5d58..be983fd4b72 100644 --- a/packages/flame/lib/src/game/game.dart +++ b/packages/flame/lib/src/game/game.dart @@ -7,7 +7,6 @@ import 'package:flame/src/flame.dart'; import 'package:flame/src/game/game_render_box.dart'; import 'package:flame/src/game/game_widget/gesture_detector_builder.dart'; import 'package:flame/src/game/overlay_manager.dart'; -import 'package:flame/src/game/projector.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -256,16 +255,6 @@ abstract mixin class Game { return _gameRenderBox!.localToGlobal(point.toOffset()).toVector2(); } - /// This is the projector used by all components that respect the camera - /// (`respectCamera = true`). - /// This can be overridden on your [Game] implementation. - Projector projector = IdentityProjector(); - - /// This is the projector used by components that don't respect the camera - /// (`positionType = PositionType.viewport;`). - /// This can be overridden on your [Game] implementation. - Projector viewportProjector = IdentityProjector(); - /// Utility method to load and cache the image for a sprite based on its /// options. Future loadSprite( diff --git a/packages/flame/lib/src/game/game_widget/gesture_detector_builder.dart b/packages/flame/lib/src/game/game_widget/gesture_detector_builder.dart index a6807727bcc..1689ef1832f 100644 --- a/packages/flame/lib/src/game/game_widget/gesture_detector_builder.dart +++ b/packages/flame/lib/src/game/game_widget/gesture_detector_builder.dart @@ -173,18 +173,11 @@ class GestureDetectorBuilder { bool hasMouseDetectors(Game game) { return game is MouseMovementDetector || game is ScrollDetector || - // ignore: deprecated_member_use_from_same_package - game is HasHoverables || game.mouseDetector != null; } Widget applyMouseDetectors(Game game, Widget child) { - final mouseMoveFn = switch (game) { - MouseMovementDetector() => game.onMouseMove, - // ignore: deprecated_member_use_from_same_package - HasHoverables() => game.onMouseMove, - _ => null, - }; + final mouseMoveFn = game is MouseMovementDetector ? game.onMouseMove : null; final mouseDetector = game.mouseDetector; return Listener( child: MouseRegion( diff --git a/packages/flame/lib/src/game/mixins/has_draggables.dart b/packages/flame/lib/src/game/mixins/has_draggables.dart deleted file mode 100644 index 0c26bba97c7..00000000000 --- a/packages/flame/lib/src/game/mixins/has_draggables.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flame/game.dart'; -import 'package:flame/src/events/flame_drag_adapter.dart'; -import 'package:flame/src/events/interfaces/multi_drag_listener.dart'; -import 'package:flame/src/gestures/events.dart'; -import 'package:flutter/gestures.dart'; -import 'package:meta/meta.dart'; - -@Deprecated( - 'Will be removed in Flame v1.10.0, use DragCallbacks without a game mixin ' - 'instead', -) -mixin HasDraggables on FlameGame implements MultiDragListener { - @mustCallSuper - void onDragStart(int pointerId, DragStartInfo info) { - propagateToChildren( - (c) => c.handleDragStart(pointerId, info), - ); - } - - @mustCallSuper - void onDragUpdate(int pointerId, DragUpdateInfo info) { - propagateToChildren( - (c) => c.handleDragUpdated(pointerId, info), - ); - } - - @mustCallSuper - void onDragEnd(int pointerId, DragEndInfo info) { - propagateToChildren( - (c) => c.handleDragEnded(pointerId, info), - ); - } - - @mustCallSuper - void onDragCancel(int pointerId) { - propagateToChildren( - (c) => c.handleDragCanceled(pointerId), - ); - } - - //#region MultiDragListener API - - @override - void handleDragStart(int pointerId, DragStartDetails details) { - onDragStart(pointerId, DragStartInfo.fromDetails(this, details)); - } - - @override - void handleDragUpdate(int pointerId, DragUpdateDetails details) { - onDragUpdate(pointerId, DragUpdateInfo.fromDetails(this, details)); - } - - @override - void handleDragEnd(int pointerId, DragEndDetails details) { - onDragEnd(pointerId, DragEndInfo.fromDetails(this, details)); - } - - @override - void handleDragCancel(int pointerId) { - onDragCancel(pointerId); - } - - //#endregion - - @override - void mount() { - gestureDetectors.add( - ImmediateMultiDragGestureRecognizer.new, - (ImmediateMultiDragGestureRecognizer instance) { - instance.onStart = (Offset point) => FlameDragAdapter(this, point); - }, - ); - super.mount(); - } -} diff --git a/packages/flame/lib/src/game/mixins/has_hoverables.dart b/packages/flame/lib/src/game/mixins/has_hoverables.dart deleted file mode 100644 index f80fd96f7d9..00000000000 --- a/packages/flame/lib/src/game/mixins/has_hoverables.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flame/game.dart'; -import 'package:flame/src/gestures/events.dart'; -import 'package:meta/meta.dart'; - -@Deprecated( - 'Will be removed in Flame v1.10.0, use HoverCallbacks without a game mixin ' - 'instead.', -) -mixin HasHoverables on FlameGame { - @mustCallSuper - void onMouseMove(PointerHoverInfo info) { - propagateToChildren((c) => c.handleMouseMovement(info)); - } -} diff --git a/packages/flame/lib/src/game/mixins/has_tappables.dart b/packages/flame/lib/src/game/mixins/has_tappables.dart deleted file mode 100644 index 9a51dc6abd3..00000000000 --- a/packages/flame/lib/src/game/mixins/has_tappables.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:flame/src/components/mixins/tappable.dart'; -import 'package:flame/src/events/game_mixins/multi_touch_tap_detector.dart'; -import 'package:flame/src/events/interfaces/multi_tap_listener.dart'; -import 'package:flame/src/game/flame_game.dart'; -import 'package:flame/src/gestures/events.dart'; -import 'package:flutter/gestures.dart'; -import 'package:meta/meta.dart'; - -/// Mixin that can be added to a [FlameGame] allowing it (and the components -/// attached to it) to receive tap events. -/// -/// This mixin is similar to [MultiTouchTapDetector] on Game, however, it also -/// propagates all tap events down the component tree, allowing each individual -/// component to respond to events that happen on that component. -/// -/// This mixin **must be** added to a game if you plan to use any components -/// that are [Tappable]. -/// -/// See [MultiTapGestureRecognizer] for the description of each individual -/// event. -@Deprecated( - 'Will be removed in Flame v1.10.0, use TapCallbacks without a game mixin ' - 'instead', -) -mixin HasTappables on FlameGame implements MultiTapListener { - @mustCallSuper - void onTapCancel(int pointerId) { - propagateToChildren( - (Tappable child) => child.handleTapCancel(pointerId), - ); - } - - @mustCallSuper - void onTapDown(int pointerId, TapDownInfo info) { - propagateToChildren( - (Tappable child) => child.handleTapDown(pointerId, info), - ); - } - - @mustCallSuper - void onTapUp(int pointerId, TapUpInfo info) { - propagateToChildren( - (Tappable child) => child.handleTapUp(pointerId, info), - ); - } - - @mustCallSuper - void onLongTapDown(int pointerId, TapDownInfo info) { - propagateToChildren( - (Tappable child) => child.handleLongTapDown(pointerId, info), - ); - } - - //#region MultiTapListener API - - @override - double get longTapDelay => 0.300; - - @override - void handleTap(int pointerId) {} - - @override - void handleTapCancel(int pointerId) => onTapCancel(pointerId); - - @override - void handleTapDown(int pointerId, TapDownDetails details) { - onTapDown(pointerId, TapDownInfo.fromDetails(this, details)); - } - - @override - void handleTapUp(int pointerId, TapUpDetails details) { - onTapUp(pointerId, TapUpInfo.fromDetails(this, details)); - } - - @override - void handleLongTapDown(int pointerId, TapDownDetails details) { - onLongTapDown(pointerId, TapDownInfo.fromDetails(this, details)); - } - - //#endregion - - @override - void mount() { - gestureDetectors.add( - MultiTapGestureRecognizer.new, - (MultiTapGestureRecognizer instance) { - instance.longTapDelay = Duration( - milliseconds: (longTapDelay * 1000).toInt(), - ); - instance.onTap = handleTap; - instance.onTapDown = handleTapDown; - instance.onTapUp = handleTapUp; - instance.onTapCancel = handleTapCancel; - instance.onLongTapDown = handleLongTapDown; - }, - ); - super.mount(); - } -} diff --git a/packages/flame/lib/src/game/transform2d.dart b/packages/flame/lib/src/game/transform2d.dart index 83dbf0b5d8b..edd84b18580 100644 --- a/packages/flame/lib/src/game/transform2d.dart +++ b/packages/flame/lib/src/game/transform2d.dart @@ -35,9 +35,6 @@ class Transform2D extends ChangeNotifier { final NotifyingVector2 _position; final NotifyingVector2 _scale; final NotifyingVector2 _offset; - @Deprecated('Use tau from the package:flame/geometry.dart export instead, ' - 'this field will be removed in Flame v1.10.0') - static const tau = 2 * math.pi; Transform2D() : _transformMatrix = Matrix4.identity(), diff --git a/packages/flame/lib/src/gestures/detectors.dart b/packages/flame/lib/src/gestures/detectors.dart index 02ad2613b88..1d216cd579b 100644 --- a/packages/flame/lib/src/gestures/detectors.dart +++ b/packages/flame/lib/src/gestures/detectors.dart @@ -97,7 +97,7 @@ mixin VerticalDragDetector on Game { } void handleVerticalDragEnd(DragEndDetails details) { - onVerticalDragEnd(DragEndInfo.fromDetails(this, details)); + onVerticalDragEnd(DragEndInfo.fromDetails(details)); } } @@ -121,7 +121,7 @@ mixin HorizontalDragDetector on Game { } void handleHorizontalDragEnd(DragEndDetails details) { - onHorizontalDragEnd(DragEndInfo.fromDetails(this, details)); + onHorizontalDragEnd(DragEndInfo.fromDetails(details)); } } @@ -168,7 +168,7 @@ mixin PanDetector on Game { } void handlePanEnd(DragEndDetails details) { - onPanEnd(DragEndInfo.fromDetails(this, details)); + onPanEnd(DragEndInfo.fromDetails(details)); } } @@ -186,7 +186,7 @@ mixin ScaleDetector on Game { } void handleScaleEnd(ScaleEndDetails details) { - onScaleEnd(ScaleEndInfo.fromDetails(this, details)); + onScaleEnd(ScaleEndInfo.fromDetails(details)); } } diff --git a/packages/flame/lib/src/gestures/events.dart b/packages/flame/lib/src/gestures/events.dart index 9f2fcfcf8d9..5af12858be7 100644 --- a/packages/flame/lib/src/gestures/events.dart +++ b/packages/flame/lib/src/gestures/events.dart @@ -9,10 +9,6 @@ import 'package:flutter/gestures.dart'; /// `globalPosition` in Flutter. /// widget: coordinate system relative to the GameWidget widget; same as /// `localPosition` in Flutter. -/// viewport: same as `widget` but also applies any transformations from the -/// viewport to the coordinate system. -/// game: same as `widget` but also applies any transformations from the camera -/// and viewport to the coordinate system. class EventPosition { final Game _game; final Offset _globalPosition; @@ -23,14 +19,6 @@ class EventPosition { /// Coordinates of the event relative to the game widget position/size late final Vector2 widget = _game.convertGlobalToLocalCoordinate(global); - /// Coordinates of the event relative to the game position/size but applying - /// only viewport transformations (not camera). - late final Vector2 viewport = _game.viewportProjector.unprojectVector(widget); - - /// Coordinates of the event relative to the game position/size and - /// transformations - late final Vector2 game = _game.projector.unprojectVector(widget); - EventPosition(this._game, this._globalPosition); } @@ -40,22 +28,13 @@ class EventPosition { /// [global]: this is the raw value received by the event without any scale /// applied to it; this is always the same as local because Flutter doesn't /// apply any scaling. -/// [game]: the scaled value with all the game transformations applied. class EventDelta { - final Game _game; final Offset _delta; /// Raw value relative to the game transformations late final Vector2 global = _delta.toVector2(); - /// Scaled value relative to the game viewport only transformations (not - /// camera). - late final Vector2 viewport = _game.viewportProjector.unscaleVector(global); - - /// Scaled value relative to the game transformations - late final Vector2 game = _game.projector.unscaleVector(global); - - EventDelta(this._game, this._delta); + EventDelta(this._delta); } /// BaseInfo is the base class for Flame's input events. @@ -104,8 +83,7 @@ class LongPressStartInfo extends PositionInfo { } class LongPressEndInfo extends PositionInfo { - late final Vector2 velocity = - _game.projector.unscaleVector(raw.velocity.pixelsPerSecond.toVector2()); + late final Vector2 velocity = raw.velocity.pixelsPerSecond.toVector2(); LongPressEndInfo.fromDetails( Game game, @@ -130,7 +108,7 @@ class ForcePressInfo extends PositionInfo { } class PointerScrollInfo extends PositionInfo { - late final EventDelta scrollDelta = EventDelta(_game, raw.scrollDelta); + late final EventDelta scrollDelta = EventDelta(raw.scrollDelta); PointerScrollInfo.fromDetails( Game game, @@ -162,7 +140,7 @@ class DragStartInfo extends PositionInfo with _HandledField { class DragUpdateInfo extends PositionInfo with _HandledField { - late final EventDelta delta = EventDelta(_game, raw.delta); + late final EventDelta delta = EventDelta(raw.delta); DragUpdateInfo.fromDetails( Game game, @@ -171,15 +149,10 @@ class DragUpdateInfo extends PositionInfo } class DragEndInfo extends BaseInfo with _HandledField { - final Game _game; - late final Vector2 velocity = - _game.projector.unscaleVector(raw.velocity.pixelsPerSecond.toVector2()); + late final Vector2 velocity = raw.velocity.pixelsPerSecond.toVector2(); double? get primaryVelocity => raw.primaryVelocity; - DragEndInfo.fromDetails( - this._game, - DragEndDetails raw, - ) : super(raw); + DragEndInfo.fromDetails(super.raw); } class ScaleStartInfo extends PositionInfo { @@ -192,26 +165,18 @@ class ScaleStartInfo extends PositionInfo { } class ScaleEndInfo extends BaseInfo { - final Game _game; - late final EventDelta velocity = EventDelta( - _game, - raw.velocity.pixelsPerSecond, - ); + late final EventDelta velocity = EventDelta(raw.velocity.pixelsPerSecond); int get pointerCount => raw.pointerCount; - ScaleEndInfo.fromDetails( - this._game, - ScaleEndDetails raw, - ) : super(raw); + ScaleEndInfo.fromDetails(super.raw); } class ScaleUpdateInfo extends PositionInfo { int get pointerCount => raw.pointerCount; double get rotation => raw.rotation; - late final EventDelta delta = EventDelta(_game, raw.focalPointDelta); + late final EventDelta delta = EventDelta(raw.focalPointDelta); late final EventDelta scale = EventDelta( - _game, Offset(raw.horizontalScale, raw.verticalScale), ); diff --git a/packages/flame/lib/util.dart b/packages/flame/lib/util.dart deleted file mode 100644 index 717d52013dd..00000000000 --- a/packages/flame/lib/util.dart +++ /dev/null @@ -1,6 +0,0 @@ -@Deprecated( - 'Import math.dart instead, this file will be removed in a Flame v1.10.0', -) -export 'src/math/random_fallback.dart'; -export 'src/math/solve_cubic.dart'; -export 'src/math/solve_quadratic.dart'; diff --git a/packages/flame/test/camera/camera_component_test.dart b/packages/flame/test/camera/camera_component_test.dart index 6fb31862f5b..2694a3b6ec4 100644 --- a/packages/flame/test/camera/camera_component_test.dart +++ b/packages/flame/test/camera/camera_component_test.dart @@ -226,6 +226,40 @@ void main() { }); testWithFlameGame('visibleWorldRect', (game) async { + await game.ready(); + final camera = game.camera; + game.onGameResize(Vector2(60, 40)); + + // By default, the viewfinder's position is (0,0), and its anchor is in + // the center of the viewport. + expect(camera.visibleWorldRect, const Rect.fromLTRB(-30, -20, 30, 20)); + + camera.viewfinder.position = Vector2(100, 200); + expect(camera.visibleWorldRect, const Rect.fromLTRB(70, 180, 130, 220)); + + camera.viewfinder.zoom = 2; + camera.viewfinder.position = Vector2(20, 30); + expect(camera.visibleWorldRect, const Rect.fromLTRB(5, 20, 35, 40)); + + camera.viewport.size = Vector2(100, 60); + expect(camera.visibleWorldRect, const Rect.fromLTRB(-5, 15, 45, 45)); + + camera.viewfinder.position = Vector2.zero(); + expect(camera.visibleWorldRect, const Rect.fromLTRB(-25, -15, 25, 15)); + + // Rotation angle: cos(a) = 0.6, sin(a) = 0.8 + // Each point (x, y) becomes (x*cos(a) - y*sin(a), x*sin(a) + y*cos(a)), + // and each of the 4 corners turns into + // (25, 15) -> (3, 29) + // (25, -15) -> (27, 11) + // (-25, -15) -> (-3, -29) + // (-25, 15) -> (-27, -11) + // which means the culling rect is (-27, -29, 27, 29) + camera.viewfinder.angle = acos(0.6); + expect(camera.visibleWorldRect, const Rect.fromLTRB(-27, -29, 27, 29)); + }); + + testWithFlameGame('visibleWorldRect with FixedSizeViewport', (game) async { final world = World(); final camera = CameraComponent( world: world, diff --git a/packages/flame/test/camera/viewports/max_viewport_test.dart b/packages/flame/test/camera/viewports/max_viewport_test.dart index 716fe589b3e..3e14f779b12 100644 --- a/packages/flame/test/camera/viewports/max_viewport_test.dart +++ b/packages/flame/test/camera/viewports/max_viewport_test.dart @@ -54,9 +54,9 @@ void main() { }, (game) async { final viewport = game.camera.viewport; expect(viewport, isA<_MyMaxViewport>()); - expect((viewport as _MyMaxViewport).onViewportResizeCalled, 2); + expect((viewport as _MyMaxViewport).onViewportResizeCalled, 3); game.onGameResize(Vector2(200, 200)); - expect(viewport.onViewportResizeCalled, 3); + expect(viewport.onViewportResizeCalled, 5); }); }); } diff --git a/packages/flame/test/components/mixins/component_viewport_margin_test.dart b/packages/flame/test/components/mixins/component_viewport_margin_test.dart index 8b5eb03c2f6..f553adfaa75 100644 --- a/packages/flame/test/components/mixins/component_viewport_margin_test.dart +++ b/packages/flame/test/components/mixins/component_viewport_margin_test.dart @@ -41,4 +41,4 @@ void main() { } class _ComponentWithViewportMargin extends PositionComponent - with HasGameRef, ComponentViewportMargin {} + with HasGameReference, ComponentViewportMargin {} diff --git a/packages/flame/test/components/mixins/draggable_test.dart b/packages/flame/test/components/mixins/draggable_test.dart deleted file mode 100644 index c85b8c709cd..00000000000 --- a/packages/flame/test/components/mixins/draggable_test.dart +++ /dev/null @@ -1,180 +0,0 @@ -// ignore_for_file: deprecated_member_use_from_same_package - -import 'package:flame/components.dart'; -import 'package:flame/game.dart'; -import 'package:flame/input.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter/gestures.dart'; -import 'package:test/test.dart'; - -void main() { - group('Draggable', () { - testWithGame<_GameHasDraggables>( - 'make sure they can be added to game with HasDraggables', - _GameHasDraggables.new, - (game) async { - await game.add(_DraggableComponent()); - await game.ready(); - }, - ); - - testWithFlameGame( - 'make sure they cannot be added to invalid games', - (game) async { - expect( - () => game.ensureAdd(_DraggableComponent()), - failsAssert( - 'Draggable Components can only be added to a FlameGame with ' - 'HasDraggables or HasDraggablesBridge', - ), - ); - }, - ); - - testWithGame<_GameHasDraggables>( - 'can be dragged', - _GameHasDraggables.new, - (game) async { - final component = _DraggableComponent() - ..x = 10 - ..y = 10 - ..width = 10 - ..height = 10; - - await game.ensureAdd(component); - game.onDragStart( - 1, - DragStartInfo.fromDetails( - game, - DragStartDetails( - localPosition: const Offset(12, 12), - globalPosition: const Offset(12, 12), - ), - ), - ); - expect(component.hasStartedDragging, true); - }, - ); - - testWithGame<_GameHasDraggables>( - 'when the game has camera zoom, can be dragged', - _GameHasDraggables.new, - (game) async { - final component = _DraggableComponent() - ..x = 10 - ..y = 10 - ..width = 10 - ..height = 10; - - await game.ensureAdd(component); - game.oldCamera.zoom = 1.5; - game.onDragStart( - 1, - DragStartInfo.fromDetails( - game, - DragStartDetails( - localPosition: const Offset(15, 15), - globalPosition: const Offset(15, 15), - ), - ), - ); - expect(component.hasStartedDragging, true); - }, - ); - - testWithGame<_GameHasDraggables>( - 'when the game has a moved camera, dragging works', - _GameHasDraggables.new, - (game) async { - final component = _DraggableComponent() - ..x = 50 - ..y = 50 - ..width = 10 - ..height = 10; - - await game.ensureAdd(component); - game.oldCamera.zoom = 1.5; - game.oldCamera.snapTo(Vector2.all(50)); - game.onDragStart( - 1, - DragStartInfo.fromDetails( - game, - DragStartDetails( - localPosition: const Offset(5, 5), - globalPosition: const Offset(5, 5), - ), - ), - ); - expect(component.hasStartedDragging, true); - }, - ); - }); - - testWithGame<_GameHasDraggables>( - 'isDragged is changed', - _GameHasDraggables.new, - (game) async { - final component = _DraggableComponent() - ..x = 10 - ..y = 10 - ..width = 10 - ..height = 10; - - await game.ensureAdd(component); - expect(component.isDragged, false); - game.onDragStart( - 1, - DragStartInfo.fromDetails( - game, - DragStartDetails( - localPosition: const Offset(12, 12), - globalPosition: const Offset(12, 12), - ), - ), - ); - expect(component.isDragged, true); - game.onDragEnd( - 1, - DragEndInfo.fromDetails( - game, - DragEndDetails(), - ), - ); - expect(component.isDragged, false); - game.onDragStart( - 1, - DragStartInfo.fromDetails( - game, - DragStartDetails( - localPosition: const Offset(12, 12), - globalPosition: const Offset(12, 12), - ), - ), - ); - expect(component.isDragged, true); - expect(component.hasCanceledDragging, false); - game.onDragCancel(1); - expect(component.isDragged, false); - expect(component.hasCanceledDragging, true); - }, - ); -} - -class _GameHasDraggables extends FlameGame with HasDraggables {} - -class _DraggableComponent extends PositionComponent with Draggable { - bool hasStartedDragging = false; - bool hasCanceledDragging = false; - - @override - bool onDragStart(DragStartInfo info) { - hasStartedDragging = true; - return true; - } - - @override - bool onDragCancel() { - hasCanceledDragging = true; - return true; - } -} diff --git a/packages/flame/test/components/mixins/hoverable_test.dart b/packages/flame/test/components/mixins/hoverable_test.dart deleted file mode 100644 index 9c59dd4dfb0..00000000000 --- a/packages/flame/test/components/mixins/hoverable_test.dart +++ /dev/null @@ -1,216 +0,0 @@ -// ignore_for_file: deprecated_member_use_from_same_package - -import 'dart:ui'; - -import 'package:flame/components.dart'; -import 'package:flame/game.dart'; -import 'package:flame/src/gestures/events.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter/gestures.dart' show PointerHoverEvent; -import 'package:test/test.dart'; - -void main() { - group('Hoverable', () { - testWithGame<_GameWithHoverables>( - 'make sure they can be added to game with HasHoverables', - _GameWithHoverables.new, - (game) async { - await game.add(_HoverableComponent()); - await game.ready(); - }, - ); - - testWithFlameGame( - 'make sure they cannot be added to invalid games', - (game) async { - expect( - () async { - await game.add(_HoverableComponent()); - await game.ready(); - }, - failsAssert( - 'Hoverable Components can only be added to a FlameGame with ' - 'HasHoverables', - ), - ); - }, - ); - - testWithGame<_GameWithHoverables>( - 'single component', - _GameWithHoverables.new, - (game) async { - final c = _HoverableComponent() - ..position = Vector2(10, 20) - ..size = Vector2(3, 3); - await game.ensureAdd(c); - - expect(c.isHovered, false); - expect(c.enterCount, 0); - expect(c.leaveCount, 0); - - _triggerMouseMove(game, 0, 0); - expect(c.isHovered, false); - expect(c.enterCount, 0); - expect(c.leaveCount, 0); - - _triggerMouseMove(game, 11, 0); - expect(c.isHovered, false); - expect(c.enterCount, 0); - expect(c.leaveCount, 0); - - _triggerMouseMove(game, 11, 21); // enter! - expect(c.isHovered, true); - expect(c.enterCount, 1); - expect(c.leaveCount, 0); - - _triggerMouseMove(game, 12, 22); // still inside - expect(c.isHovered, true); - expect(c.enterCount, 1); - expect(c.leaveCount, 0); - - _triggerMouseMove(game, 11, 25); // leave - expect(c.isHovered, false); - expect(c.enterCount, 1); - expect(c.leaveCount, 1); - - _triggerMouseMove(game, 11, 21); // enter again - expect(c.isHovered, true); - expect(c.enterCount, 2); - expect(c.leaveCount, 1); - }, - ); - - testWithGame<_GameWithHoverables>( - 'camera is respected', - _GameWithHoverables.new, - (game) async { - final c = _HoverableComponent() - ..position = Vector2(10, 20) - ..size = Vector2(3, 3); - await game.ensureAdd(c); - - // component is now at the corner of the screen - game.oldCamera.snapTo(Vector2(10, 20)); - - _triggerMouseMove(game, 11, 21); - expect(c.isHovered, false); - _triggerMouseMove(game, 11, 1); - expect(c.isHovered, false); - _triggerMouseMove(game, 1, 1); - expect(c.isHovered, true); - _triggerMouseMove(game, 5, 1); - expect(c.isHovered, false); - }, - ); - - testWithGame<_GameWithHoverables>( - 'multiple components', - _GameWithHoverables.new, - (game) async { - final a = _HoverableComponent() - ..position = Vector2(10, 0) - ..size = Vector2(2, 20); - final b = _HoverableComponent() - ..position = Vector2(10, 10) - ..size = Vector2(2, 2); - final c = _HoverableComponent() - ..position = Vector2(0, 7) - ..size = Vector2(20, 2); - await game.ensureAdd(a); - await game.ensureAdd(b); - await game.ensureAdd(c); - - _triggerMouseMove(game, 0, 0); - expect(a.isHovered, false); - expect(b.isHovered, false); - expect(c.isHovered, false); - - _triggerMouseMove(game, 10, 10); - expect(a.isHovered, true); - expect(b.isHovered, true); - expect(c.isHovered, false); - - _triggerMouseMove(game, 11, 8); - expect(a.isHovered, true); - expect(b.isHovered, false); - expect(c.isHovered, true); - - _triggerMouseMove(game, 11, 6); - expect(a.isHovered, true); - expect(b.isHovered, false); - expect(c.isHovered, false); - }, - ); - - testWithGame<_GameWithHoverables>( - 'composed components', - _GameWithHoverables.new, - (game) async { - final parent = _HoverableComponent() - ..position = Vector2.all(10) - ..size = Vector2.all(10); - final child = _NonPropagatingComponent() - ..position = Vector2.all(0) - ..size = Vector2.all(10); - await parent.add(child); - await game.ensureAdd(parent); - _triggerMouseMove(game, 15, 15); - expect(child.isHovered, true); - expect(parent.isHovered, false); - expect(child.enterCount, 1); - expect(parent.enterCount, 0); - _triggerMouseMove(game, 35, 35); - expect(child.isHovered, false); - expect(parent.isHovered, false); - expect(child.leaveCount, 1); - expect(parent.leaveCount, 0); - }, - ); - }); -} - -class _GameWithHoverables extends FlameGame with HasHoverables {} - -class _HoverableComponent extends PositionComponent with Hoverable { - int enterCount = 0; - int leaveCount = 0; - - @override - bool onHoverEnter(PointerHoverInfo info) { - enterCount++; - return true; - } - - @override - bool onHoverLeave(PointerHoverInfo info) { - leaveCount++; - return true; - } -} - -class _NonPropagatingComponent extends _HoverableComponent { - @override - bool onHoverEnter(PointerHoverInfo info) { - super.onHoverEnter(info); - return false; - } - - @override - bool onHoverLeave(PointerHoverInfo info) { - super.onHoverLeave(info); - return false; - } -} - -// TODO(luan): we can probably provide some helpers to facilitate testing events -void _triggerMouseMove(HasHoverables game, double dx, double dy) { - game.onMouseMove( - PointerHoverInfo.fromDetails( - game, - PointerHoverEvent( - position: Offset(dx, dy), - ), - ), - ); -} diff --git a/packages/flame/test/components/position_type_test.dart b/packages/flame/test/components/position_type_test.dart deleted file mode 100644 index 32a672e93db..00000000000 --- a/packages/flame/test/components/position_type_test.dart +++ /dev/null @@ -1,179 +0,0 @@ -// ignore_for_file: deprecated_member_use_from_same_package - -import 'dart:ui'; - -import 'package:canvas_test/canvas_test.dart'; -import 'package:flame/components.dart'; -import 'package:flame/game.dart'; -import 'package:flame/palette.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('PositionType', () { - testWithFlameGame( - 'PositionType.game', - (game) async { - await game.ensureAddAll([ - _MyComponent(4), - _MyComponent(1), - _MyComponent(2), - ]); - - final canvas = MockCanvas(); - game.oldCamera.snapTo(Vector2(12.0, 18.0)); - game.render(canvas); - - expect( - canvas, - MockCanvas() - ..translate(-12.0, -18.0) - ..drawRect(const Rect.fromLTWH(1, 1, 1, 1)) - ..drawRect(const Rect.fromLTWH(2, 2, 1, 1)) - ..drawRect(const Rect.fromLTWH(4, 4, 1, 1)) - ..translate(0.0, 0.0), - ); - }, - ); - - testWithFlameGame( - 'PositionType.viewport', - (game) async { - game.removeAll([game.world, game.camera]); - await game.ensureAddAll([ - _MyComponent(4, positionType: PositionType.viewport), - _MyComponent(1, positionType: PositionType.viewport), - _MyComponent(2, positionType: PositionType.viewport), - ]); - final canvas = MockCanvas(); - game.oldCamera.snapTo(Vector2(12.0, 18.0)); - game.render(canvas); - - expect( - canvas, - MockCanvas() - ..drawRect(const Rect.fromLTWH(1, 1, 1, 1)) - ..drawRect(const Rect.fromLTWH(2, 2, 1, 1)) - ..drawRect(const Rect.fromLTWH(4, 4, 1, 1)), - ); - }, - ); - - group('PositionType.widget', () { - testWithFlameGame( - 'viewport does not affect component with PositionType.widget', - (game) async { - game.removeAll([game.world, game.camera]); - game.oldCamera.viewport = FixedResolutionViewport(Vector2.all(50)); - game.onGameResize(Vector2.all(200.0)); - await game.ensureAdd( - _MyComponent(0, positionType: PositionType.widget), - ); - - final canvas = MockCanvas(); - game.render(canvas); - expect( - canvas, - MockCanvas()..drawRect(const Rect.fromLTWH(0, 0, 1, 1)), - ); - }, - ); - - testWithFlameGame( - 'camera does not affect component with PositionType.widget', - (game) async { - game.removeAll([game.world, game.camera]); - await game.ensureAdd( - _MyComponent(0, positionType: PositionType.widget), - ); - game.oldCamera.snapTo(Vector2(100, 100)); - - final canvas = MockCanvas(); - game.render(canvas); - expect( - canvas, - MockCanvas()..drawRect(const Rect.fromLTWH(0, 0, 1, 1)), - ); - }, - ); - - testWithFlameGame( - 'Several static components', - (game) async { - game.removeAll([game.world, game.camera]); - await game.ensureAddAll([ - _MyComponent(5, positionType: PositionType.widget), - _MyComponent(1, positionType: PositionType.widget), - _MyComponent(2, positionType: PositionType.widget), - ]); - - final canvas = MockCanvas(); - game.oldCamera.snapTo(Vector2(12.0, 18.0)); - game.render(canvas); - - expect( - canvas, - MockCanvas() - ..drawRect(const Rect.fromLTWH(1, 1, 1, 1)) - ..drawRect(const Rect.fromLTWH(2, 2, 1, 1)) - ..drawRect(const Rect.fromLTWH(5, 5, 1, 1)), - ); - }, - ); - }); - - testWithFlameGame( - 'mixed', - (game) async { - game.removeAll([game.world, game.camera]); - await game.ensureAddAll([ - _MyComponent(4), - _MyComponent(1), - _MyComponent(7, positionType: PositionType.viewport), - _MyComponent(5, positionType: PositionType.viewport), - _MyComponent(3, positionType: PositionType.viewport), - _MyComponent(0), - _MyComponent(6, positionType: PositionType.widget), - _MyComponent(2, positionType: PositionType.widget), - ]); - - final canvas = MockCanvas(); - game.oldCamera.snapTo(Vector2(12.0, 18.0)); - game.render(canvas); - - expect( - canvas, - MockCanvas() - ..translate(-12.0, -18.0) - ..drawRect(const Rect.fromLTWH(0, 0, 1, 1)) - ..drawRect(const Rect.fromLTWH(1, 1, 1, 1)) - ..translate(0.0, 0.0) - ..drawRect(const Rect.fromLTWH(2, 2, 1, 1)) - ..drawRect(const Rect.fromLTWH(3, 3, 1, 1)) - ..translate(-12.0, -18.0) - ..drawRect(const Rect.fromLTWH(4, 4, 1, 1)) - ..translate(0.0, 0.0) - ..drawRect(const Rect.fromLTWH(5, 5, 1, 1)) - ..drawRect(const Rect.fromLTWH(6, 6, 1, 1)) - ..drawRect(const Rect.fromLTWH(7, 7, 1, 1)), - ); - }, - ); - }); -} - -class _MyComponent extends Component { - _MyComponent(int priority, {this.positionType = PositionType.game}) - : super(priority: priority); - - @override - PositionType positionType; - - @override - void render(Canvas canvas) { - final p = Vector2.all(priority.toDouble()); - final size = Vector2.all(1.0); - canvas.drawRect(p & size, BasicPalette.white.paint()); - } -} diff --git a/packages/flame/test/events/component_mixins/drag_callbacks_test.dart b/packages/flame/test/events/component_mixins/drag_callbacks_test.dart index b1c6c0fc9b3..afe3ad06cb9 100644 --- a/packages/flame/test/events/component_mixins/drag_callbacks_test.dart +++ b/packages/flame/test/events/component_mixins/drag_callbacks_test.dart @@ -184,6 +184,121 @@ void main() { }, ); }); + + group('HasDraggableComponents', () { + testWidgets( + 'drags are delivered to DragCallbacks components', + (tester) async { + var nDragStartCalled = 0; + var nDragUpdateCalled = 0; + var nDragEndCalled = 0; + final game = FlameGame( + children: [ + _DragWithCallbacksComponent( + position: Vector2(20, 20), + size: Vector2(100, 100), + onDragStart: (e) => nDragStartCalled++, + onDragUpdate: (e) => nDragUpdateCalled++, + onDragEnd: (e) => nDragEndCalled++, + ), + ], + ); + await tester.pumpWidget(GameWidget(game: game)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 10)); + + expect(game.children.length, 4); + expect(game.children.elementAt(1), isA<_DragWithCallbacksComponent>()); + expect(game.children.elementAt(2), isA()); + +// regular drag + await tester.timedDragFrom( + const Offset(50, 50), + const Offset(20, 0), + const Duration(milliseconds: 100), + ); + expect(nDragStartCalled, 1); + expect(nDragUpdateCalled, 8); + expect(nDragEndCalled, 1); + +// cancelled drag + final gesture = await tester.startGesture(const Offset(50, 50)); + await gesture.moveBy(const Offset(10, 10)); + await gesture.cancel(); + await tester.pump(const Duration(seconds: 1)); + expect(nDragStartCalled, 2); + expect(nDragEndCalled, 2); + }, + ); + + testWidgets( + 'drag event does not affect more than one component', + (tester) async { + var nEvents = 0; + final game = FlameGame( + children: [ + _DragWithCallbacksComponent( + size: Vector2.all(100), + onDragStart: (e) => nEvents++, + onDragUpdate: (e) => nEvents++, + onDragEnd: (e) => nEvents++, + ), + _SimpleDragCallbacksComponent(size: Vector2.all(200)), + ], + ); + await tester.pumpWidget(GameWidget(game: game)); + await tester.pump(); + await tester.pump(); + expect(game.children.length, 5); + expect(game.children.elementAt(3), isA()); + + await tester.timedDragFrom( + const Offset(20, 20), + const Offset(5, 5), + const Duration(seconds: 1), + ); + expect(nEvents, 0); + }, + ); + + testWidgets( + 'drag event can move outside the component bounds', + (tester) async { + final points = []; + final game = FlameGame( + children: [ + _DragWithCallbacksComponent( + size: Vector2.all(95), + position: Vector2.all(5), + onDragUpdate: (e) => points.add(e.localPosition), + ), + ], + ); + await tester.pumpWidget(GameWidget(game: game)); + await tester.pump(); + await tester.pump(); + expect(game.children.length, 4); + expect(game.children.elementAt(2), isA()); + + await tester.timedDragFrom( + const Offset(80, 80), + const Offset(0, 40), + const Duration(seconds: 1), + frequency: 40, + ); + expect(points.length, 42); + expect(points.first, Vector2(75, 75)); + expect( + points.skip(1).take(20), + List.generate(20, (i) => Vector2(75.0, 75.0 + i)), + ); + expect( + points.skip(21), + everyElement(predicate((Vector2 v) => v.isNaN)), + ); + }, + ); + }); } mixin _DragCounter on DragCallbacks { @@ -235,3 +350,41 @@ class _DragCallbacksComponent extends PositionComponent with DragCallbacks, _DragCounter {} class _DragCallbacksGame extends FlameGame with DragCallbacks, _DragCounter {} + +class _DragWithCallbacksComponent extends PositionComponent with DragCallbacks { + _DragWithCallbacksComponent({ + void Function(DragStartEvent)? onDragStart, + void Function(DragUpdateEvent)? onDragUpdate, + void Function(DragEndEvent)? onDragEnd, + super.position, + super.size, + }) : _onDragStart = onDragStart, + _onDragUpdate = onDragUpdate, + _onDragEnd = onDragEnd; + + final void Function(DragStartEvent)? _onDragStart; + final void Function(DragUpdateEvent)? _onDragUpdate; + final void Function(DragEndEvent)? _onDragEnd; + + @override + void onDragStart(DragStartEvent event) { + super.onDragStart(event); + return _onDragStart?.call(event); + } + + @override + void onDragUpdate(DragUpdateEvent event) { + return _onDragUpdate?.call(event); + } + + @override + void onDragEnd(DragEndEvent event) { + super.onDragEnd(event); + return _onDragEnd?.call(event); + } +} + +class _SimpleDragCallbacksComponent extends PositionComponent + with DragCallbacks { + _SimpleDragCallbacksComponent({super.size}); +} diff --git a/packages/flame/test/events/component_mixins/tap_callbacks_test.dart b/packages/flame/test/events/component_mixins/tap_callbacks_test.dart index 2f730b01414..995205a0f82 100644 --- a/packages/flame/test/events/component_mixins/tap_callbacks_test.dart +++ b/packages/flame/test/events/component_mixins/tap_callbacks_test.dart @@ -233,6 +233,248 @@ void main() { }, ); }); + + testWidgets( + 'taps are delivered to a TapCallbacks component', + (tester) async { + var nTapDown = 0; + var nLongTapDown = 0; + var nTapCancel = 0; + var nTapUp = 0; + final game = FlameGame( + children: [ + _TapWithCallbacksComponent( + size: Vector2(200, 100), + position: Vector2(50, 50), + onTapDown: (e) => nTapDown++, + onLongTapDown: (e) => nLongTapDown++, + onTapCancel: (e) => nTapCancel++, + onTapUp: (e) => nTapUp++, + ), + ], + ); + await tester.pumpWidget(GameWidget(game: game)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 10)); + expect(game.children.length, 4); + + // regular tap + await tester.tapAt(const Offset(100, 100)); + await tester.pump(const Duration(milliseconds: 100)); + expect(nTapDown, 1); + expect(nTapUp, 1); + expect(nLongTapDown, 0); + expect(nTapCancel, 0); + + // long tap + await tester.longPressAt(const Offset(100, 100)); + await tester.pump(const Duration(seconds: 1)); + expect(nTapDown, 2); + expect(nTapUp, 2); + expect(nLongTapDown, 1); + expect(nTapCancel, 0); + + // cancelled tap + var gesture = await tester.startGesture(const Offset(100, 100)); + await gesture.cancel(); + await tester.pump(const Duration(seconds: 1)); + expect(nTapDown, 3); + expect(nTapUp, 2); + expect(nTapCancel, 1); + + // tap cancelled via movement + gesture = await tester.startGesture(const Offset(100, 100)); + await gesture.moveBy(const Offset(20, 20)); + await tester.pump(const Duration(seconds: 1)); + expect(nTapDown, 4); + expect(nTapUp, 2); + expect(nTapCancel, 2); + }, + ); + + testWidgets( + 'TapCallbacks component nested in another TapCallbacks component', + (tester) async { + var nTapDownChild = 0; + var nTapDownParent = 0; + var nTapCancelChild = 0; + var nTapCancelParent = 0; + var nTapUpChild = 0; + var nTapUpParent = 0; + final game = FlameGame( + children: [ + _TapWithCallbacksComponent( + size: Vector2.all(100), + position: Vector2.zero(), + onTapDown: (e) => nTapDownParent++, + onTapUp: (e) => nTapUpParent++, + onTapCancel: (e) => nTapCancelParent++, + children: [ + _TapWithCallbacksComponent( + size: Vector2.all(50), + position: Vector2.all(25), + onTapDown: (e) { + nTapDownChild++; + e.continuePropagation = true; + }, + onTapCancel: (e) => nTapCancelChild++, + onTapUp: (e) => nTapUpChild++, + ), + ], + ), + ], + ); + await tester.pumpWidget(GameWidget(game: game)); + await tester.pump(); + await tester.pump(); + expect(game.children.length, 4); + expect(game.children.elementAt(1).children.length, 1); + + await tester.longPressAt(const Offset(50, 50)); + await tester.pump(const Duration(seconds: 1)); + expect(nTapDownChild, 1); + expect(nTapDownParent, 1); + expect(nTapUpChild, 1); + expect(nTapUpParent, 1); + expect(nTapCancelChild, 0); + expect(nTapCancelParent, 0); + + // cancelled tap + final gesture = await tester.startGesture(const Offset(50, 50)); + await gesture.cancel(); + await tester.pump(const Duration(seconds: 1)); + expect(nTapDownChild, 2); + expect(nTapDownParent, 2); + expect(nTapUpChild, 1); + expect(nTapUpParent, 1); + expect(nTapCancelChild, 1); + expect(nTapCancelParent, 1); + }, + ); + + testWidgets( + 'tap events do not propagate down by default', + (tester) async { + var nTapDownParent = 0; + var nTapCancelParent = 0; + var nTapUpParent = 0; + final game = FlameGame( + children: [ + _TapWithCallbacksComponent( + size: Vector2.all(100), + position: Vector2.zero(), + onTapDown: (e) => nTapDownParent++, + onTapUp: (e) => nTapUpParent++, + onTapCancel: (e) => nTapCancelParent++, + children: [ + _SimpleTapCallbacksComponent(size: Vector2.all(100)), + ], + ), + ], + ); + await tester.pumpWidget(GameWidget(game: game)); + await tester.pump(); + await tester.pump(); + expect(game.children.length, 4); + expect(game.children.elementAt(1).children.length, 1); + + await tester.longPressAt(const Offset(50, 50)); + await tester.pump(const Duration(seconds: 1)); + expect(nTapDownParent, 0); + expect(nTapUpParent, 0); + expect(nTapCancelParent, 0); + + // cancelled tap + final gesture = await tester.startGesture(const Offset(50, 50)); + await gesture.cancel(); + await tester.pump(const Duration(seconds: 1)); + expect(nTapDownParent, 0); + expect(nTapUpParent, 0); + expect(nTapCancelParent, 0); + }, + ); + + testWidgets( + 'local coordinates during tap events', + (tester) async { + TapDownEvent? tapDownEvent; + final game = FlameGame( + children: [ + PositionComponent( + size: Vector2.all(400), + position: Vector2.all(10), + children: [ + PositionComponent( + size: Vector2(300, 200), + scale: Vector2(1.5, 2), + position: Vector2.all(40), + children: [ + _TapWithCallbacksComponent( + size: Vector2(100, 50), + position: Vector2(50, 50), + onTapDown: (e) => tapDownEvent = e, + ), + ], + ), + ], + ), + ], + ); + await tester.pumpWidget(GameWidget(game: game)); + await tester.pump(); + await tester.pump(); + expect(game.children.length, 4); + expect(game.children.elementAt(1).children.length, 1); + + await tester.tapAt(const Offset(200, 200)); + await tester.pump(const Duration(seconds: 1)); + expect(tapDownEvent, isNotNull); + expect(tapDownEvent!.devicePosition, Vector2(200, 200)); + expect(tapDownEvent!.canvasPosition, Vector2(200, 200)); + expect(tapDownEvent!.localPosition, Vector2(50, 25)); + final trace = tapDownEvent!.renderingTrace.reversed.toList(); + expect(trace[0], Vector2(50, 25)); + expect(trace[1], Vector2(100, 75)); + expect(trace[2], Vector2(190, 190)); + expect(trace[3], Vector2(200, 200)); + }, + ); +} + +class _TapWithCallbacksComponent extends PositionComponent with TapCallbacks { + _TapWithCallbacksComponent({ + required Vector2 super.position, + required Vector2 super.size, + super.children, + void Function(TapDownEvent)? onTapDown, + void Function(TapDownEvent)? onLongTapDown, + void Function(TapUpEvent)? onTapUp, + void Function(TapCancelEvent)? onTapCancel, + }) : _onTapDown = onTapDown, + _onLongTapDown = onLongTapDown, + _onTapUp = onTapUp, + _onTapCancel = onTapCancel; + + final void Function(TapDownEvent)? _onTapDown; + final void Function(TapDownEvent)? _onLongTapDown; + final void Function(TapUpEvent)? _onTapUp; + final void Function(TapCancelEvent)? _onTapCancel; + + @override + void onTapDown(TapDownEvent event) => _onTapDown?.call(event); + + @override + void onLongTapDown(TapDownEvent event) => _onLongTapDown?.call(event); + + @override + void onTapUp(TapUpEvent event) => _onTapUp?.call(event); + + @override + void onTapCancel(TapCancelEvent event) => _onTapCancel?.call(event); +} + +class _SimpleTapCallbacksComponent extends PositionComponent with TapCallbacks { + _SimpleTapCallbacksComponent({super.size}); } mixin _TapCounter on TapCallbacks { diff --git a/packages/flame/test/events/flame_game_mixins/has_draggable_components_test.dart b/packages/flame/test/events/flame_game_mixins/has_draggable_components_test.dart deleted file mode 100644 index bf2385a1e61..00000000000 --- a/packages/flame/test/events/flame_game_mixins/has_draggable_components_test.dart +++ /dev/null @@ -1,229 +0,0 @@ -// ignore_for_file: deprecated_member_use_from_same_package - -import 'package:flame/components.dart'; -import 'package:flame/events.dart'; -import 'package:flame/game.dart'; -import 'package:flame/src/events/flame_game_mixins/multi_drag_dispatcher.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('HasDraggableComponents', () { - testWidgets( - 'drags are delivered to DragCallbacks components', - (tester) async { - var nDragStartCalled = 0; - var nDragUpdateCalled = 0; - var nDragEndCalled = 0; - final game = FlameGame( - children: [ - _DragCallbacksComponent( - position: Vector2(20, 20), - size: Vector2(100, 100), - onDragStart: (e) => nDragStartCalled++, - onDragUpdate: (e) => nDragUpdateCalled++, - onDragEnd: (e) => nDragEndCalled++, - ), - ], - ); - await tester.pumpWidget(GameWidget(game: game)); - await tester.pump(); - await tester.pump(const Duration(milliseconds: 10)); - - expect(game.children.length, 4); - expect(game.children.elementAt(1), isA<_DragCallbacksComponent>()); - expect(game.children.elementAt(2), isA()); - - // regular drag - await tester.timedDragFrom( - const Offset(50, 50), - const Offset(20, 0), - const Duration(milliseconds: 100), - ); - expect(nDragStartCalled, 1); - expect(nDragUpdateCalled, 8); - expect(nDragEndCalled, 1); - - // cancelled drag - final gesture = await tester.startGesture(const Offset(50, 50)); - await gesture.moveBy(const Offset(10, 10)); - await gesture.cancel(); - await tester.pump(const Duration(seconds: 1)); - expect(nDragStartCalled, 2); - expect(nDragEndCalled, 2); - }, - ); - - testWidgets( - 'drag event does not affect more than one component', - (tester) async { - var nEvents = 0; - final game = FlameGame( - children: [ - _DragCallbacksComponent( - size: Vector2.all(100), - onDragStart: (e) => nEvents++, - onDragUpdate: (e) => nEvents++, - onDragEnd: (e) => nEvents++, - ), - _SimpleDragCallbacksComponent(size: Vector2.all(200)), - ], - ); - await tester.pumpWidget(GameWidget(game: game)); - await tester.pump(); - await tester.pump(); - expect(game.children.length, 5); - expect(game.children.elementAt(3), isA()); - - await tester.timedDragFrom( - const Offset(20, 20), - const Offset(5, 5), - const Duration(seconds: 1), - ); - expect(nEvents, 0); - }, - ); - - testWidgets( - 'drag event can move outside the component bounds', - (tester) async { - final points = []; - final game = FlameGame( - children: [ - _DragCallbacksComponent( - size: Vector2.all(95), - position: Vector2.all(5), - onDragUpdate: (e) => points.add(e.localPosition), - ), - ], - ); - await tester.pumpWidget(GameWidget(game: game)); - await tester.pump(); - await tester.pump(); - expect(game.children.length, 4); - expect(game.children.elementAt(2), isA()); - - await tester.timedDragFrom( - const Offset(80, 80), - const Offset(0, 40), - const Duration(seconds: 1), - frequency: 40, - ); - expect(points.length, 42); - expect(points.first, Vector2(75, 75)); - expect( - points.skip(1).take(20), - List.generate(20, (i) => Vector2(75.0, 75.0 + i)), - ); - expect( - points.skip(21), - everyElement(predicate((Vector2 v) => v.isNaN)), - ); - }, - ); - - testWidgets( - 'game with Draggables', - (tester) async { - var nDragCallbackUpdates = 0; - var nDraggableUpdates = 0; - final game = _GameWithDualDraggableComponents( - children: [ - _DragCallbacksComponent( - size: Vector2.all(100), - onDragStart: (e) => e.continuePropagation = true, - onDragUpdate: (e) => nDragCallbackUpdates++, - ), - _DraggableComponent( - size: Vector2.all(100), - onDragStart: (e) => true, - onDragUpdate: (e) { - nDraggableUpdates++; - return true; - }, - ), - ], - ); - await tester.pumpWidget(GameWidget(game: game)); - await tester.pump(); - await tester.pump(); - expect(game.children.length, 5); - expect(game.children.elementAt(3), isA()); - - await tester.timedDragFrom( - const Offset(50, 50), - const Offset(-20, 20), - const Duration(seconds: 1), - ); - expect(nDragCallbackUpdates, 62); - expect(nDraggableUpdates, 62); - }, - ); - }); -} - -class _GameWithDualDraggableComponents extends FlameGame - with HasDraggablesBridge { - _GameWithDualDraggableComponents({super.children}); -} - -class _DragCallbacksComponent extends PositionComponent with DragCallbacks { - _DragCallbacksComponent({ - void Function(DragStartEvent)? onDragStart, - void Function(DragUpdateEvent)? onDragUpdate, - void Function(DragEndEvent)? onDragEnd, - super.position, - super.size, - }) : _onDragStart = onDragStart, - _onDragUpdate = onDragUpdate, - _onDragEnd = onDragEnd; - - final void Function(DragStartEvent)? _onDragStart; - final void Function(DragUpdateEvent)? _onDragUpdate; - final void Function(DragEndEvent)? _onDragEnd; - - @override - void onDragStart(DragStartEvent event) { - super.onDragStart(event); - return _onDragStart?.call(event); - } - - @override - void onDragUpdate(DragUpdateEvent event) { - return _onDragUpdate?.call(event); - } - - @override - void onDragEnd(DragEndEvent event) { - super.onDragEnd(event); - return _onDragEnd?.call(event); - } -} - -class _SimpleDragCallbacksComponent extends PositionComponent - with DragCallbacks { - _SimpleDragCallbacksComponent({super.size}); -} - -class _DraggableComponent extends PositionComponent with Draggable { - _DraggableComponent({ - super.size, - bool Function(DragStartInfo)? onDragStart, - bool Function(DragUpdateInfo)? onDragUpdate, - bool Function(DragEndInfo)? onDragEnd, - }) : _onDragStart = onDragStart, - _onDragUpdate = onDragUpdate, - _onDragEnd = onDragEnd; - - final bool Function(DragStartInfo)? _onDragStart; - final bool Function(DragUpdateInfo)? _onDragUpdate; - final bool Function(DragEndInfo)? _onDragEnd; - - @override - bool onDragStart(DragStartInfo info) => _onDragStart?.call(info) ?? true; - - @override - bool onDragUpdate(DragUpdateInfo info) => _onDragUpdate?.call(info) ?? true; - - @override - bool onDragEnd(DragEndInfo info) => _onDragEnd?.call(info) ?? true; -} diff --git a/packages/flame/test/events/flame_game_mixins/has_tappable_components_test.dart b/packages/flame/test/events/flame_game_mixins/has_tappable_components_test.dart deleted file mode 100644 index ea53dc54046..00000000000 --- a/packages/flame/test/events/flame_game_mixins/has_tappable_components_test.dart +++ /dev/null @@ -1,341 +0,0 @@ -// ignore_for_file: deprecated_member_use_from_same_package - -import 'package:flame/components.dart'; -import 'package:flame/events.dart'; -import 'package:flame/game.dart'; -import 'package:flame/src/events/flame_game_mixins/multi_tap_dispatcher.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('HasTappableComponents', () { - testWidgets( - 'taps are delivered to a TapCallbacks component', - (tester) async { - var nTapDown = 0; - var nLongTapDown = 0; - var nTapCancel = 0; - var nTapUp = 0; - final game = FlameGame( - children: [ - _TapCallbacksComponent( - size: Vector2(200, 100), - position: Vector2(50, 50), - onTapDown: (e) => nTapDown++, - onLongTapDown: (e) => nLongTapDown++, - onTapCancel: (e) => nTapCancel++, - onTapUp: (e) => nTapUp++, - ), - ], - ); - await tester.pumpWidget(GameWidget(game: game)); - await tester.pump(); - await tester.pump(const Duration(milliseconds: 10)); - expect(game.children.length, 4); - - // regular tap - await tester.tapAt(const Offset(100, 100)); - await tester.pump(const Duration(milliseconds: 100)); - expect(nTapDown, 1); - expect(nTapUp, 1); - expect(nLongTapDown, 0); - expect(nTapCancel, 0); - - // long tap - await tester.longPressAt(const Offset(100, 100)); - await tester.pump(const Duration(seconds: 1)); - expect(nTapDown, 2); - expect(nTapUp, 2); - expect(nLongTapDown, 1); - expect(nTapCancel, 0); - - // cancelled tap - var gesture = await tester.startGesture(const Offset(100, 100)); - await gesture.cancel(); - await tester.pump(const Duration(seconds: 1)); - expect(nTapDown, 3); - expect(nTapUp, 2); - expect(nTapCancel, 1); - - // tap cancelled via movement - gesture = await tester.startGesture(const Offset(100, 100)); - await gesture.moveBy(const Offset(20, 20)); - await tester.pump(const Duration(seconds: 1)); - expect(nTapDown, 4); - expect(nTapUp, 2); - expect(nTapCancel, 2); - }, - ); - - testWidgets( - 'TapCallbacks component nested in another TapCallbacks component', - (tester) async { - var nTapDownChild = 0; - var nTapDownParent = 0; - var nTapCancelChild = 0; - var nTapCancelParent = 0; - var nTapUpChild = 0; - var nTapUpParent = 0; - final game = FlameGame( - children: [ - _TapCallbacksComponent( - size: Vector2.all(100), - position: Vector2.zero(), - onTapDown: (e) => nTapDownParent++, - onTapUp: (e) => nTapUpParent++, - onTapCancel: (e) => nTapCancelParent++, - children: [ - _TapCallbacksComponent( - size: Vector2.all(50), - position: Vector2.all(25), - onTapDown: (e) { - nTapDownChild++; - e.continuePropagation = true; - }, - onTapCancel: (e) => nTapCancelChild++, - onTapUp: (e) => nTapUpChild++, - ), - ], - ), - ], - ); - await tester.pumpWidget(GameWidget(game: game)); - await tester.pump(); - await tester.pump(); - expect(game.children.length, 4); - expect(game.children.elementAt(1).children.length, 1); - - await tester.longPressAt(const Offset(50, 50)); - await tester.pump(const Duration(seconds: 1)); - expect(nTapDownChild, 1); - expect(nTapDownParent, 1); - expect(nTapUpChild, 1); - expect(nTapUpParent, 1); - expect(nTapCancelChild, 0); - expect(nTapCancelParent, 0); - - // cancelled tap - final gesture = await tester.startGesture(const Offset(50, 50)); - await gesture.cancel(); - await tester.pump(const Duration(seconds: 1)); - expect(nTapDownChild, 2); - expect(nTapDownParent, 2); - expect(nTapUpChild, 1); - expect(nTapUpParent, 1); - expect(nTapCancelChild, 1); - expect(nTapCancelParent, 1); - }, - ); - - testWidgets( - 'tap events do not propagate down by default', - (tester) async { - var nTapDownParent = 0; - var nTapCancelParent = 0; - var nTapUpParent = 0; - final game = FlameGame( - children: [ - _TapCallbacksComponent( - size: Vector2.all(100), - position: Vector2.zero(), - onTapDown: (e) => nTapDownParent++, - onTapUp: (e) => nTapUpParent++, - onTapCancel: (e) => nTapCancelParent++, - children: [ - _SimpleTapCallbacksComponent(size: Vector2.all(100)), - ], - ), - ], - ); - await tester.pumpWidget(GameWidget(game: game)); - await tester.pump(); - await tester.pump(); - expect(game.children.length, 4); - expect(game.children.elementAt(1).children.length, 1); - - await tester.longPressAt(const Offset(50, 50)); - await tester.pump(const Duration(seconds: 1)); - expect(nTapDownParent, 0); - expect(nTapUpParent, 0); - expect(nTapCancelParent, 0); - - // cancelled tap - final gesture = await tester.startGesture(const Offset(50, 50)); - await gesture.cancel(); - await tester.pump(const Duration(seconds: 1)); - expect(nTapDownParent, 0); - expect(nTapUpParent, 0); - expect(nTapCancelParent, 0); - }, - ); - - testWidgets( - 'local coordinates during tap events', - (tester) async { - TapDownEvent? tapDownEvent; - final game = FlameGame( - children: [ - PositionComponent( - size: Vector2.all(400), - position: Vector2.all(10), - children: [ - PositionComponent( - size: Vector2(300, 200), - scale: Vector2(1.5, 2), - position: Vector2.all(40), - children: [ - _TapCallbacksComponent( - size: Vector2(100, 50), - position: Vector2(50, 50), - onTapDown: (e) => tapDownEvent = e, - ), - ], - ), - ], - ), - ], - ); - await tester.pumpWidget(GameWidget(game: game)); - await tester.pump(); - await tester.pump(); - expect(game.children.length, 4); - expect(game.children.elementAt(1).children.length, 1); - - await tester.tapAt(const Offset(200, 200)); - await tester.pump(const Duration(seconds: 1)); - expect(tapDownEvent, isNotNull); - expect(tapDownEvent!.devicePosition, Vector2(200, 200)); - expect(tapDownEvent!.canvasPosition, Vector2(200, 200)); - expect(tapDownEvent!.localPosition, Vector2(50, 25)); - final trace = tapDownEvent!.renderingTrace.reversed.toList(); - expect(trace[0], Vector2(50, 25)); - expect(trace[1], Vector2(100, 75)); - expect(trace[2], Vector2(190, 190)); - expect(trace[3], Vector2(200, 200)); - }, - ); - }); - - group('HasTappablesBridge', () { - testWidgets( - 'taps are delivered to tappables of both kinds', - (tester) async { - var nTappableDown = 0; - var nTappableCancelled = 0; - var nTapCallbacksDown = 0; - var nTapCallbacksCancelled = 0; - final game = _GameWithDualTappableComponents( - children: [ - _TapCallbacksComponent( - size: Vector2(100, 100), - position: Vector2(20, 20), - onTapDown: (e) { - e.continuePropagation = true; - nTapCallbacksDown++; - }, - onTapCancel: (e) { - e.continuePropagation = true; - nTapCallbacksCancelled++; - }, - ), - _TappableComponent( - size: Vector2(100, 100), - position: Vector2(40, 40), - onTapDown: (e) { - nTappableDown++; - return true; - }, - onTapCancel: () { - nTappableCancelled++; - return true; - }, - ), - ], - ); - await tester.pumpWidget(GameWidget(game: game)); - await tester.pump(); - await tester.pump(const Duration(milliseconds: 10)); - expect(game.children.length, 5); - expect(game.children.elementAt(3), isA()); - - await tester.tapAt(const Offset(50, 50)); - await tester.pump(const Duration(seconds: 1)); - expect(nTapCallbacksDown, 1); - expect(nTappableDown, 1); - - // cancelled tap - final gesture = await tester.startGesture(const Offset(100, 100)); - await gesture.cancel(); - await tester.pump(const Duration(seconds: 1)); - expect(nTapCallbacksDown, 2); - expect(nTapCallbacksCancelled, 1); - expect(nTappableDown, 2); - expect(nTappableCancelled, 1); - }, - ); - }); -} - -class _GameWithDualTappableComponents extends FlameGame - with HasTappablesBridge { - _GameWithDualTappableComponents({super.children}); -} - -class _TapCallbacksComponent extends PositionComponent with TapCallbacks { - _TapCallbacksComponent({ - required Vector2 super.position, - required Vector2 super.size, - super.children, - void Function(TapDownEvent)? onTapDown, - void Function(TapDownEvent)? onLongTapDown, - void Function(TapUpEvent)? onTapUp, - void Function(TapCancelEvent)? onTapCancel, - }) : _onTapDown = onTapDown, - _onLongTapDown = onLongTapDown, - _onTapUp = onTapUp, - _onTapCancel = onTapCancel; - - final void Function(TapDownEvent)? _onTapDown; - final void Function(TapDownEvent)? _onLongTapDown; - final void Function(TapUpEvent)? _onTapUp; - final void Function(TapCancelEvent)? _onTapCancel; - - @override - void onTapDown(TapDownEvent event) => _onTapDown?.call(event); - - @override - void onLongTapDown(TapDownEvent event) => _onLongTapDown?.call(event); - - @override - void onTapUp(TapUpEvent event) => _onTapUp?.call(event); - - @override - void onTapCancel(TapCancelEvent event) => _onTapCancel?.call(event); -} - -class _SimpleTapCallbacksComponent extends PositionComponent with TapCallbacks { - _SimpleTapCallbacksComponent({super.size}); -} - -class _TappableComponent extends PositionComponent with Tappable { - _TappableComponent({ - required Vector2 size, - required Vector2 position, - bool Function(TapDownInfo)? onTapDown, - bool Function()? onTapCancel, - }) : _onTapDown = onTapDown, - _onTapCancel = onTapCancel, - super(size: size, position: position); - - final bool Function(TapDownInfo)? _onTapDown; - final bool Function()? _onTapCancel; - - @override - bool onTapDown(TapDownInfo info) { - return _onTapDown?.call(info) ?? true; - } - - @override - bool onTapCancel() { - return _onTapCancel?.call() ?? true; - } -} diff --git a/packages/flame/test/game/camera/camera_test.dart b/packages/flame/test/game/camera/camera_test.dart deleted file mode 100644 index 72fd01bac93..00000000000 --- a/packages/flame/test/game/camera/camera_test.dart +++ /dev/null @@ -1,355 +0,0 @@ -// ignore_for_file: deprecated_member_use_from_same_package - -import 'dart:ui' show Paint; - -import 'package:canvas_test/canvas_test.dart'; -import 'package:flame/components.dart'; -import 'package:flame/extensions.dart'; -import 'package:flame/game.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('Camera', () { - testWithFlameGame( - 'default camera applies no translation', - (game) async { - expect(game.oldCamera.position, Vector2.zero()); - - await game.ensureAdd(_TestComponent(Vector2.all(10.0))); - - final canvas = MockCanvas(); - game.render(canvas); - expect( - canvas, - MockCanvas() - ..translate(10, 10) - ..drawRect(const Rect.fromLTWH(0, 0, 1, 1)) - ..translate(0, 0), // reset camera - ); - }, - ); - - testWithFlameGame( - 'camera snap movement', - (game) async { - expect(game.oldCamera.position, Vector2.zero()); - - await game.ensureAdd(_TestComponent(Vector2.all(10.0))); - - // this puts the top left of the screen on (4,4) - game.oldCamera.moveTo(Vector2.all(4.0)); - game.oldCamera.snap(); - // the component will now be at 10 - 4 = (6, 6) - expect(game.oldCamera.position, Vector2.all(4.0)); - - final canvas = MockCanvas(); - game.render(canvas); - expect( - canvas, - MockCanvas() - ..translate(-4, -4) // Camera translation - ..translate(10, 10) // PositionComponent translation - ..drawRect(const Rect.fromLTWH(0, 0, 1, 1)) - ..translate(0, 0), // reset camera - ); - }, - ); - - testWithFlameGame( - 'camera smooth movement', - (game) async { - game.oldCamera.speed = 1; // 1 pixel per second - game.oldCamera.moveTo(Vector2(0.0, 10.0)); - - // nothing should change yet - expect(game.oldCamera.position, Vector2.all(0.0)); - game.update(2.0); // 2s - expect(game.oldCamera.position, Vector2(0.0, 2.0)); - game.update(5.0); // 5s - expect(game.oldCamera.position, Vector2(0.0, 7.0)); - game.update(100.0); // more than needed at once - expect(game.oldCamera.position, Vector2(0.0, 10.0)); - }, - ); - - testWithFlameGame( - 'camera follow', - (game) async { - game.onGameResize(Vector2.all(100.0)); - - final p = _TestComponent(Vector2.all(10.0))..anchor = Anchor.center; - await game.ensureAdd(p); - game.oldCamera.followComponent(p); - - expect(game.oldCamera.position, Vector2.all(0.0)); - p.position.setValues(10.0, 20.0); - // follow happens immediately because the object's movement is assumed - // to be smooth. - game.update(0); - // (10,20) - half screen (50,50). - expect(game.oldCamera.position, Vector2(-40, -30)); - - final canvas = MockCanvas(); - game.render(canvas); - expect( - canvas, - MockCanvas() - ..translate(40, 30) // Camera translation - ..translate(9.5, 19.5) // PositionComponent translation - ..drawRect(const Rect.fromLTWH(0, 0, 1, 1)) - ..translate(0, 0), // reset camera - // result: 50 - w/2, 50 - h/2 (perfectly centered) - ); - }, - ); - - testWithFlameGame( - 'camera follow with relative position', - (game) async { - game.onGameResize(Vector2.all(100.0)); - - final p = _TestComponent(Vector2.all(10.0))..anchor = Anchor.center; - await game.ensureAdd(p); - // this would be a typical vertical shoot-em-up - game.oldCamera - .followComponent(p, relativeOffset: const Anchor(0.5, 0.8)); - - expect(game.oldCamera.position, Vector2.all(0.0)); - p.position.setValues(600.0, 2000.0); - // follow happens immediately because the object's movement is assumed - // to be smooth. - game.update(0); - // (600,2000) - fractional screen (50,80) - expect(game.oldCamera.position, Vector2(550, 1920)); - - final canvas = MockCanvas(); - game.render(canvas); - expect( - canvas, - MockCanvas() - ..translate(-550, -1920) // Camera translation - ..translate(599.5, 1999.5) // PositionComponent translation - ..drawRect(const Rect.fromLTWH(0, 0, 1, 1)) - ..translate(0, 0), // reset camera - ); - }, - ); - - testWithFlameGame( - 'camera follow with world boundaries', - (game) async { - game.onGameResize(Vector2.all(100.0)); - - final p = _TestComponent(Vector2.all(10.0))..anchor = Anchor.center; - await game.ensureAdd(p); - game.oldCamera.followComponent( - p, - worldBounds: const Rect.fromLTWH(-1000, -1000, 2000, 2000), - ); - - p.position.setValues(600.0, 700.0); // well within bounds - game.update(0); - expect(game.oldCamera.position, Vector2(550, 650)); - - // x ok, y starts to get to bounds - p.position.setValues(600.0, 950.0); // right on the edge - game.update(0); - expect(game.oldCamera.position, Vector2(550, 900)); - - p.position.setValues(600.0, 950.0); // stop advancing - game.update(0); - expect(game.oldCamera.position, Vector2(550, 900)); - - p.position.setValues(-1100.0, 950.0); - game.update(0); - expect(game.oldCamera.position, Vector2(-1000, 900)); - - p.position.setValues(1000.0, 1000.0); - game.update(0); - expect(game.oldCamera.position, Vector2(900, 900)); - }, - ); - - testWithFlameGame( - 'camera follow with world boundaries smaller than the screen', - (game) async { - game.onGameResize(Vector2.all(200.0)); - - final p = _TestComponent(Vector2.all(10.0))..anchor = Anchor.center; - await game.ensureAdd(p); - game.oldCamera.followComponent( - p, - worldBounds: const Rect.fromLTWH(0, 0, 100, 100), - ); - - // In this case the camera will just center the world, no matter the - // player position. - game.update(0); - expect(game.oldCamera.position, Vector2(50, 50)); - - p.position.setValues(60.0, 50.0); - game.update(0); - expect(game.oldCamera.position, Vector2(50, 50)); - - p.position.setValues(-10.0, -20.0); - game.update(0); - expect(game.oldCamera.position, Vector2(50, 50)); - }, - ); - - testWithFlameGame( - 'camera follow with zoom', - (game) async { - game.onGameResize(Vector2.all(100.0)); - game.oldCamera.zoom = 2; - - final p = _TestComponent(Vector2.all(10.0))..anchor = Anchor.center; - await game.ensureAdd(p); - game.oldCamera.followComponent( - p, - worldBounds: const Rect.fromLTWH(0, 0, 100, 100), - ); - - game.update(0); - expect(game.oldCamera.position, Vector2(0, 0)); - - p.position.setValues(50.0, 60.0); - game.update(0); - expect(game.oldCamera.position, Vector2(25, 35)); - - p.position.setValues(80.0, 90.0); - game.update(0); - expect(game.oldCamera.position, Vector2(50, 50)); - - p.position.setValues(-10.0, -20.0); - game.update(0); - expect(game.oldCamera.position, Vector2(0, 0)); - }, - ); - - testWithFlameGame( - 'camera relative offset without follow', - (game) async { - game.onGameResize(Vector2.all(200.0)); - - game.oldCamera.setRelativeOffset(Anchor.center); - - game.update(0); - expect(game.oldCamera.position, Vector2.zero()); - - game.update(10000); - expect(game.oldCamera.position, Vector2.all(-100.0)); - }, - ); - - testWithFlameGame( - 'camera zoom', - (game) async { - game.oldCamera.zoom = 2; - - final p = _TestComponent(Vector2.all(100.0))..anchor = Anchor.center; - await game.ensureAdd(p); - - final canvas = MockCanvas(); - game.render(canvas); - expect( - canvas, - MockCanvas() - ..scale(2) // Camera zoom - ..translate(99.5, 99.5) // PositionComponent translation - ..drawRect(const Rect.fromLTWH(0, 0, 1, 1)) - ..translate(0, 0), // reset camera - ); - }, - ); - - testWithFlameGame( - 'camera zoom with setRelativeOffset', - (game) async { - game.onGameResize(Vector2.all(200.0)); - game.oldCamera.zoom = 2; - game.oldCamera.setRelativeOffset(Anchor.center); - - final p = _TestComponent(Vector2.all(100.0))..anchor = Anchor.center; - game.add(p); - game.update(10000); - - final canvas = MockCanvas(); - game.render(canvas); - expect( - canvas, - MockCanvas() - ..translate(100, 100) // camera translation - ..scale(2) // camera zoom - ..translate(99.5, 99.5) // position component - ..drawRect(const Rect.fromLTWH(0, 0, 1, 1)) - ..translate(0, 0), // reset camera - ); - expect(game.oldCamera.position, Vector2.all(-50.0)); - }, - ); - - testWithFlameGame( - 'camera shake should return to where it started', - (game) async { - final camera = game.oldCamera; - expect(camera.position, Vector2.zero()); - camera.shake(duration: 9000); - game.update(5000); - game.update(5000); - game.update(5000); - expect(camera.position, Vector2.zero()); - }, - ); - - testWithFlameGame( - 'default ratio viewport + camera with world boundaries', - (game) async { - game.oldCamera.viewport = FixedResolutionViewport(Vector2.all(100)); - game.onGameResize(Vector2.all(200.0)); - expect(game.canvasSize, Vector2.all(200.00)); - expect(game.size, Vector2.all(100.00)); - - final p = _TestComponent(Vector2.all(10.0))..anchor = Anchor.center; - await game.ensureAdd(p); - game.oldCamera.followComponent( - p, - // this could be a typical mario-like platformer, where the player is - // more on the bottom left to allow the level to be seen - relativeOffset: const Anchor(0.25, 0.25), - worldBounds: const Rect.fromLTWH(0, 0, 1000, 1000), - ); - - game.update(0); - expect(game.oldCamera.position, Vector2(0, 0)); - - p.position.setValues(30.0, 0.0); - game.update(0); - expect(game.oldCamera.position, Vector2(5, 0)); - - p.position.setValues(30.0, 100.0); - game.update(0); - expect(game.oldCamera.position, Vector2(5, 75)); - - p.position.setValues(30.0, 1000.0); - game.update(0); - expect(game.oldCamera.position, Vector2(5, 900)); - - p.position.setValues(950.0, 20.0); - game.update(0); - expect(game.oldCamera.position, Vector2(900, 0)); - }, - ); - }); -} - -class _TestComponent extends PositionComponent { - _TestComponent(Vector2 position) - : super(position: position, size: Vector2.all(1.0)); - - @override - void render(Canvas c) { - c.drawRect(size.toRect(), Paint()); - } -} diff --git a/packages/flame/test/game/camera/viewport_test.dart b/packages/flame/test/game/camera/viewport_test.dart deleted file mode 100644 index 4b7fdd3a1cc..00000000000 --- a/packages/flame/test/game/camera/viewport_test.dart +++ /dev/null @@ -1,110 +0,0 @@ -// ignore_for_file: deprecated_member_use_from_same_package - -import 'dart:ui'; - -import 'package:canvas_test/canvas_test.dart'; -import 'package:flame/components.dart'; -import 'package:flame/game.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('FixedResolutionViewport', () { - testWithFlameGame( - 'fixed ratio viewport has perfect ratio', - (game) async { - game.oldCamera.viewport = FixedResolutionViewport(Vector2.all(50)); - game.onGameResize(Vector2.all(200.0)); - expect(game.canvasSize, Vector2.all(200.00)); - expect(game.size, Vector2.all(50.00)); - - final viewport = game.oldCamera.viewport as FixedResolutionViewport; - expect(viewport.resizeOffset, Vector2.zero()); - expect(viewport.scaledSize, Vector2(200.0, 200.0)); - expect(viewport.scale, 4.0); - - await game.ensureAdd(_TestComponent(Vector2.zero())); - - final canvas = MockCanvas(); - game.render(canvas); - expect( - canvas, - MockCanvas() - ..clipRect(const Rect.fromLTWH(0, 0, 200, 200)) - ..scale(4) - ..drawRect(const Rect.fromLTWH(0, 0, 1, 1)) - ..translate(0, 0), // reset camera - ); - }, - ); - - testWithFlameGame( - 'fixed ratio viewport maxes width', - (game) async { - game.oldCamera.viewport = FixedResolutionViewport(Vector2.all(50)); - game.onGameResize(Vector2(100.0, 200.0)); - expect(game.canvasSize, Vector2(100.0, 200.00)); - expect(game.size, Vector2.all(50.00)); - - final viewport = game.oldCamera.viewport as FixedResolutionViewport; - expect(viewport.resizeOffset, Vector2(0, 50.0)); - expect(viewport.scaledSize, Vector2(100.0, 100.0)); - expect(viewport.scale, 2.0); - - await game.ensureAdd(_TestComponent(Vector2.zero())); - - final canvas = MockCanvas(); - game.render(canvas); - expect( - canvas, - MockCanvas() - ..clipRect(const Rect.fromLTWH(0, 50, 100, 100)) - ..translate(0, 50) - ..scale(2) - ..drawRect(const Rect.fromLTWH(0, 0, 1, 1)) - ..translate(0, 0), // reset camera - ); - }, - ); - - testWithFlameGame( - 'fixed ratio viewport maxes height', - (game) async { - game.oldCamera.viewport = - FixedResolutionViewport(Vector2(100.0, 400.0)); - game.onGameResize(Vector2(100.0, 200.0)); - expect(game.canvasSize, Vector2(100.0, 200.00)); - expect(game.size, Vector2(100.00, 400.0)); - - final viewport = game.oldCamera.viewport as FixedResolutionViewport; - expect(viewport.resizeOffset, Vector2(25.0, 0)); - expect(viewport.scaledSize, Vector2(50.0, 200.0)); - expect(viewport.scale, 0.5); - - await game.ensureAdd(_TestComponent(Vector2.zero())); - - final canvas = MockCanvas(); - game.render(canvas); - expect( - canvas, - MockCanvas() - ..clipRect(const Rect.fromLTWH(25, 0, 50, 200)) - ..translate(25, 0) - ..scale(0.5) - ..drawRect(const Rect.fromLTWH(0, 0, 1, 1)) - ..translate(0, 0), // reset camera - ); - }, - ); - }); -} - -class _TestComponent extends PositionComponent { - _TestComponent(Vector2 position) - : super(position: position, size: Vector2.all(1.0)); - - @override - void render(Canvas canvas) { - canvas.drawRect(size.toRect(), Paint()); - } -} diff --git a/packages/flame/test/game/flame_game_test.dart b/packages/flame/test/game/flame_game_test.dart index 5190f14b2a4..d5564d9f9d7 100644 --- a/packages/flame/test/game/flame_game_test.dart +++ b/packages/flame/test/game/flame_game_test.dart @@ -1,5 +1,3 @@ -// ignore_for_file: deprecated_member_use_from_same_package - import 'dart:ui'; import 'package:collection/collection.dart'; @@ -13,9 +11,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../components/component_test.dart'; -import 'projector_test.dart'; - void main() { group('FlameGame', () { testWithFlameGame( @@ -27,20 +22,6 @@ void main() { }, ); - testWithFlameGame('game resize in zoomed game', (game) async { - game - ..oldCamera.zoom = 10 - ..onGameResize(Vector2(300, 200)); - final component = ComponentWithSizeHistory(); - await game.ensureAdd(component); - - game.onGameResize(Vector2(400, 500)); - expect( - component.history, - equals([Vector2(300, 200), Vector2(400, 500)]), - ); - }); - testWithFlameGame('Game in game', (game) async { final innerGame = FlameGame(); game.add(innerGame); @@ -185,595 +166,146 @@ void main() { ); }); - group('projector', () { - testWithFlameGame( - 'default viewport+camera should be identity projections', - (game) async { - checkProjectorReversibility(game.projector); - expect(game.projector.projectVector(Vector2(1, 2)), Vector2(1, 2)); - expect(game.projector.unprojectVector(Vector2(1, 2)), Vector2(1, 2)); - expect(game.projector.scaleVector(Vector2(1, 2)), Vector2(1, 2)); - expect(game.projector.unscaleVector(Vector2(1, 2)), Vector2(1, 2)); - }, - ); - - testWithFlameGame( - 'viewport only with scale projection (no camera)', - (game) async { - final viewport = FixedResolutionViewport(Vector2.all(100)); - game.oldCamera.viewport = viewport; - game.onGameResize(Vector2(200, 200)); - expect(viewport.scale, 2); - expect(viewport.resizeOffset, Vector2.zero()); // no translation - checkProjectorReversibility(game.projector); - - expect(game.projector.projectVector(Vector2(1, 2)), Vector2(2, 4)); - expect(game.projector.unprojectVector(Vector2(2, 4)), Vector2(1, 2)); - expect(game.projector.scaleVector(Vector2(1, 2)), Vector2(2, 4)); - expect(game.projector.unscaleVector(Vector2(2, 4)), Vector2(1, 2)); - - expect( - game.viewportProjector.projectVector(Vector2(1, 2)), - Vector2(2, 4), - ); - expect( - game.viewportProjector.unprojectVector(Vector2(2, 4)), - Vector2(1, 2), - ); - expect( - game.viewportProjector.scaleVector(Vector2(1, 2)), - Vector2(2, 4), - ); - expect( - game.viewportProjector.unscaleVector(Vector2(2, 4)), - Vector2(1, 2), - ); - }, - ); - - testWithFlameGame( - 'viewport only with translation projection (no camera)', - (game) async { - final viewport = FixedResolutionViewport(Vector2.all(100)); - game.oldCamera.viewport = viewport; - game.onGameResize(Vector2(200, 100)); - expect(viewport.scale, 1); // no scale - expect(viewport.resizeOffset, Vector2(50, 0)); // y is unchanged - checkProjectorReversibility(game.projector); - - // Click on x=0 means -50 in the game coordinates. - expect( - game.projector.unprojectVector(Vector2.zero()), - Vector2(-50, 0), - ); - // Click on x=50 is right on the edge of the viewport. - expect( - game.projector.unprojectVector(Vector2.all(50)), - Vector2(0, 50), - ); - // Click on x=150 is on the other edge. - expect( - game.projector.unprojectVector(Vector2.all(150)), - Vector2(100, 150), - ); - // 50 past the end. - expect( - game.projector.unprojectVector(Vector2(200, 0)), - Vector2(150, 0), - ); - - // Translations should not affect projecting deltas. - expect(game.projector.unscaleVector(Vector2.zero()), Vector2.zero()); - expect( - game.projector.unscaleVector(Vector2.all(50)), - Vector2.all(50), - ); - expect( - game.projector.unscaleVector(Vector2.all(150)), - Vector2.all(150), - ); - expect( - game.projector.unscaleVector(Vector2(200, 0)), - Vector2(200, 0), - ); - }, - ); - - testWithFlameGame( - 'viewport only with both scale and translation (no camera)', - (game) async { - final viewport = FixedResolutionViewport(Vector2.all(100)); - game.oldCamera.viewport = viewport; - game.onGameResize(Vector2(200, 400)); - expect(viewport.scale, 2); - expect(viewport.resizeOffset, Vector2(0, 100)); // x is unchanged - checkProjectorReversibility(game.projector); - - // Click on y=0 means -100 in the game coordinates. - expect( - game.projector.unprojectVector(Vector2.zero()), - Vector2(0, -50), - ); - expect( - game.projector.unprojectVector(Vector2(0, 100)), - Vector2.zero(), - ); - expect( - game.projector.unprojectVector(Vector2(0, 300)), - Vector2(0, 100), - ); - expect( - game.projector.unprojectVector(Vector2(0, 400)), - Vector2(0, 150), - ); - }, - ); - - testWithFlameGame( - 'camera only with zoom (default viewport)', - (game) async { - game.onGameResize(Vector2.all(1)); - - game.oldCamera.zoom = 3; // 3x zoom - checkProjectorReversibility(game.projector); - - expect( - game.projector.unprojectVector(Vector2.zero()), - Vector2.zero(), - ); - expect(game.projector.unprojectVector(Vector2(3, 6)), Vector2(1, 2)); - - expect( - game.viewportProjector.unprojectVector(Vector2.zero()), - Vector2.zero(), - ); - expect( - game.viewportProjector.unprojectVector(Vector2(3, 6)), - Vector2(3, 6), - ); - - // Delta considers the zoom. - expect(game.projector.unscaleVector(Vector2.zero()), Vector2.zero()); - expect(game.projector.unscaleVector(Vector2(3, 6)), Vector2(1, 2)); - }, - ); - - testWithFlameGame( - 'camera only with translation (default viewport)', - (game) async { - game.onGameResize(Vector2.all(1)); - - // Top left corner of the screen is (50, 100). - game.oldCamera.snapTo(Vector2(50, 100)); - checkProjectorReversibility(game.projector); - - expect( - game.projector.unprojectVector(Vector2.zero()), - Vector2(50, 100), - ); - expect( - game.projector.unprojectVector(Vector2(-50, 50)), - Vector2(0, 150), - ); - - // Delta ignores translations. - expect(game.projector.scaleVector(Vector2.zero()), Vector2.zero()); - expect( - game.projector.scaleVector(Vector2(-50, 50)), - Vector2(-50, 50), - ); - }, - ); - - testWithFlameGame( - 'camera only with both zoom and translation (default viewport)', - (game) async { - game.onGameResize(Vector2.all(10)); - - // No-op because the default is already top left. - game.oldCamera.setRelativeOffset(Anchor.topLeft); - // Top left corner of the screen is (-100, -100). - game.oldCamera.snapTo(Vector2.all(-100)); - // Zoom is 2x, meaning every 1 unit you walk away of (-100, -100). - game.oldCamera.zoom = 2; - checkProjectorReversibility(game.projector); - - expect( - game.projector.unprojectVector(Vector2.zero()), - Vector2(-100, -100), - ); - // Let's "walk" 10 pixels down on the screen; - // that means the world walks 5 units. - expect( - game.projector.unprojectVector(Vector2(0, 10)), - Vector2(-100, -95), - ); - // To get to the world 0,0 we need to walk 200 screen units. - expect( - game.projector.unprojectVector(Vector2.all(200)), - Vector2.zero(), - ); - - // Note: in the current implementation, if we change the relative - // position the zoom is still applied w.r.t. the top left of the - // screen. - game.oldCamera.setRelativeOffset(Anchor.center); - game.oldCamera.snap(); - - // That means that the center would be -100, -100 if the zoom was 1 - // meaning the topLeft will be (-105, -105) (regardless of zoom), - // but since the offset is set to center, topLeft will be - // (-102.5, -102.5) - expect( - game.projector.unprojectVector(Vector2.zero()), - Vector2.all(-102.5), - ); - // and with 2x zoom the center will actually be -100, -100 since the - // relative offset is set to center. - expect( - game.projector.unprojectVector(Vector2.all(5)), - Vector2.all(-100), - ); - // TODO(luan): we might want to change the behavior so that the zoom - // is applied w.r.t. the relativeOffset and not topLeft - - // For deltas, we consider only the zoom. - expect(game.projector.unscaleVector(Vector2.zero()), Vector2.zero()); - expect(game.projector.unscaleVector(Vector2(2, 4)), Vector2(1, 2)); - }, - ); - - testWithFlameGame('camera & viewport - two translations', (game) async { - final viewport = FixedResolutionViewport(Vector2.all(100)); - game.oldCamera.viewport = viewport; // default camera - game.onGameResize(Vector2(200, 100)); - game.oldCamera.snapTo(Vector2(10, 100)); - expect(viewport.scale, 1); // no scale - expect(viewport.resizeOffset, Vector2(50, 0)); // y is unchanged - checkProjectorReversibility(game.projector); - - // Top left of viewport should be top left of camera. - expect( - game.projector.unprojectVector(Vector2(50, 0)), - Vector2(10, 100), - ); - // Viewport only, top left should be the top left of screen. - expect( - game.viewportProjector.unprojectVector(Vector2(50, 0)), - Vector2.zero(), + testWithGame( + 'children in the constructor', + () { + return FlameGame( + world: World( + children: [_IndexedComponent(1), _IndexedComponent(2)], + ), ); - // Top right of viewport is top left of camera + effective screen width. - expect( - game.projector.unprojectVector(Vector2(150, 0)), - Vector2(110, 100), - ); - // Vertically, only the camera translation is applied. + }, + (game) async { + final world = game.world; + world.add(_IndexedComponent(3)); + world.add(_IndexedComponent(4)); + await game.ready(); + + expect(world.children.length, 4); expect( - game.projector.unprojectVector(Vector2(40, 123)), - Vector2(0, 223), + world.children + .whereType<_IndexedComponent>() + .map((c) => c.index) + .isSorted((a, b) => a.compareTo(b)), + isTrue, ); + }, + ); - // Deltas should not be affected by translations at all. - expect(game.projector.unscaleVector(Vector2.zero()), Vector2.zero()); - expect(game.projector.unscaleVector(Vector2(1, 2)), Vector2(1, 2)); - }); + testWithGame( + 'children in the constructor and onLoad', + () { + return _ConstructorChildrenGame( + constructorChildren: [_IndexedComponent(1), _IndexedComponent(2)], + onLoadChildren: [_IndexedComponent(3), _IndexedComponent(4)], + ); + }, + (game) async { + game.add(_IndexedComponent(5)); + game.add(_IndexedComponent(6)); + await game.ready(); - testWithFlameGame('camera zoom & viewport translation', (game) async { - final viewport = FixedResolutionViewport(Vector2.all(100)); - game.oldCamera.viewport = viewport; - game.onGameResize(Vector2(200, 100)); - game.oldCamera.zoom = 2; - game.oldCamera.snap(); - expect(viewport.scale, 1); // no scale - expect(viewport.resizeOffset, Vector2(50, 0)); // y is unchanged - checkProjectorReversibility(game.projector); - - // (50, 0) is the top left corner of the camera - expect(game.projector.unprojectVector(Vector2(50, 0)), Vector2.zero()); - // the top left of the screen is 50 viewport units to the left, - // but applying the camera zoom on top results in 25 units - // in the game space - expect(game.projector.unprojectVector(Vector2.zero()), Vector2(-25, 0)); - // for every two units we walk to right or bottom means one game units - expect(game.projector.unprojectVector(Vector2(52, 20)), Vector2(1, 10)); - // the bottom right of the viewport is at (150, 100) on screen - // coordinates for the viewport that is walking (100, 100) in viewport - // units for the camera we need to half that so it means walking - // (50, 50) this is the bottom-right most world pixel that is painted. + expect(game.children.whereType<_IndexedComponent>().length, 6); expect( - game.projector.unprojectVector(Vector2(150, 100)), - Vector2.all(50), + game.children + .whereType<_IndexedComponent>() + .map((c) => c.index) + .isSorted((a, b) => a.compareTo(b)), + isTrue, ); + }, + ); + }); - // For deltas, we consider only the 2x zoom of the camera. - expect(game.projector.unscaleVector(Vector2.zero()), Vector2.zero()); - expect(game.projector.unscaleVector(Vector2(2, 4)), Vector2(1, 2)); - }); + group('Render box attachment', () { + testWidgets('calls on attach', (tester) async { + await tester.runAsync(() async { + var hasAttached = false; + final game = _OnAttachGame(() => hasAttached = true); - testWithFlameGame( - 'camera translation & viewport scale+translation', - (game) async { - final viewport = FixedResolutionViewport(Vector2.all(100)); - game.oldCamera.viewport = viewport; - game.onGameResize(Vector2(200, 400)); - expect(viewport.scale, 2); - expect(viewport.resizeOffset, Vector2(0, 100)); // x is unchanged - - // The camera should apply a (10, 10) translation on top of the - // viewport. - game.oldCamera.snapTo(Vector2.all(10)); - - checkProjectorReversibility(game.projector); - - // In the viewport space the top left of the screen would be - // (0, -100), which means 100 viewport units above the top left of the - // camera (10, 10) each viewport unit is twice the camera unit, so - // that is 50 above. - expect( - game.projector.unprojectVector(Vector2.zero()), - Vector2(10, -40), - ); - // The top left of the camera should be (10, 10) on game space. - // The top left of the camera should be at (0, 100) on the viewport. - expect( - game.projector.unprojectVector(Vector2(0, 100)), - Vector2(10, 10), - ); + await tester.pumpWidget(GameWidget(game: game)); + await game.toBeLoaded(); + await tester.pump(); - // For deltas, we consider only 2x scale of the viewport. - expect(game.projector.unscaleVector(Vector2.zero()), Vector2.zero()); - expect(game.projector.unscaleVector(Vector2(2, 4)), Vector2(1, 2)); - }, - ); + expect(hasAttached, isTrue); + }); + }); + }); - testWithFlameGame( - 'camera & viewport scale/zoom + translation (cancel out scaling)', - (game) async { - final viewport = FixedResolutionViewport(Vector2.all(100)); - game.oldCamera.viewport = viewport; - game.onGameResize(Vector2(200, 400)); - expect(viewport.scale, 2); - expect(viewport.resizeOffset, Vector2(0, 100)); // x is unchanged - - // The camera should apply a (10, 10) translation + a 0.5x zoom on top - // of the viewport coordinate system. - game.oldCamera.zoom = 0.5; - game.oldCamera.snapTo(Vector2.all(10)); - - checkProjectorReversibility(game.projector); - - // In the viewport space the top left of the screen would be (0, -100) - // that means 100 screen units above the top left of the camera - // (10, 10) each viewport unit is exactly one camera unit, because the - // zoom cancels out the scale. - expect( - game.projector.unprojectVector(Vector2.zero()), - Vector2(10, -90), - ); - // The top-left most visible pixel on the viewport would be at - // (0, 100) in screen coordinates. That should be the top left of the - // camera that is snapped to 10,10 by definition. - expect( - game.projector.unprojectVector(Vector2(0, 100)), - Vector2(10, 10), - ); - // Now each unit we walk in screen space means only 2 units on the - // viewport space because of the scale. however, since the camera - // applies a 1/2x zoom, each unit step equals to 1 unit on the game - // space. - expect( - game.projector.unprojectVector(Vector2(1, 100)), - Vector2(11, 10), - ); - // The last pixel of the viewport is on screen coordinates of - // (200, 300) for the camera, that should be: top left (10,10) + - // effective size (100, 100) * zoom (1/2). - expect( - game.projector.unprojectVector(Vector2(200, 300)), - Vector2.all(210), - ); + group('pauseWhenBackgrounded:', () { + testWithFlameGame('true', (game) async { + game.pauseWhenBackgrounded = true; - // For deltas, since the scale and the zoom cancel out, this should - // no-op. - expect(game.projector.unscaleVector(Vector2.zero()), Vector2.zero()); - expect(game.projector.unscaleVector(Vector2(1, 2)), Vector2(1, 2)); - }, - ); + game.lifecycleStateChange(AppLifecycleState.paused); + expect(game.paused, isTrue); - testWithFlameGame( - 'camera & viewport scale/zoom + translation', - (game) async { - final viewport = FixedResolutionViewport(Vector2.all(100)); - game.oldCamera.viewport = viewport; - game.onGameResize(Vector2(200, 400)); - expect(viewport.scale, 2); - expect(viewport.resizeOffset, Vector2(0, 100)); // x is unchanged - - // The camera should apply a (50, 0) translation + 4x zoom on top of - // the viewport coordinate system. - game.oldCamera.zoom = 4; - game.oldCamera.snapTo(Vector2(50, 0)); - - checkProjectorReversibility(game.projector); - - // In the viewport space the top left of the screen would be (0, -100) - // that means 100 screen units above the top left of the camera - // (50, 0) each screen unit is 1/2 a viewport unit each viewport unit - // is 1/4 of a camera unit so a game unit is 1/8 the screen unit. - expect( - game.projector.unprojectVector(Vector2.zero()), - Vector2(50, -100 / 8), - ); - // The top left of the camera (50, 0) should be at screen coordinates - // (0, 100). - expect( - game.projector.unprojectVector(Vector2(0, 100)), - Vector2(50, 0), - ); - // now each unit we walk on that 200 unit square on screen equals to - // 1/2 units on the same 100 unit square on the viewport space - // then, given the 4x camera zoom, each viewport unit becomes 4x a - // game unit. - expect( - game.projector.unprojectVector(Vector2(8, 108)), - Vector2(51, 1), - ); - expect( - game.projector.unprojectVector(Vector2(16, 104)), - Vector2(52, 0.5), - ); - expect( - game.projector.unprojectVector(Vector2(12, 112)), - Vector2(51.5, 1.5), - ); + game.lifecycleStateChange(AppLifecycleState.resumed); + expect(game.paused, isFalse); + }); - // Deltas only care about the effective scaling factor, which is 8x. - expect(game.projector.unscaleVector(Vector2.zero()), Vector2.zero()); - expect(game.projector.unscaleVector(Vector2(8, 16)), Vector2(1, 2)); - }, - ); + testWithFlameGame('false', (game) async { + game.pauseWhenBackgrounded = false; - testWithGame( - 'children in the constructor', - () { - return FlameGame( - world: World( - children: [_IndexedComponent(1), _IndexedComponent(2)], - ), - ); - }, - (game) async { - final world = game.world; - world.add(_IndexedComponent(3)); - world.add(_IndexedComponent(4)); - await game.ready(); + game.lifecycleStateChange(AppLifecycleState.paused); + expect(game.paused, isFalse); - expect(world.children.length, 4); - expect( - world.children - .whereType<_IndexedComponent>() - .map((c) => c.index) - .isSorted((a, b) => a.compareTo(b)), - isTrue, - ); - }, - ); + game.lifecycleStateChange(AppLifecycleState.resumed); + expect(game.paused, isFalse); + }); - testWithGame( - 'children in the constructor and onLoad', - () { - return _ConstructorChildrenGame( - constructorChildren: [_IndexedComponent(1), _IndexedComponent(2)], - onLoadChildren: [_IndexedComponent(3), _IndexedComponent(4)], + for (final startingLifecycleState in AppLifecycleState.values) { + testWidgets( + 'game is not paused on start when initially $startingLifecycleState', + (tester) async { + WidgetsBinding.instance.handleAppLifecycleStateChanged( + startingLifecycleState, ); - }, - (game) async { - game.add(_IndexedComponent(5)); - game.add(_IndexedComponent(6)); - await game.ready(); - - expect(game.children.whereType<_IndexedComponent>().length, 6); + addTearDown(() { + // Don't use [WidgetsBinding.instance.resetLifecycleState()] + // because it sets the lifecycle to null which prevents + // [game.onLoad] from running in other tests. + WidgetsBinding.instance.handleAppLifecycleStateChanged( + AppLifecycleState.resumed, + ); + }); expect( - game.children - .whereType<_IndexedComponent>() - .map((c) => c.index) - .isSorted((a, b) => a.compareTo(b)), - isTrue, + WidgetsBinding.instance.lifecycleState, + startingLifecycleState, ); - }, - ); - }); - - group('Render box attachment', () { - testWidgets('calls on attach', (tester) async { - await tester.runAsync(() async { - var hasAttached = false; - final game = _OnAttachGame(() => hasAttached = true); - await tester.pumpWidget(GameWidget(game: game)); - await game.toBeLoaded(); - await tester.pump(); + final game = FlameGame(); - expect(hasAttached, isTrue); - }); - }); - }); + final gameWidget = GameWidget(game: game); + await tester.pumpWidget(gameWidget); - group('pauseWhenBackgrounded:', () { - testWithFlameGame('true', (game) async { - game.pauseWhenBackgrounded = true; + GameWidgetState.initGameStateListener(game, () {}); - game.lifecycleStateChange(AppLifecycleState.paused); - expect(game.paused, isTrue); + expect(game.paused, isFalse); + }, + ); + } - game.lifecycleStateChange(AppLifecycleState.resumed); - expect(game.paused, isFalse); - }); + testWidgets( + 'game is paused when app is backgrounded', + (tester) async { + final game = FlameGame(); - testWithFlameGame('false', (game) async { - game.pauseWhenBackgrounded = false; + await tester.pumpWidget(GameWidget(game: game)); - game.lifecycleStateChange(AppLifecycleState.paused); - expect(game.paused, isFalse); + await game.toBeLoaded(); + await tester.pump(); - game.lifecycleStateChange(AppLifecycleState.resumed); expect(game.paused, isFalse); - }); - - for (final startingLifecycleState in AppLifecycleState.values) { - testWidgets( - 'game is not paused on start when initially $startingLifecycleState', - (tester) async { - WidgetsBinding.instance.handleAppLifecycleStateChanged( - startingLifecycleState, - ); - addTearDown(() { - // Don't use [WidgetsBinding.instance.resetLifecycleState()] - // because it sets the lifecycle to null which prevents - // [game.onLoad] from running in other tests. - WidgetsBinding.instance.handleAppLifecycleStateChanged( - AppLifecycleState.resumed, - ); - }); - expect( - WidgetsBinding.instance.lifecycleState, - startingLifecycleState, - ); - - final game = FlameGame(); - - final gameWidget = GameWidget(game: game); - await tester.pumpWidget(gameWidget); - - GameWidgetState.initGameStateListener(game, () {}); - - expect(game.paused, isFalse); - }, + WidgetsBinding.instance.handleAppLifecycleStateChanged( + AppLifecycleState.paused, ); - } - - testWidgets( - 'game is paused when app is backgrounded', - (tester) async { - final game = FlameGame(); - - await tester.pumpWidget(GameWidget(game: game)); - - await game.toBeLoaded(); - await tester.pump(); - - expect(game.paused, isFalse); - WidgetsBinding.instance.handleAppLifecycleStateChanged( - AppLifecycleState.paused, - ); - expect(game.paused, isTrue); - WidgetsBinding.instance.handleAppLifecycleStateChanged( - AppLifecycleState.resumed, - ); - expect(game.paused, isFalse); - }, - ); - }); + expect(game.paused, isTrue); + WidgetsBinding.instance.handleAppLifecycleStateChanged( + AppLifecycleState.resumed, + ); + expect(game.paused, isFalse); + }, + ); }); } diff --git a/packages/flame/test/game/game_widget/game_widget_tap_test.dart b/packages/flame/test/game/game_widget/game_widget_tap_test.dart index 67cbdfdcd42..44b92b5bb1d 100644 --- a/packages/flame/test/game/game_widget/game_widget_tap_test.dart +++ b/packages/flame/test/game/game_widget/game_widget_tap_test.dart @@ -1,3 +1,4 @@ +import 'package:flame/events.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; diff --git a/packages/flame/test/game/mixins/has_draggables_test.dart b/packages/flame/test/game/mixins/has_draggables_test.dart deleted file mode 100644 index a449d15619e..00000000000 --- a/packages/flame/test/game/mixins/has_draggables_test.dart +++ /dev/null @@ -1,111 +0,0 @@ -// ignore_for_file: deprecated_member_use_from_same_package - -import 'dart:ui'; - -import 'package:flame/components.dart'; -import 'package:flame/game.dart'; -import 'package:flame/input.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:test/test.dart'; - -class _GameWithDraggables extends FlameGame with HasDraggables { - int handledOnDragStart = 0; - int handledOnDragUpdate = 0; - int handledOnDragCancel = 0; - - @override - void onDragStart(int pointerId, DragStartInfo info) { - super.onDragStart(pointerId, info); - if (info.handled) { - handledOnDragStart++; - } - } - - @override - void onDragUpdate(int pointerId, DragUpdateInfo info) { - super.onDragUpdate(pointerId, info); - if (info.handled) { - handledOnDragUpdate++; - } - } - - @override - void onDragCancel(int pointerId) { - super.onDragCancel(pointerId); - handledOnDragCancel++; - } -} - -class _DraggableComponent extends PositionComponent with Draggable { - _DraggableComponent() : super(size: Vector2.all(100)); - - @override - bool onDragCancel() { - return true; - } - - @override - bool onDragStart(DragStartInfo info) { - info.handled = true; - return true; - } - - @override - bool onDragUpdate(DragUpdateInfo info) { - info.handled = true; - return true; - } -} - -void main() { - final withDraggables = FlameTester(_GameWithDraggables.new); - - group('HasDraggables', () { - testWithGame<_GameWithDraggables>( - 'make sure Draggables can be added to valid games', - _GameWithDraggables.new, - (game) async { - await game.ensureAdd(_DraggableComponent()); - }, - ); - - testWithFlameGame( - 'make sure Draggables cannot be added to invalid games', - (game) async { - expect( - () => game.ensureAdd(_DraggableComponent()), - failsAssert( - 'Draggable Components can only be added to a FlameGame with ' - 'HasDraggables or HasDraggablesBridge', - ), - ); - }, - ); - - withDraggables.testGameWidget( - 'drag correctly registered handled event', - setUp: (game, _) async { - await game.ensureAdd(_DraggableComponent()); - }, - verify: (game, tester) async { - await tester.dragFrom(const Offset(10, 10), const Offset(90, 90)); - expect(game.handledOnDragStart, 1); - expect(game.handledOnDragUpdate > 0, isTrue); - expect(game.handledOnDragCancel, 0); - }, - ); - - withDraggables.testGameWidget( - 'drag outside of component is not registered as handled', - setUp: (game, _) async { - await game.ensureAdd(_DraggableComponent()); - }, - verify: (game, tester) async { - await tester.dragFrom(const Offset(110, 110), const Offset(120, 120)); - expect(game.handledOnDragStart, 0); - expect(game.handledOnDragUpdate, 0); - expect(game.handledOnDragCancel, 0); - }, - ); - }); -} diff --git a/packages/flame/test/game/mixins/has_hoverables_test.dart b/packages/flame/test/game/mixins/has_hoverables_test.dart deleted file mode 100644 index 91b0d0b9b31..00000000000 --- a/packages/flame/test/game/mixins/has_hoverables_test.dart +++ /dev/null @@ -1,33 +0,0 @@ -// ignore_for_file: deprecated_member_use_from_same_package - -import 'package:flame/events.dart'; -import 'package:flame/game.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('HasHoverables', () { - testWithGame<_GameWithHoverables>('testName', _GameWithHoverables.new, - (game) async { - game.onMouseMove( - PointerHoverInfo.fromDetails( - game, - const PointerHoverEvent(), - ), - ); - - expect(game.handledOnMouseMove, 1); - }); - }); -} - -class _GameWithHoverables extends FlameGame with HasHoverables { - int handledOnMouseMove = 0; - - @override - void onMouseMove(PointerHoverInfo info) { - super.onMouseMove(info); - handledOnMouseMove++; - } -} diff --git a/packages/flame/test/game/mixins/has_tappables_test.dart b/packages/flame/test/game/mixins/has_tappables_test.dart deleted file mode 100644 index 0d9e9521d23..00000000000 --- a/packages/flame/test/game/mixins/has_tappables_test.dart +++ /dev/null @@ -1,261 +0,0 @@ -// ignore_for_file: deprecated_member_use_from_same_package - -import 'dart:ui'; - -import 'package:canvas_test/canvas_test.dart'; -import 'package:flame/components.dart'; -import 'package:flame/game.dart'; -import 'package:flame/input.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter/gestures.dart'; -import 'package:test/test.dart'; - -void main() { - final withTappables = FlameTester(_GameWithTappables.new); - - group('HasTappables', () { - testWithGame<_GameWithTappables>( - 'make sure Tappables can be added to valid games', - _GameWithTappables.new, - (game) async { - await game.ensureAdd(_TappableComponent()); - }, - ); - - testWithFlameGame( - 'make sure Tappables cannot be added to invalid games', - (game) async { - expect( - () => game.ensureAdd(_TappableComponent()), - failsAssert( - 'Tappable components can only be added to a FlameGame with ' - 'HasTappables or HasTappablesBridge', - ), - ); - }, - ); - - withTappables.testGameWidget( - 'tap correctly registered handled event', - setUp: (game, _) async { - await game.ensureAdd(_TappableComponent()); - }, - verify: (game, tester) async { - await tester.tapAt(const Offset(10, 10)); - await tester.pump(const Duration(seconds: 1)); - expect(game.handledOnTapDown, 1); - expect(game.handledOnLongTapDown, 0); - expect(game.handledOnTapUp, 1); - expect(game.handledOnTapCancel, 0); - }, - ); - - withTappables.testGameWidget( - 'long tap correctly registered handled event', - setUp: (game, _) async { - await game.ensureAdd(_TappableComponent()); - }, - verify: (game, tester) async { - await tester.longPressAt(const Offset(10, 10)); - await tester.pump(const Duration(seconds: 1)); - expect(game.handledOnTapDown, 1); - expect(game.handledOnLongTapDown, 1); - expect(game.handledOnTapUp, 1); - expect(game.handledOnTapCancel, 0); - }, - ); - - withTappables.testGameWidget( - 'tap outside of component is not registered as handled', - setUp: (game, _) async { - await game.ensureAdd(_TappableComponent()); - }, - verify: (game, tester) async { - await tester.tapAt(const Offset(110, 110)); - await tester.pump(const Duration(seconds: 1)); - expect(game.handledOnTapDown, 0); - expect(game.handledOnLongTapDown, 0); - expect(game.handledOnTapUp, 0); - expect(game.handledOnTapCancel, 0); - }, - ); - - testWithGame<_GameWithTappables>( - 'taps and resizes children', - _GameWithTappables.new, - (game) async { - final child = _MyTapComponent()..size = Vector2.all(1); - final parent = Component(); - game.add(parent..add(child)); - await game.ready(); - - game.onTapDown( - 1, - TapDownInfo.fromDetails(game, TapDownDetails()), - ); - expect(child.gameSize, Vector2(800, 600)); - expect(child.tapped, true); - }, - ); - - testWithGame<_GameWithTappables>( - 'tap on offset children', - _GameWithTappables.new, - (game) async { - final child = _MyTapComponent() - ..position = Vector2.all(100) - ..size = Vector2.all(100) - ..addToParent( - PositionComponent() - ..position = Vector2.all(100) - ..size = Vector2.all(300) - ..addToParent(game), - ); - await game.ready(); - - game.onTapDown( - 1, - TapDownInfo.fromDetails( - game, - TapDownDetails(globalPosition: const Offset(250, 250)), - ), - ); - - expect(child.gameSize, Vector2(800, 600)); - expect(child.tapped, true); - expect(child.tapTimes, 1); - }, - ); - - testWithGame<_GameWithTappables>( - 'tap on child on top of child without propagation', - _GameWithTappables.new, - (game) async { - final child1 = _MyTapComponent()..size = Vector2.all(100); - final child2 = _MyTapComponent()..size = Vector2.all(100); - final parent = PositionComponent()..size = Vector2.all(300); - game.add(parent..addAll([child1, child2])); - await game.ready(); - game.onTapDown( - 1, - TapDownInfo.fromDetails( - game, - TapDownDetails(globalPosition: const Offset(50, 50)), - ), - ); - - expect(child2.tapped, true); - expect(child2.tapTimes, 1); - expect(child1.tapped, false); - expect(child1.tapTimes, 0); - }, - ); - - testWithGame<_GameWithTappables>( - 'updates and renders children', - _GameWithTappables.new, - (game) async { - final child = _MyTapComponent(); - final parent = Component()..add(child); - game.add(parent); - await game.ready(); - - game.update(0); - expect(child.updated, true); - game.render(MockCanvas()); - expect(child.rendered, true); - }, - ); - }); -} - -class _GameWithTappables extends FlameGame with HasTappables { - int handledOnTapDown = 0; - int handledOnLongTapDown = 0; - int handledOnTapUp = 0; - int handledOnTapCancel = 0; - - @override - void onTapDown(int pointerId, TapDownInfo info) { - super.onTapDown(pointerId, info); - if (info.handled) { - handledOnTapDown++; - } - } - - @override - void onLongTapDown(int pointerId, TapDownInfo info) { - super.onLongTapDown(pointerId, info); - if (info.handled) { - handledOnLongTapDown++; - } - } - - @override - void onTapUp(int pointerId, TapUpInfo info) { - super.onTapUp(pointerId, info); - if (info.handled) { - handledOnTapUp++; - } - } - - @override - void onTapCancel(int pointerId) { - super.onTapCancel(pointerId); - handledOnTapCancel++; - } -} - -class _TappableComponent extends PositionComponent with Tappable { - _TappableComponent() : super(size: Vector2.all(100)); - - @override - bool onTapCancel() { - return true; - } - - @override - bool onTapDown(TapDownInfo info) { - info.handled = true; - return true; - } - - @override - bool onLongTapDown(TapDownInfo info) { - info.handled = true; - return true; - } - - @override - bool onTapUp(TapUpInfo info) { - info.handled = true; - return true; - } -} - -class _MyTapComponent extends PositionComponent with Tappable { - late Vector2 gameSize; - - int tapTimes = 0; - bool get tapped => tapTimes > 0; - bool updated = false; - bool rendered = false; - - @override - void update(double dt) => updated = true; - - @override - void render(Canvas canvas) => rendered = true; - - @override - void onGameResize(Vector2 gameSize) { - super.onGameResize(gameSize); - this.gameSize = gameSize; - } - - @override - bool onTapDown(_) { - ++tapTimes; - return false; - } -} diff --git a/packages/flame/test/gestures/detectors_test.dart b/packages/flame/test/gestures/detectors_test.dart index e554b390783..f4f842aa445 100644 --- a/packages/flame/test/gestures/detectors_test.dart +++ b/packages/flame/test/gestures/detectors_test.dart @@ -1,5 +1,5 @@ +import 'package:flame/events.dart' hide PointerMoveEvent; import 'package:flame/game.dart'; -import 'package:flame/input.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/flame_audio/example/lib/main.dart b/packages/flame_audio/example/lib/main.dart index 616d9c9aa4b..7ade1ae9b3c 100644 --- a/packages/flame_audio/example/lib/main.dart +++ b/packages/flame_audio/example/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; @@ -75,7 +76,7 @@ class AudioGame extends FlameGame with TapDetector { @override void onTapDown(TapDownInfo info) { - if (button.containsPoint(info.eventPosition.game)) { + if (button.containsPoint(info.eventPosition.widget)) { fireTwo(); } else { fireOne(); diff --git a/packages/flame_bloc/example/lib/src/game/game.dart b/packages/flame_bloc/example/lib/src/game/game.dart index 49b82e9f623..e70535919db 100644 --- a/packages/flame_bloc/example/lib/src/game/game.dart +++ b/packages/flame_bloc/example/lib/src/game/game.dart @@ -1,6 +1,6 @@ import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/game.dart'; -import 'package:flame/input.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_bloc_example/src/game/components/enemy.dart'; import 'package:flame_bloc_example/src/game/components/enemy_creator.dart'; @@ -78,7 +78,7 @@ class SpaceShooterGame extends FlameGame @override void onPanUpdate(DragUpdateInfo info) { - player.move(info.delta.game.x, info.delta.game.y); + player.move(info.delta.global.x, info.delta.global.y); } void increaseScore() { diff --git a/packages/flame_fire_atlas/example/lib/main.dart b/packages/flame_fire_atlas/example/lib/main.dart index 6b07c91c2b0..c5bf7ac8d1f 100644 --- a/packages/flame_fire_atlas/example/lib/main.dart +++ b/packages/flame_fire_atlas/example/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; import 'package:flame_fire_atlas/flame_fire_atlas.dart'; @@ -60,7 +61,7 @@ class ExampleGame extends FlameGame with TapDetector { removeOnFinish: true, ) ..anchor = Anchor.center - ..position = info.eventPosition.game, + ..position = info.eventPosition.widget, ); } } diff --git a/packages/flame_network_assets/example/lib/main.dart b/packages/flame_network_assets/example/lib/main.dart index 2e349cee33c..21e0eb34fe1 100644 --- a/packages/flame_network_assets/example/lib/main.dart +++ b/packages/flame_network_assets/example/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flame/components.dart'; +import 'package:flame/events.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; import 'package:flame/input.dart'; @@ -34,7 +35,7 @@ class MyGame extends FlameGame with TapDetector { ), size: Vector2(100, 50), anchor: Anchor.center, - position: info.eventPosition.game, + position: info.eventPosition.widget, ), ); } diff --git a/packages/flame_tiled/lib/src/renderable_tile_map.dart b/packages/flame_tiled/lib/src/renderable_tile_map.dart index 0383fb9734e..fff399c239d 100644 --- a/packages/flame_tiled/lib/src/renderable_tile_map.dart +++ b/packages/flame_tiled/lib/src/renderable_tile_map.dart @@ -4,7 +4,6 @@ import 'package:flame/cache.dart'; import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/flame.dart'; -import 'package:flame/game.dart'; import 'package:flame_tiled/src/flame_tsx_provider.dart'; import 'package:flame_tiled/src/mutable_transform.dart'; import 'package:flame_tiled/src/renderable_layers/group_layer.dart'; @@ -31,8 +30,8 @@ import 'package:tiled/tiled.dart'; /// - [Layer.opacity] /// - [Layer.offsetX] /// - [Layer.offsetY] -/// - [Layer.parallaxX] (only supported if [Camera] is supplied) -/// - [Layer.parallaxY] (only supported if [Camera] is supplied) +/// - [Layer.parallaxX] (only supported if a [CameraComponent] is supplied) +/// - [Layer.parallaxY] (only supported if a [CameraComponent] is supplied) /// /// {@endtemplate} class RenderableTiledMap {