From 62712ab81ab9877a57cf902cc331248b07713525 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 20 Apr 2022 11:07:54 -0700 Subject: [PATCH 01/12] Added FollowBehavior --- .../lib/src/experimental/follow_behavior.dart | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 packages/flame/lib/src/experimental/follow_behavior.dart diff --git a/packages/flame/lib/src/experimental/follow_behavior.dart b/packages/flame/lib/src/experimental/follow_behavior.dart new file mode 100644 index 00000000000..96f3bace390 --- /dev/null +++ b/packages/flame/lib/src/experimental/follow_behavior.dart @@ -0,0 +1,74 @@ +import '../components/component.dart'; +import '../components/position_component.dart'; +import '../effects/provider_interfaces.dart'; +import 'viewfinder.dart'; +import 'viewport.dart'; + +/// This behavior will make the [owner] follow the [target]. +/// +/// Here, both the owner and the target are [PositionProvider]s, which could be +/// either [PositionComponent]s, or camera's [Viewfinder]/[Viewport], or any +/// other objects, including custom implementations. +/// +/// The [maxSpeed] parameter controls the maximum speed with which the [owner] +/// is allowed to move as it pursues the [target]. By default, the max speed is +/// infinite, allowing the owner to stay on top of the target all the time. +/// +/// The flags [horizontalOnly]/[verticalOnly] allow constraining the [owner]'s +/// movement to the horizontal/vertical directions respectively. +class FollowBehavior extends Component { + FollowBehavior({ + required PositionProvider target, + PositionProvider? owner, + double maxSpeed = double.infinity, + this.horizontalOnly = false, + this.verticalOnly = false, + }) : _target = target, + _owner = owner, + _speed = maxSpeed, + assert(maxSpeed > 0, 'maxSpeed must be positive: $maxSpeed'), + assert( + !(horizontalOnly && verticalOnly), + 'The behavior cannot be both horizontalOnly and verticalOnly', + ); + + PositionProvider get target => _target; + final PositionProvider _target; + + PositionProvider get owner => _owner!; + PositionProvider? _owner; + + double get maxSpeed => _speed; + final double _speed; + + final bool horizontalOnly; + final bool verticalOnly; + + @override + void onMount() { + if (_owner == null) { + if (parent is! PositionProvider) { + throw UnsupportedError( + 'Can only apply this behavior to a PositionProvider', + ); + } + _owner = parent! as PositionProvider; + } + } + + @override + void update(double dt) { + final delta = target.position - owner.position; + if (horizontalOnly) { + delta.y = 0; + } + if (verticalOnly) { + delta.x = 0; + } + final distance = delta.length; + if (distance > _speed * dt) { + delta.scale(_speed * dt / distance); + } + owner.position = delta..add(owner.position); + } +} From c0ac4912395f1735b10d39b238aa845b2f3a234e Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 20 Apr 2022 23:44:34 -0700 Subject: [PATCH 02/12] follow() function --- .../lib/src/effects/provider_interfaces.dart | 11 ++++ .../src/experimental/camera_component.dart | 50 +++++++++++++++++++ .../lib/src/experimental/follow_behavior.dart | 4 +- 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/packages/flame/lib/src/effects/provider_interfaces.dart b/packages/flame/lib/src/effects/provider_interfaces.dart index c7716183d0e..31cb492e9ad 100644 --- a/packages/flame/lib/src/effects/provider_interfaces.dart +++ b/packages/flame/lib/src/effects/provider_interfaces.dart @@ -6,6 +6,17 @@ abstract class PositionProvider { set position(Vector2 value); } +class ValuePositionProvider implements PositionProvider { + ValuePositionProvider([Vector2? position]) + : _position = position?.clone() ?? Vector2.zero(); + + @override + Vector2 get position => _position; + final Vector2 _position; + @override + set position(Vector2 value) => _position.setFrom(value); +} + /// Interface for a component that can be affected by scale effects. abstract class ScaleProvider { Vector2 get scale; diff --git a/packages/flame/lib/src/experimental/camera_component.dart b/packages/flame/lib/src/experimental/camera_component.dart index e82c1cff8d0..b1b0bf9893b 100644 --- a/packages/flame/lib/src/experimental/camera_component.dart +++ b/packages/flame/lib/src/experimental/camera_component.dart @@ -1,8 +1,11 @@ import 'dart:ui'; import 'package:meta/meta.dart'; +import 'package:vector_math/vector_math_64.dart'; import '../components/component.dart'; +import '../effects/provider_interfaces.dart'; +import 'follow_behavior.dart'; import 'max_viewport.dart'; import 'viewfinder.dart'; import 'viewport.dart'; @@ -115,4 +118,51 @@ class CameraComponent extends Component { /// This variable helps prevent infinite recursion when a camera is set to /// look at the world that contains that camera. static int maxCamerasDepth = 4; + + /// Makes the [viewfinder] follow the given [target]. + /// + /// This method adds a [FollowBehavior] to the viewfinder. If there is another + /// [FollowBehavior] currently applied to the viewfinder, it will be removed + /// first. + /// + /// Parameters [maxSpeed], [horizontalOnly] and [verticalOnly] have the same + /// meaning as in the [FollowBehavior.new] constructor. + /// + /// If [snap] is true, then the viewfinder's starting position will be set to + /// the target's current location. If [snap] is false, then the viewfinder + /// will move from its current position to the target's position at the given + /// speed. + void follow( + PositionProvider target, { + double maxSpeed = double.infinity, + bool horizontalOnly = false, + bool verticalOnly = false, + bool snap = false, + }) { + stopFollowing(); + viewfinder.add( + FollowBehavior( + target: target, + owner: viewfinder, + maxSpeed: maxSpeed, + horizontalOnly: horizontalOnly, + verticalOnly: verticalOnly, + ), + ); + if (snap) { + viewfinder.position = target.position; + } + } + + /// Removes current [FollowBehavior]s from the viewfinder, if any. + void stopFollowing() { + viewfinder.children.whereType().forEach( + (child) => child.removeFromParent(), + ); + } + + /// Moves the camera's viewfinder towards the specified [point]. + void moveTo(Vector2 point, {double speed = double.infinity}) { + follow(ValuePositionProvider(point), maxSpeed: speed); + } } diff --git a/packages/flame/lib/src/experimental/follow_behavior.dart b/packages/flame/lib/src/experimental/follow_behavior.dart index 96f3bace390..9f2c9519647 100644 --- a/packages/flame/lib/src/experimental/follow_behavior.dart +++ b/packages/flame/lib/src/experimental/follow_behavior.dart @@ -23,6 +23,7 @@ class FollowBehavior extends Component { double maxSpeed = double.infinity, this.horizontalOnly = false, this.verticalOnly = false, + int? priority, }) : _target = target, _owner = owner, _speed = maxSpeed, @@ -30,7 +31,8 @@ class FollowBehavior extends Component { assert( !(horizontalOnly && verticalOnly), 'The behavior cannot be both horizontalOnly and verticalOnly', - ); + ), + super(priority: priority); PositionProvider get target => _target; final PositionProvider _target; From fc8e023a19567fef4d22d2313b3e215b57260d02 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Thu, 21 Apr 2022 10:05:28 -0700 Subject: [PATCH 03/12] Create APositionProvider --- .../lib/src/effects/provider_interfaces.dart | 21 +++++++++++++------ .../src/experimental/camera_component.dart | 10 +++++++-- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/flame/lib/src/effects/provider_interfaces.dart b/packages/flame/lib/src/effects/provider_interfaces.dart index 31cb492e9ad..da09968fc7f 100644 --- a/packages/flame/lib/src/effects/provider_interfaces.dart +++ b/packages/flame/lib/src/effects/provider_interfaces.dart @@ -6,15 +6,24 @@ abstract class PositionProvider { set position(Vector2 value); } -class ValuePositionProvider implements PositionProvider { - ValuePositionProvider([Vector2? position]) - : _position = position?.clone() ?? Vector2.zero(); +/// This class allows constructing [PositionProvider]s on the fly, using the +/// callbacks for the position getter and setter. This class doesn't require +/// either the getter or the setter, if you do not intend to use those. +class APositionProvider implements PositionProvider { + APositionProvider({ + Vector2 Function()? getValue, + void Function(Vector2)? setValue, + }) : _getter = getValue, + _setter = setValue; + + final Vector2 Function()? _getter; + final void Function(Vector2)? _setter; @override - Vector2 get position => _position; - final Vector2 _position; + Vector2 get position => _getter!(); + @override - set position(Vector2 value) => _position.setFrom(value); + set position(Vector2 value) => _setter!(value); } /// Interface for a component that can be affected by scale effects. diff --git a/packages/flame/lib/src/experimental/camera_component.dart b/packages/flame/lib/src/experimental/camera_component.dart index b1b0bf9893b..599b3b7edce 100644 --- a/packages/flame/lib/src/experimental/camera_component.dart +++ b/packages/flame/lib/src/experimental/camera_component.dart @@ -4,6 +4,7 @@ import 'package:meta/meta.dart'; import 'package:vector_math/vector_math_64.dart'; import '../components/component.dart'; +import '../components/position_component.dart'; import '../effects/provider_interfaces.dart'; import 'follow_behavior.dart'; import 'max_viewport.dart'; @@ -121,6 +122,10 @@ class CameraComponent extends Component { /// Makes the [viewfinder] follow the given [target]. /// + /// The [target] here can be any read-only [PositionProvider]. For example, a + /// [PositionComponent] is the most common choice of target. Alternatively, + /// you can use [APositionProvider] to construct the target dynamically. + /// /// This method adds a [FollowBehavior] to the viewfinder. If there is another /// [FollowBehavior] currently applied to the viewfinder, it will be removed /// first. @@ -161,8 +166,9 @@ class CameraComponent extends Component { ); } - /// Moves the camera's viewfinder towards the specified [point]. + /// Moves the camera towards the specified world [point]. void moveTo(Vector2 point, {double speed = double.infinity}) { - follow(ValuePositionProvider(point), maxSpeed: speed); + final p = point.clone(); + follow(APositionProvider(getValue: () => p), maxSpeed: speed); } } From 9385b37562e6dd11f74d5b5a2f2a59c93b6cd343 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 23 Apr 2022 12:35:06 -0700 Subject: [PATCH 04/12] adding tests --- packages/flame/lib/experimental.dart | 1 + .../lib/src/experimental/follow_behavior.dart | 9 +- .../experimental/camera_component_test.dart | 25 ++++ .../experimental/follow_behavior_test.dart | 120 ++++++++++++++++++ 4 files changed, 150 insertions(+), 5 deletions(-) create mode 100644 packages/flame/test/experimental/camera_component_test.dart create mode 100644 packages/flame/test/experimental/follow_behavior_test.dart diff --git a/packages/flame/lib/experimental.dart b/packages/flame/lib/experimental.dart index 4b4fe0a1f4f..4387954d01b 100644 --- a/packages/flame/lib/experimental.dart +++ b/packages/flame/lib/experimental.dart @@ -15,6 +15,7 @@ export 'src/experimental/circular_viewport.dart' show CircularViewport; export 'src/experimental/fixed_aspect_ratio_viewport.dart' show FixedAspectRatioViewport; export 'src/experimental/fixed_size_viewport.dart' show FixedSizeViewport; +export 'src/experimental/follow_behavior.dart' show FollowBehavior; export 'src/experimental/max_viewport.dart' show MaxViewport; export 'src/experimental/viewfinder.dart' show Viewfinder; export 'src/experimental/viewport.dart' show Viewport; diff --git a/packages/flame/lib/src/experimental/follow_behavior.dart b/packages/flame/lib/src/experimental/follow_behavior.dart index 9f2c9519647..32de31bf73a 100644 --- a/packages/flame/lib/src/experimental/follow_behavior.dart +++ b/packages/flame/lib/src/experimental/follow_behavior.dart @@ -49,11 +49,10 @@ class FollowBehavior extends Component { @override void onMount() { if (_owner == null) { - if (parent is! PositionProvider) { - throw UnsupportedError( - 'Can only apply this behavior to a PositionProvider', - ); - } + assert( + parent is PositionProvider, + 'Can only apply this behavior to a PositionProvider', + ); _owner = parent! as PositionProvider; } } diff --git a/packages/flame/test/experimental/camera_component_test.dart b/packages/flame/test/experimental/camera_component_test.dart new file mode 100644 index 00000000000..846b831587e --- /dev/null +++ b/packages/flame/test/experimental/camera_component_test.dart @@ -0,0 +1,25 @@ + +import 'package:flame/components.dart'; +import 'package:flame/experimental.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('CameraComponent', () { + testWithFlameGame('simple camera follow', (game) async { + final world = World()..addToParent(game); + final camera = CameraComponent(world: world)..addToParent(game); + final player = PositionComponent()..addToParent(world); + camera.follow(player); + await game.ready(); + + expect(camera.viewfinder.children.length, 1); + expect(camera.viewfinder.children.first, isA()); + for (var i = 0; i < 20; i++) { + player.position.add(Vector2(i * 5.0, 20.0 - i)); + game.update(0.01); + expect(camera.viewfinder.position, closeToVector(player.x, player.y)); + } + }); + }); +} diff --git a/packages/flame/test/experimental/follow_behavior_test.dart b/packages/flame/test/experimental/follow_behavior_test.dart new file mode 100644 index 00000000000..028089047f6 --- /dev/null +++ b/packages/flame/test/experimental/follow_behavior_test.dart @@ -0,0 +1,120 @@ +import 'package:flame/components.dart'; +import 'package:flame/experimental.dart'; +import 'package:flame/src/effects/provider_interfaces.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('FollowBehavior', () { + test('basic properties', () { + final target = PositionComponent(); + final owner = PositionComponent(); + final behavior = FollowBehavior(target: target, owner: owner); + expect(behavior.target, target); + expect(behavior.owner, owner); + expect(behavior.maxSpeed, double.infinity); + expect(behavior.horizontalOnly, false); + expect(behavior.verticalOnly, false); + }); + + test('errors', () { + expect( + () => FollowBehavior(target: PositionComponent(), maxSpeed: 0), + failsAssert('maxSpeed must be positive: 0.0'), + ); + expect( + () => FollowBehavior(target: PositionComponent(), maxSpeed: -2.45), + failsAssert('maxSpeed must be positive: -2.45'), + ); + expect( + () => FollowBehavior( + target: PositionComponent(), + horizontalOnly: true, + verticalOnly: true, + ), + failsAssert( + 'The behavior cannot be both horizontalOnly and verticalOnly', + ), + ); + }); + + testWithFlameGame('parent is not position provider', (game) async { + final target = PositionComponent()..addToParent(game); + final component = Component()..addToParent(game); + await game.ready(); + + expect( + () async { + component.add(FollowBehavior(target: target)); + await game.ready(); + }, + failsAssert('Can only apply this behavior to a PositionProvider'), + ); + }); + + testWithFlameGame('custom position provider', (game) async { + final target = PositionComponent() + ..position = Vector2(3, 100) + ..addToParent(game); + final component = Component()..addToParent(game); + await game.ready(); + + final followTarget = Vector2(3, 1); + component.add( + FollowBehavior( + target: target, + owner: APositionProvider( + getValue: () => followTarget, + setValue: followTarget.setFrom, + ), + maxSpeed: 1, + ), + ); + await game.ready(); + + const dt = 0.11; + for (var i = 0; i < 20; i++) { + expect(followTarget, closeToVector(3, 1 + i * dt, epsilon: 1e-14)); + game.update(dt); + } + }); + + testWithFlameGame('simple follow', (game) async { + final target = PositionComponent()..addToParent(game); + final pursuer = PositionComponent() + ..add(FollowBehavior(target: target)) + ..addToParent(game); + await game.ready(); + + for (var i = 0; i < 10; i++) { + target.position = Vector2.random()..scale(1000); + game.update(0.01); + expect( + pursuer.position, + closeToVector(target.position.x, target.position.y, epsilon: 1e-12), + ); + } + }); + + testWithFlameGame('follow with max speed', (game) async { + const dt = 0.013; + const speed = 587.0; + final target = PositionComponent() + ..position = Vector2(600, 800) + ..addToParent(game); + final pursuer = PositionComponent() + ..add(FollowBehavior(target: target, maxSpeed: speed)) + ..addToParent(game); + await game.ready(); + + for (var i = 0; i < 100; i++) { + final distance = speed * i * dt; + expect( + pursuer.position, + closeToVector(distance * 0.6, distance * 0.8, epsilon: 1e-12), + ); + game.update(dt); + } + }); + }); +} From 6e56972381d4f2ddc3922657c9d92eaa3e303f07 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 24 Apr 2022 00:19:26 -0700 Subject: [PATCH 05/12] updated tests --- .../experimental/camera_component_test.dart | 46 +++++++++++++++++++ .../experimental/follow_behavior_test.dart | 32 +++++++++++++ 2 files changed, 78 insertions(+) diff --git a/packages/flame/test/experimental/camera_component_test.dart b/packages/flame/test/experimental/camera_component_test.dart index 846b831587e..b69ec4848c1 100644 --- a/packages/flame/test/experimental/camera_component_test.dart +++ b/packages/flame/test/experimental/camera_component_test.dart @@ -21,5 +21,51 @@ void main() { expect(camera.viewfinder.position, closeToVector(player.x, player.y)); } }); + + testWithFlameGame('follow with snap', (game) async { + final world = World() ..addToParent(game); + final player = PositionComponent() + ..position = Vector2(100, 100) + ..addToParent(world); + final camera = CameraComponent(world: world) + ..follow(player, maxSpeed: 1, snap: true) + ..addToParent(game); + await game.ready(); + + expect(camera.viewfinder.position, Vector2(100, 100)); + }); + + testWithFlameGame('moveTo', (game) async { + final world = World()..addToParent(game); + final camera = CameraComponent(world: world)..addToParent(game); + await game.ready(); + + final point = Vector2(1000, 2000); + camera.moveTo(point); + game.update(0); + expect(camera.viewfinder.position, Vector2(1000, 2000)); + // updating [point] doesn't affect the camera's target + point.x = 0; + game.update(1); + expect(camera.viewfinder.position, Vector2(1000, 2000)); + }); + + testWithFlameGame('moveTo x 2', (game) async { + final world = World()..addToParent(game); + final camera = CameraComponent(world: world)..addToParent(game); + await game.ready(); + + camera.moveTo(Vector2(100, 0), speed: 5); + for (var i = 0; i < 10; i++) { + expect(camera.viewfinder.position, closeToVector(0.5 * i, 0)); + game.update(0.1); + } + camera.moveTo(Vector2(5, 200), speed: 10); + for (var i = 0; i < 10; i++) { + expect(camera.viewfinder.position, closeToVector(5, 1.0 * i)); + game.update(0.1); + } + expect(camera.viewfinder.children.length, 1); + }); }); } diff --git a/packages/flame/test/experimental/follow_behavior_test.dart b/packages/flame/test/experimental/follow_behavior_test.dart index 028089047f6..11f556be9d4 100644 --- a/packages/flame/test/experimental/follow_behavior_test.dart +++ b/packages/flame/test/experimental/follow_behavior_test.dart @@ -116,5 +116,37 @@ void main() { game.update(dt); } }); + + testWithFlameGame('horizontal-only follow', (game) async { + final target = PositionComponent(position: Vector2(20, 10)); + final pursuer = PositionComponent(); + pursuer.add( + FollowBehavior(target: target, horizontalOnly: true, maxSpeed: 1), + ); + game.addAll([target, pursuer]); + await game.ready(); + + for (var i = 0; i < 10; i++) { + expect(pursuer.position.x, i); + expect(pursuer.position.y, 0); + game.update(1); + } + }); + + testWithFlameGame('vertical-only follow', (game) async { + final target = PositionComponent(position: Vector2(20, 100)); + final pursuer = PositionComponent(); + pursuer.add( + FollowBehavior(target: target, verticalOnly: true, maxSpeed: 1), + ); + game.addAll([target, pursuer]); + await game.ready(); + + for (var i = 0; i < 10; i++) { + expect(pursuer.position.x, 0); + expect(pursuer.position.y, i); + game.update(1); + } + }); }); } From 89885e2c204569af4b85f9564aa2ad4d4687f6ea Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 24 Apr 2022 01:12:37 -0700 Subject: [PATCH 06/12] added docs --- doc/flame/camera_component.md | 49 ++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/doc/flame/camera_component.md b/doc/flame/camera_component.md index aebfb99ffca..8f69241e1b6 100644 --- a/doc/flame/camera_component.md +++ b/doc/flame/camera_component.md @@ -12,17 +12,17 @@ one camera simultaneously. In order to understand how this approach works, imagine that your game world is an entity that exists _somewhere_ independently from your application. Imagine that your game is merely a window through which you can look into that world. -That you can close that window at any moment, and the game world would still be +That you can close that window at any moment, and the game world would still be there. Or, on the contrary, you can open multiple windows that all look at the same world (or different worlds) at the same time. With this mindset, we can now understand how camera-as-a-component works. -First, there is the [](#world) class, which contains all components that are -inside your game world. The `World` component can be mounted anywhere, for +First, there is the [](#world) class, which contains all components that are +inside your game world. The `World` component can be mounted anywhere, for example at the root of your game class. -Then, a [](#cameracomponent) class that "looks at" the `World`. The +Then, a [](#cameracomponent) class that "looks at" the `World`. The `CameraComponent` has a `Viewport` and a `Viewfinder` inside, allowing both the flexibility of rendering the world at any place on the screen, and also control the viewing location and angle. @@ -44,9 +44,9 @@ then mount B. ## CameraComponent -This is a component through which a `World` is rendered. It requires a +This is a component through which a `World` is rendered. It requires a reference to a `World` instance during construction; however later the target -world can be replaced with another one. Multiple cameras can observe the same +world can be replaced with another one. Multiple cameras can observe the same world at the same time. A `CameraComponent` has two other components inside: a [](#viewport) and a @@ -79,7 +79,7 @@ The following viewports are available: - `MaxViewport` (default) -- this viewport expands to the maximum size allowed by the game, i.e. it will be equal to the size of the game canvas. - `FixedSizeViewport` -- a simple rectangular viewport with predefined size. - - `FixedAspectRatioViewport` -- a rectangular viewport which expands to fit + - `FixedAspectRatioViewport` -- a rectangular viewport which expands to fit into the game canvas, but preserving its aspect ratio. - `CircularViewport` -- a viewport in the shape of a circle, fixed size. @@ -102,6 +102,37 @@ were part of the world (but on top). It is more useful to add behavioral components to the viewfinder, for example [](effects.md) or other controllers. +## Camera controls + +There are several ways to modify camera's settings at runtime: + + 1. Do it manually. You can always override the `CameraComponent.update()` + method (or the same method on the viewfinder or viewport) and within it + change the viewfinder's position or zoom as you see fit. This approach may + be viable in some circumstances, but in general it is not recommended. + + 2. Apply effects and/or behaviors to the camera's `Viewfinder` or `Viewport`. + The effects and behaviors are special kinds of components whose purpose is + to modify over time some property of a component that they attach to. + + 3. Use special camera functions such as `follow()` and `moveTo()`. Under the + hood, this approach uses the same effects/behaviors as in (2). + +Camera has several methods for controlling its behavior: + + - `Camera.follow()` will force the camera to follow the provided target. + Optionally you can limit the maximum speed of movement of the camera, or + allow it to move horizontally/vertically only. + + - `Camera.stopFollowing()` will undo the effect of the previous call and stop + the camera at its current position. + + - `Camera.moveTo()` can be used to move the camera to the designated point on + the world map. If the camera was already following another component or + moving towards another point, those behaviors would be automatically + cancelled. + + ## Comparison to the traditional camera Compared to the normal [Camera](camera_and_viewport.md), the `CameraComponent` @@ -113,8 +144,8 @@ Pros: - 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; - - (NYI) Effects can be applied either to the viewport, or to the viewfinder; - - (NYI) More flexible camera controllers. + - Effects can be applied either to the viewport, or to the viewfinder; + - More flexible camera controllers. Cons (we are planning to eliminate these in the near future): - Camera controls are not yet implemented; From 1062d87b92355fce80fc73b483c47a5abf83dfc7 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 24 Apr 2022 11:11:25 -0700 Subject: [PATCH 07/12] format --- packages/flame/test/experimental/camera_component_test.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/flame/test/experimental/camera_component_test.dart b/packages/flame/test/experimental/camera_component_test.dart index b69ec4848c1..2d53eb62007 100644 --- a/packages/flame/test/experimental/camera_component_test.dart +++ b/packages/flame/test/experimental/camera_component_test.dart @@ -1,4 +1,3 @@ - import 'package:flame/components.dart'; import 'package:flame/experimental.dart'; import 'package:flame_test/flame_test.dart'; @@ -23,7 +22,7 @@ void main() { }); testWithFlameGame('follow with snap', (game) async { - final world = World() ..addToParent(game); + final world = World()..addToParent(game); final player = PositionComponent() ..position = Vector2(100, 100) ..addToParent(world); From e302905b453f44fc744a33e6e6990083103a2e2e Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 24 Apr 2022 23:30:39 -0700 Subject: [PATCH 08/12] rename APositionProvider -> PositionProviderImpl --- packages/flame/lib/src/effects/provider_interfaces.dart | 4 ++-- packages/flame/lib/src/experimental/camera_component.dart | 4 ++-- packages/flame/test/experimental/follow_behavior_test.dart | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/flame/lib/src/effects/provider_interfaces.dart b/packages/flame/lib/src/effects/provider_interfaces.dart index f9c3f7857bc..e58bdb2bf09 100644 --- a/packages/flame/lib/src/effects/provider_interfaces.dart +++ b/packages/flame/lib/src/effects/provider_interfaces.dart @@ -11,8 +11,8 @@ abstract class PositionProvider { /// This class allows constructing [PositionProvider]s on the fly, using the /// callbacks for the position getter and setter. This class doesn't require /// either the getter or the setter, if you do not intend to use those. -class APositionProvider implements PositionProvider { - APositionProvider({ +class PositionProviderImpl implements PositionProvider { + PositionProviderImpl({ Vector2 Function()? getValue, void Function(Vector2)? setValue, }) : _getter = getValue, diff --git a/packages/flame/lib/src/experimental/camera_component.dart b/packages/flame/lib/src/experimental/camera_component.dart index 599b3b7edce..5f17658bfd6 100644 --- a/packages/flame/lib/src/experimental/camera_component.dart +++ b/packages/flame/lib/src/experimental/camera_component.dart @@ -124,7 +124,7 @@ class CameraComponent extends Component { /// /// The [target] here can be any read-only [PositionProvider]. For example, a /// [PositionComponent] is the most common choice of target. Alternatively, - /// you can use [APositionProvider] to construct the target dynamically. + /// you can use [PositionProviderImpl] to construct the target dynamically. /// /// This method adds a [FollowBehavior] to the viewfinder. If there is another /// [FollowBehavior] currently applied to the viewfinder, it will be removed @@ -169,6 +169,6 @@ class CameraComponent extends Component { /// Moves the camera towards the specified world [point]. void moveTo(Vector2 point, {double speed = double.infinity}) { final p = point.clone(); - follow(APositionProvider(getValue: () => p), maxSpeed: speed); + follow(PositionProviderImpl(getValue: () => p), maxSpeed: speed); } } diff --git a/packages/flame/test/experimental/follow_behavior_test.dart b/packages/flame/test/experimental/follow_behavior_test.dart index 11f556be9d4..e3dd196d325 100644 --- a/packages/flame/test/experimental/follow_behavior_test.dart +++ b/packages/flame/test/experimental/follow_behavior_test.dart @@ -63,7 +63,7 @@ void main() { component.add( FollowBehavior( target: target, - owner: APositionProvider( + owner: PositionProviderImpl( getValue: () => followTarget, setValue: followTarget.setFrom, ), From 7dfaf1ba517801716c3a01094132f1891c099a43 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Mon, 25 Apr 2022 00:55:07 -0700 Subject: [PATCH 09/12] stopFollowing -> stop --- .../lib/src/experimental/camera_component.dart | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/flame/lib/src/experimental/camera_component.dart b/packages/flame/lib/src/experimental/camera_component.dart index 5f17658bfd6..0012f954d17 100644 --- a/packages/flame/lib/src/experimental/camera_component.dart +++ b/packages/flame/lib/src/experimental/camera_component.dart @@ -5,7 +5,9 @@ import 'package:vector_math/vector_math_64.dart'; import '../components/component.dart'; import '../components/position_component.dart'; +import '../effects/move_effect.dart'; import '../effects/provider_interfaces.dart'; +import '../effects/rotate_effect.dart'; import 'follow_behavior.dart'; import 'max_viewport.dart'; import 'viewfinder.dart'; @@ -144,7 +146,7 @@ class CameraComponent extends Component { bool verticalOnly = false, bool snap = false, }) { - stopFollowing(); + stop(); viewfinder.add( FollowBehavior( target: target, @@ -159,11 +161,15 @@ class CameraComponent extends Component { } } - /// Removes current [FollowBehavior]s from the viewfinder, if any. - void stopFollowing() { - viewfinder.children.whereType().forEach( - (child) => child.removeFromParent(), - ); + /// Removes all movement effects or behaviors from the viewfinder. + void stop() { + viewfinder.children.forEach((child) { + if (child is FollowBehavior || + child is MoveEffect || + child is RotateEffect) { + child.removeFromParent(); + } + }); } /// Moves the camera towards the specified world [point]. From 400d8921475cd4ec85aa67d754b14abd3d15f326 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Mon, 25 Apr 2022 01:04:21 -0700 Subject: [PATCH 10/12] Use an effect for CameraComponent.moveTo() --- .../flame/lib/src/experimental/camera_component.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/flame/lib/src/experimental/camera_component.dart b/packages/flame/lib/src/experimental/camera_component.dart index 0012f954d17..cb1354951a9 100644 --- a/packages/flame/lib/src/experimental/camera_component.dart +++ b/packages/flame/lib/src/experimental/camera_component.dart @@ -5,7 +5,9 @@ import 'package:vector_math/vector_math_64.dart'; import '../components/component.dart'; import '../components/position_component.dart'; +import '../effects/controllers/effect_controller.dart'; import '../effects/move_effect.dart'; +import '../effects/move_to_effect.dart'; import '../effects/provider_interfaces.dart'; import '../effects/rotate_effect.dart'; import 'follow_behavior.dart'; @@ -164,9 +166,7 @@ class CameraComponent extends Component { /// Removes all movement effects or behaviors from the viewfinder. void stop() { viewfinder.children.forEach((child) { - if (child is FollowBehavior || - child is MoveEffect || - child is RotateEffect) { + if (child is FollowBehavior || child is MoveEffect) { child.removeFromParent(); } }); @@ -174,7 +174,9 @@ class CameraComponent extends Component { /// Moves the camera towards the specified world [point]. void moveTo(Vector2 point, {double speed = double.infinity}) { - final p = point.clone(); - follow(PositionProviderImpl(getValue: () => p), maxSpeed: speed); + stop(); + viewfinder.add( + MoveToEffect(point, EffectController(speed: speed)), + ); } } From f02383e4112010086d44ef7e82d8de054e2345fb Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Mon, 25 Apr 2022 01:07:02 -0700 Subject: [PATCH 11/12] docs --- doc/flame/camera_component.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/flame/camera_component.md b/doc/flame/camera_component.md index 8f69241e1b6..2ce5f941f1b 100644 --- a/doc/flame/camera_component.md +++ b/doc/flame/camera_component.md @@ -124,8 +124,8 @@ Camera has several methods for controlling its behavior: Optionally you can limit the maximum speed of movement of the camera, or allow it to move horizontally/vertically only. - - `Camera.stopFollowing()` will undo the effect of the previous call and stop - the camera at its current position. + - `Camera.stop()` will undo the effect of the previous call and stop the camera + at its current position. - `Camera.moveTo()` can be used to move the camera to the designated point on the world map. If the camera was already following another component or From eac284d5f2f6a505b95b9ef6264b384b0938f826 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Mon, 25 Apr 2022 01:35:21 -0700 Subject: [PATCH 12/12] remove unused import --- packages/flame/lib/src/experimental/camera_component.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/flame/lib/src/experimental/camera_component.dart b/packages/flame/lib/src/experimental/camera_component.dart index cb1354951a9..ca0dddd3706 100644 --- a/packages/flame/lib/src/experimental/camera_component.dart +++ b/packages/flame/lib/src/experimental/camera_component.dart @@ -9,7 +9,6 @@ import '../effects/controllers/effect_controller.dart'; import '../effects/move_effect.dart'; import '../effects/move_to_effect.dart'; import '../effects/provider_interfaces.dart'; -import '../effects/rotate_effect.dart'; import 'follow_behavior.dart'; import 'max_viewport.dart'; import 'viewfinder.dart';