-
-
Notifications
You must be signed in to change notification settings - Fork 899
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Added FollowBehavior and ability for the new Camera to follow a…
… component (#1561)
- Loading branch information
Showing
7 changed files
with
421 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
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, | ||
int? priority, | ||
}) : _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', | ||
), | ||
super(priority: priority); | ||
|
||
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) { | ||
assert( | ||
parent is PositionProvider, | ||
'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); | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
packages/flame/test/experimental/camera_component_test.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
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<FollowBehavior>()); | ||
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)); | ||
} | ||
}); | ||
|
||
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); | ||
}); | ||
}); | ||
} |
Oops, something went wrong.