Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Added anchor for the Viewport #1611

Merged
merged 11 commits into from
May 11, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class CameraComponentPropertiesExample extends FlameGame with HasTappables {
@override
void onGameResize(Vector2 size) {
super.onGameResize(size);
_camera?.viewport.anchor = Anchor.center;
_camera?.viewport.size = size * 0.7;
_camera?.viewport.position = size * 0.6;
}
Expand Down Expand Up @@ -70,7 +71,7 @@ class ViewportFrame extends Component {
final size = (parent! as Viewport).size;
canvas.drawRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(-size.x / 2, -size.y / 2, size.x, size.y),
Rect.fromLTWH(0, 0, size.x, size.y),
const Radius.circular(5),
),
paint,
Expand Down
10 changes: 8 additions & 2 deletions packages/flame/lib/src/experimental/camera_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ class CameraComponent extends Component {
@override
void renderTree(Canvas canvas) {
canvas.save();
canvas.translate(viewport.position.x, viewport.position.y);
canvas.translate(
viewport.position.x - viewport.anchor.x * viewport.size.x,
viewport.position.y - viewport.anchor.y * viewport.size.y,
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this the same as canvas.translateVector(anchor.translate(viewport.position, viewport.size)?

we could even have a function to combine an anchor and a viewport that calls anchor.translate

// Render the world through the viewport
if (world.isMounted && currentCameras.length < maxCamerasDepth) {
canvas.save();
Expand All @@ -110,7 +113,10 @@ class CameraComponent extends Component {

@override
Iterable<ComponentPointPair> componentsAtPoint(Vector2 point) sync* {
final viewportPoint = point - viewport.position;
final viewportPoint = Vector2(
point.x - viewport.position.x + viewport.anchor.x * viewport.size.x,
point.y - viewport.position.y + viewport.anchor.y * viewport.size.y,
);
Comment on lines +116 to +119
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this the same as: point - anchor.translate(viewport.position, viewport.size) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be, but I don't really understand what is anchor.translate(), and when it can be used.

if (world.isMounted && currentCameras.length < maxCamerasDepth) {
if (viewport.containsPoint(viewportPoint)) {
try {
Expand Down
7 changes: 3 additions & 4 deletions packages/flame/lib/src/experimental/fixed_size_viewport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ class FixedSizeViewport extends Viewport {

@override
bool containsLocalPoint(Vector2 point) {
return point.x.abs() <= size.x / 2 && point.y.abs() <= size.y / 2;
final x = point.x, y = point.y;
return x >= 0 && x <= size.x && y >= 0 && y <= size.y;
st-pasha marked this conversation as resolved.
Show resolved Hide resolved
}

@override
void onViewportResize() {
final x = size.x / 2;
final y = size.y / 2;
_clipRect = Rect.fromLTRB(-x, -y, x, y);
_clipRect = Rect.fromLTWH(0, 0, size.x, size.y);
}
}
1 change: 0 additions & 1 deletion packages/flame/lib/src/experimental/max_viewport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ class MaxViewport extends Viewport {
void onGameResize(Vector2 gameSize) {
super.onGameResize(gameSize);
size = gameSize;
position = gameSize / 2;
}

@override
Expand Down
4 changes: 2 additions & 2 deletions packages/flame/lib/src/experimental/viewfinder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ class Viewfinder extends Component
void onViewportResize() {
if (parent != null) {
final viewportSize = camera.viewport.size;
_transform.position.x = viewportSize.x * (_anchor.x - 0.5);
_transform.position.y = viewportSize.y * (_anchor.y - 0.5);
_transform.position.x = viewportSize.x * _anchor.x;
_transform.position.y = viewportSize.y * _anchor.y;
}
}

Expand Down
29 changes: 20 additions & 9 deletions packages/flame/lib/src/experimental/viewport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:ui';
import 'package:meta/meta.dart';
import 'package:vector_math/vector_math_64.dart';

import '../anchor.dart';
import '../components/component.dart';
import '../effects/provider_interfaces.dart';
import 'camera_component.dart';
Expand All @@ -11,17 +12,20 @@ import 'camera_component.dart';
///
/// The viewport describes a "window" through which the underlying game world
/// is observed. At the same time, the viewport is agnostic of the game world,
/// and only contain properties that describe the "window". These properties
/// are: the window's size, shape, and position on the screen.
/// and only contain properties that describe the "window" itself. These
/// properties are: the window's size, shape, and position on the screen.
///
/// There are several implementations of [Viewport], which differ by their
/// shape, and also by their behavior in response to changes to the canvas size.
/// Users may also create their own implementations.
///
/// 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
implements PositionProvider, SizeProvider {
implements AnchorProvider, PositionProvider, SizeProvider {
Viewport({Iterable<Component>? children}) : super(children: children);

/// Position of the viewport's center in the parent's coordinate frame.
/// Position of the viewport's anchor in the parent's coordinate frame.
///
/// Changing this position will move the viewport around the screen, but will
/// not affect which portion of the game world is visible. Thus, the game
Expand All @@ -32,15 +36,22 @@ abstract class Viewport extends Component
@override
set position(Vector2 value) => _position.setFrom(value);

/// Size of the viewport, i.e. the width and the height.
/// The logical "center" of the viewport.
///
/// This point will be used to establish the placement of the viewport in the
/// parent's coordinate frame.
@override
Anchor anchor = Anchor.topLeft;

/// Size of the viewport, i.e. its width and height.
///
/// This property represents the bounding box of the viewport. If the viewport
/// is rectangular in shape, then [size] describes the dimensions of that
/// rectangle. If the viewport has any other shape (for example, circular),
/// then [size] describes the dimensions of the bounding box of the viewport.
///
/// Changing the size at runtime triggers the [handleResize] event. The size
/// cannot be negative.
/// Changing the size at runtime triggers the [onViewportResize] event. The
/// size cannot be negative.
@override
Vector2 get size => _size;
final Vector2 _size = Vector2.zero();
Expand All @@ -63,8 +74,8 @@ abstract class Viewport extends Component
/// Apply clip mask to the [canvas].
///
/// The mask must be in the viewport's local coordinate system, where the
/// center of the viewport has coordinates (0, 0). The overall size of the
/// clip mask's shape must match the [size] of the viewport.
/// top left corner of the viewport has coordinates (0, 0). The overall size
/// of the clip mask's shape must match the [size] of the viewport.
///
/// This API must be implemented by all viewports.
void clip(Canvas canvas);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
87 changes: 87 additions & 0 deletions packages/flame/test/experimental/fixed_size_viewport_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import 'dart:ui';

import 'package:flame/components.dart';
import 'package:flame/experimental.dart';
import 'package:flame/game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
group('FixedSizeViewport', () {
testWithFlameGame('camera with FixedSizeViewport', (game) async {
final camera = CameraComponent(
world: World(),
viewport: FixedSizeViewport(300, 100),
);
game.addAll([camera.world, camera]);
await game.ready();

expect(camera.viewport, isA<FixedSizeViewport>());
expect(camera.viewport.size, Vector2(300, 100));
expect(camera.viewport.position, Vector2(0, 0));

game.onGameResize(Vector2(200, 200));
expect(camera.viewport.size, Vector2(300, 100));
expect(camera.viewport.position, Vector2(0, 0));
});

testWithFlameGame('hit-testing', (game) async {
final camera = CameraComponent(
world: World(),
viewport: FixedSizeViewport(400, 100),
);
game.addAll([camera.world, camera]);
await game.ready();

final viewport = camera.viewport;
expect(viewport, isA<FixedSizeViewport>());
expect(viewport.containsLocalPoint(Vector2(0, 0)), true);
expect(viewport.containsLocalPoint(Vector2(-1, -1)), false);
expect(viewport.containsLocalPoint(Vector2(400, 100)), true);
expect(viewport.containsLocalPoint(Vector2(150, 50)), true);
expect(viewport.containsLocalPoint(Vector2(-150, 50)), false);
expect(viewport.containsLocalPoint(Vector2(150, -50)), false);
expect(viewport.containsLocalPoint(Vector2(100, 20)), true);
expect(viewport.containsLocalPoint(Vector2(1000, -1000)), false);
expect(viewport.containsLocalPoint(Vector2(300, 100)), true);
});

FlameTester(() => FlameGame()).testGameWidget(
'Clipping behavior',
setUp: (game, tester) async {
final world = World();
final camera = CameraComponent(
world: world,
viewport: FixedSizeViewport(500, 200),
)
..viewport.position = Vector2(400, 300)
..viewport.anchor = Anchor.center
..viewfinder.position = Vector2.zero();
world.add(
CircleComponent(
position: Vector2.zero(),
radius: 200,
anchor: Anchor.center,
paint: Paint()..color = const Color(0x4400ffff),
),
);
world.addAll([
for (var i = -6; i <= 6; i++)
CircleComponent(
position: Vector2(i * 60.0 - i * i.abs() * 3, 0),
radius: 25 - i.abs() * 3,
anchor: Anchor.center,
)
]);
game.addAll([world, camera]);
await game.ready();
},
verify: (game, tester) async {
await expectLater(
find.byGame<FlameGame>(),
matchesGoldenFile('../_goldens/fixed_size_viewport_test_1.png'),
);
},
);
});
}
58 changes: 58 additions & 0 deletions packages/flame/test/experimental/max_viewport_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:flame/experimental.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:vector_math/vector_math_64.dart';

void main() {
group('MaxViewport', () {
testWithFlameGame('camera with MaxViewport', (game) async {
expect(game.size, Vector2(800, 600));
final world = World()..addToParent(game);
final camera = CameraComponent(world: world)..addToParent(game);
await game.ready();

expect(camera.viewport, isA<MaxViewport>());
expect(camera.viewport.size, Vector2(800, 600));
expect(camera.viewport.position, Vector2(0, 0));

game.onGameResize(Vector2(500, 200));
expect(camera.viewport.size, Vector2(500, 200));
expect(camera.viewport.position, Vector2(0, 0));
});

testWithFlameGame('hit-testing', (game) async {
final world = World()..addToParent(game);
final camera = CameraComponent(world: world)..addToParent(game);
await game.ready();

final viewport = camera.viewport;
expect(viewport, isA<MaxViewport>());
expect(viewport.containsLocalPoint(Vector2(0, 0)), true);
expect(viewport.containsLocalPoint(Vector2(100, 200)), true);
expect(viewport.containsLocalPoint(Vector2(-1, -1)), true);
expect(viewport.containsLocalPoint(Vector2(1000, -1000)), true);
});

testWithFlameGame('check that onViewportResize is called', (game) async {
final world = World();
final camera = CameraComponent(world: world, viewport: _MyMaxViewport());
game.addAll([world, camera]);
await game.ready();

final viewport = camera.viewport;
expect(viewport, isA<_MyMaxViewport>());
expect((viewport as _MyMaxViewport).onViewportResizeCalled, 1);
game.onGameResize(Vector2(200, 200));
expect(viewport.onViewportResizeCalled, 2);
});
});
}

class _MyMaxViewport extends MaxViewport {
int onViewportResizeCalled = 0;

@override
void onViewportResize() {
onViewportResizeCalled++;
}
}
2 changes: 1 addition & 1 deletion packages/flame/test/experimental/viewfinder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ void main() {
);
expect(
camera.viewport.position,
closeToVector(400 + 40 * t, 300 - 77 * t, epsilon: 1e-12),
closeToVector(40 * t, -77 * t, epsilon: 1e-12),
);
game.update(0.1);
}
Expand Down