diff --git a/examples/lib/main.dart b/examples/lib/main.dart index 3b3935fdd..ad77b0741 100644 --- a/examples/lib/main.dart +++ b/examples/lib/main.dart @@ -3,6 +3,7 @@ import 'package:flame/flame.dart'; import 'package:flutter/material.dart'; import 'stories/animations/animations.dart'; +import 'stories/camera_and_viewport/camera_and_viewport.dart'; import 'stories/collision_detection/collision_detection.dart'; import 'stories/components/components.dart'; import 'stories/controls/controls.dart'; @@ -29,6 +30,7 @@ void main() async { addSpritesStories(dashbook); addRenderingStories(dashbook); addUtilsStories(dashbook); + addCameraAndViewportStories(dashbook); addParallaxStories(dashbook); await _setupWidgetsExample(); diff --git a/examples/lib/stories/camera_and_viewport/camera_and_viewport.dart b/examples/lib/stories/camera_and_viewport/camera_and_viewport.dart new file mode 100644 index 000000000..5a6e51cf9 --- /dev/null +++ b/examples/lib/stories/camera_and_viewport/camera_and_viewport.dart @@ -0,0 +1,51 @@ +import 'package:dashbook/dashbook.dart'; +import 'package:flame/game.dart'; + +import '../../commons/commons.dart'; +import 'follow_object.dart'; +import 'zoom.dart'; + +void addCameraAndViewportStories(Dashbook dashbook) { + dashbook.storiesOf('Camera & Viewport') + ..add( + 'Follow Object', + (context) { + return GameWidget( + game: CameraAndViewportGame( + viewportResolution: Vector2( + context.numberProperty('viewport width', 500), + context.numberProperty('viewport height', 500), + ), + ), + ); + }, + codeLink: baseLink('camera_and_viewport/follow_object.dart'), + /* + Text for instructions: + + Move around with W, A, S, D and notice how the camera follows the white square + The blue squares can also be clicked to show how the coordinate system respect + The camera transformation + */ + ) + ..add( + 'Zoom', + (context) { + return GameWidget( + game: ZoomGame( + viewportResolution: Vector2( + context.numberProperty('viewport width', 500), + context.numberProperty('viewport height', 500), + ), + ), + ); + }, + codeLink: baseLink('camera_and_viewport/zoom.dart'), + /* + Text for instructions: + + On web: use scroll to zoom in and out + On mobile: use scale gesture to zoom in and out + */ + ); +} diff --git a/examples/lib/stories/utils/camera_and_viewport.dart b/examples/lib/stories/camera_and_viewport/follow_object.dart similarity index 95% rename from examples/lib/stories/utils/camera_and_viewport.dart rename to examples/lib/stories/camera_and_viewport/follow_object.dart index c68e40c46..e6740c706 100644 --- a/examples/lib/stories/utils/camera_and_viewport.dart +++ b/examples/lib/stories/camera_and_viewport/follow_object.dart @@ -118,9 +118,15 @@ class CameraAndViewportGame extends BaseGame with KeyboardEvents, HasCollidables, HasTapableComponents { late MovableSquare square; + final Vector2 viewportResolution; + + CameraAndViewportGame({ + required this.viewportResolution, + }); + @override Future onLoad() async { - viewport = FixedResolutionViewport(Vector2(500, 500)); + viewport = FixedResolutionViewport(viewportResolution); add(Map()); add(square = MovableSquare()); diff --git a/examples/lib/stories/camera_and_viewport/zoom.dart b/examples/lib/stories/camera_and_viewport/zoom.dart new file mode 100644 index 000000000..e46196845 --- /dev/null +++ b/examples/lib/stories/camera_and_viewport/zoom.dart @@ -0,0 +1,47 @@ +import 'package:flame/components.dart'; +import 'package:flame/game.dart'; +import 'package:flame/gestures.dart'; + +class ZoomGame extends BaseGame with ScrollDetector, ScaleDetector { + final Vector2 viewportResolution; + late SpriteComponent flame; + + Vector2? lastScale; + + ZoomGame({ + required this.viewportResolution, + }); + + @override + Future onLoad() async { + final flameSprite = await loadSprite('flame.png'); + + viewport = FixedResolutionViewport(viewportResolution); + + final flameSize = Vector2(149, 211); + add( + flame = SpriteComponent( + sprite: flameSprite, + size: flameSize, + )..anchor = Anchor.center, + ); + camera.followComponent(flame); + } + + static const zoomPerScrollUnit = 0.001; + @override + void onScroll(PointerScrollInfo event) { + camera.zoom += event.scrollDelta.game.y * zoomPerScrollUnit; + } + + @override + void onScaleUpdate(ScaleUpdateInfo info) { + final scale = lastScale; + if (scale != null) { + final delta = info.scale.game - scale; + camera.zoom += delta.y; + } + + lastScale = info.scale.game; + } +} diff --git a/examples/lib/stories/controls/scroll.dart b/examples/lib/stories/controls/scroll.dart index d8e2c5d31..4cfbbf94b 100644 --- a/examples/lib/stories/controls/scroll.dart +++ b/examples/lib/stories/controls/scroll.dart @@ -14,7 +14,7 @@ class ScrollGame extends BaseGame with ScrollDetector { @override void onScroll(PointerScrollInfo event) { - target = position + event.scrollDelta * 5; + target = position + event.scrollDelta.game * 5; } @override diff --git a/examples/lib/stories/utils/utils.dart b/examples/lib/stories/utils/utils.dart index 6b3f13caf..836986102 100644 --- a/examples/lib/stories/utils/utils.dart +++ b/examples/lib/stories/utils/utils.dart @@ -2,7 +2,6 @@ import 'package:dashbook/dashbook.dart'; import 'package:flame/game.dart'; import '../../commons/commons.dart'; -import 'camera_and_viewport.dart'; import 'nine_tile_box.dart'; import 'particles.dart'; import 'timer.dart'; @@ -29,10 +28,5 @@ void addUtilsStories(Dashbook dashbook) { 'Particles', (_) => GameWidget(game: ParticlesGame()), codeLink: baseLink('utils/particles.dart'), - ) - ..add( - 'Camera & Viewport', - (_) => GameWidget(game: CameraAndViewportGame()), - codeLink: baseLink('utils/camera_and_viewport.dart'), ); } diff --git a/packages/flame/lib/src/gestures/events.dart b/packages/flame/lib/src/gestures/events.dart index ecaa03016..273f43037 100644 --- a/packages/flame/lib/src/gestures/events.dart +++ b/packages/flame/lib/src/gestures/events.dart @@ -26,6 +26,23 @@ class EventPosition { EventPosition(this._game, this._localPosition, this._globalPosition); } +/// [EventDelta] converts deltas based events to two different values (game and global). +/// +/// [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 scalled value applied all the game transformations. +class EventDelta { + final Game _game; + final Offset _delta; + + /// Scaled value relative to the game transformations + late final Vector2 game = _game.scaleVector(_delta.toVector2()); + + /// Raw value relative to the game transformations + late final Vector2 global = _delta.toVector2(); + + EventDelta(this._game, this._delta); +} + /// BaseInfo is the base class for Flame's input events. /// This base class just wraps Flutter's [raw] attribute. abstract class BaseInfo { @@ -104,8 +121,7 @@ class ForcePressInfo extends PositionInfo { } class PointerScrollInfo extends PositionInfo { - late final Vector2 scrollDelta = - _game.unscaleVector(raw.scrollDelta.toVector2()); + late final EventDelta scrollDelta = EventDelta(_game, raw.scrollDelta); PointerScrollInfo.fromDetails( Game game, @@ -135,7 +151,7 @@ class DragStartInfo extends PositionInfo { } class DragUpdateInfo extends PositionInfo { - late final Vector2 delta = _game.unscaleVector(raw.delta.toVector2()); + late final EventDelta delta = EventDelta(_game, raw.delta); DragUpdateInfo.fromDetails( Game game, @@ -166,8 +182,9 @@ class ScaleStartInfo extends PositionInfo { class ScaleEndInfo extends BaseInfo { final Game _game; - late final Vector2 velocity = - _game.unscaleVector(raw.velocity.pixelsPerSecond.toVector2()); + late final EventDelta velocity = + EventDelta(_game, raw.velocity.pixelsPerSecond); + int get pointerCount => raw.pointerCount; ScaleEndInfo.fromDetails( @@ -179,8 +196,10 @@ class ScaleEndInfo extends BaseInfo { class ScaleUpdateInfo extends PositionInfo { int get pointerCount => raw.pointerCount; double get rotation => raw.rotation; - late final Vector2 scale = - _game.unscaleVector(Vector2(raw.horizontalScale, raw.verticalScale)); + late final EventDelta scale = EventDelta( + _game, + Offset(raw.horizontalScale, raw.verticalScale), + ); ScaleUpdateInfo.fromDetails( Game game,