From e852064e494e58ea2be19a5b035e09ed2e465608 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Mon, 7 Aug 2023 13:57:21 +0200 Subject: [PATCH] fix: Viewport should recieve events before the world (#2630) The viewport should receive events before the world, otherwise all huds will get the events after the world components, if there are any world components underneath them. --- .../input/joystick_advanced_example.dart | 16 +++--- .../lib/stories/input/joystick_player.dart | 1 - .../lib/src/camera/camera_component.dart | 4 +- .../test/camera/camera_component_test.dart | 6 +-- .../viewports/circular_viewport_test.dart | 10 +++- .../fixed_aspect_ratio_viewport_test.dart | 7 ++- .../component_mixins/tap_callbacks_test.dart | 51 +++++++++++++++++++ 7 files changed, 77 insertions(+), 18 deletions(-) diff --git a/examples/lib/stories/input/joystick_advanced_example.dart b/examples/lib/stories/input/joystick_advanced_example.dart index d6942f6dc1d..c1125ada89f 100644 --- a/examples/lib/stories/input/joystick_advanced_example.dart +++ b/examples/lib/stories/input/joystick_advanced_example.dart @@ -39,7 +39,7 @@ class JoystickAdvancedExample extends FlameGame with HasCollisionDetection { columns: 6, rows: 1, ); - add(ScreenHitbox()); + world.add(ScreenHitbox()..anchor = cameraComponent.viewfinder.anchor); joystick = JoystickComponent( knob: SpriteComponent( sprite: sheet.getSpriteById(1), @@ -184,13 +184,13 @@ class JoystickAdvancedExample extends FlameGame with HasCollisionDetection { ), )..add(directionText); - add(player); - add(joystick); - add(flipButton); - add(flopButton); - add(buttonComponent); - add(spriteButtonComponent); - add(shapeButton); + world.add(player); + cameraComponent.viewport.add(joystick); + cameraComponent.viewport.add(flipButton); + cameraComponent.viewport.add(flopButton); + cameraComponent.viewport.add(buttonComponent); + cameraComponent.viewport.add(spriteButtonComponent); + cameraComponent.viewport.add(shapeButton); cameraComponent.viewport.add(speedWithMargin); cameraComponent.viewport.add(directionWithMargin); } diff --git a/examples/lib/stories/input/joystick_player.dart b/examples/lib/stories/input/joystick_player.dart index 8cdccf9687e..c8d01d93bec 100644 --- a/examples/lib/stories/input/joystick_player.dart +++ b/examples/lib/stories/input/joystick_player.dart @@ -17,7 +17,6 @@ class JoystickPlayer extends SpriteComponent @override Future onLoad() async { sprite = await gameRef.loadSprite('layers/player.png'); - position = gameRef.size / 2; add(RectangleHitbox()); } diff --git a/packages/flame/lib/src/camera/camera_component.dart b/packages/flame/lib/src/camera/camera_component.dart index 7b32dffd6f0..a99af7b8c05 100644 --- a/packages/flame/lib/src/camera/camera_component.dart +++ b/packages/flame/lib/src/camera/camera_component.dart @@ -168,17 +168,17 @@ class CameraComponent extends Component { point.x - viewport.position.x + viewport.anchor.x * viewport.size.x, point.y - viewport.position.y + viewport.anchor.y * viewport.size.y, ); + yield* viewport.componentsAtPoint(viewportPoint, nestedPoints); if ((world?.isMounted ?? false) && currentCameras.length < maxCamerasDepth) { if (viewport.containsLocalPoint(viewportPoint)) { currentCameras.add(this); final worldPoint = viewfinder.transform.globalToLocal(viewportPoint); - yield* world!.componentsAtPoint(worldPoint, nestedPoints); yield* viewfinder.componentsAtPoint(worldPoint, nestedPoints); + yield* world!.componentsAtPoint(worldPoint, nestedPoints); currentCameras.removeLast(); } } - yield* viewport.componentsAtPoint(viewportPoint, nestedPoints); } /// A camera that currently performs rendering. diff --git a/packages/flame/test/camera/camera_component_test.dart b/packages/flame/test/camera/camera_component_test.dart index 99834e391c9..5ac71e97e9f 100644 --- a/packages/flame/test/camera/camera_component_test.dart +++ b/packages/flame/test/camera/camera_component_test.dart @@ -200,15 +200,15 @@ void main() { final nested = []; final it = game.componentsAtPoint(Vector2(400, 300), nested).iterator; expect(it.moveNext(), true); + expect(it.current, camera.viewport); + expect(nested, [Vector2(400, 300), Vector2(300, 200)]); + expect(it.moveNext(), true); expect(it.current, component); expect(nested, [Vector2(400, 300), Vector2(100, 50), Vector2(50, 20)]); expect(it.moveNext(), true); expect(it.current, world); expect(nested, [Vector2(400, 300), Vector2(100, 50)]); expect(it.moveNext(), true); - expect(it.current, camera.viewport); - expect(nested, [Vector2(400, 300), Vector2(300, 200)]); - expect(it.moveNext(), true); expect(it.current, game); expect(nested, [Vector2(400, 300)]); expect(it.moveNext(), false); diff --git a/packages/flame/test/camera/viewports/circular_viewport_test.dart b/packages/flame/test/camera/viewports/circular_viewport_test.dart index 8573a74999d..edc60d5ea89 100644 --- a/packages/flame/test/camera/viewports/circular_viewport_test.dart +++ b/packages/flame/test/camera/viewports/circular_viewport_test.dart @@ -95,15 +95,18 @@ void main() { testWithFlameGame('hit testing', (game) async { final world = _MyWorld(); + final viewport = CircularViewport.ellipse(80, 20) + ..position = Vector2(20, 30); final camera = CameraComponent( world: world, - viewport: CircularViewport.ellipse(80, 20)..position = Vector2(20, 30), + viewport: viewport, ); game.addAll([world, camera]); await game.ready(); bool hit(double x, double y) { - return game.componentsAtPoint(Vector2(x, y)).first == world; + final components = game.componentsAtPoint(Vector2(x, y)).toList(); + return components.first == viewport && components[1] == world; } expect(hit(10, 20), false); @@ -118,6 +121,9 @@ void main() { final nestedPoints = []; final center = Vector2(100, 50); for (final component in game.componentsAtPoint(center, nestedPoints)) { + if (component == viewport) { + continue; + } expect(component, world); expect(nestedPoints.last, Vector2.zero()); break; diff --git a/packages/flame/test/camera/viewports/fixed_aspect_ratio_viewport_test.dart b/packages/flame/test/camera/viewports/fixed_aspect_ratio_viewport_test.dart index 80d5fa16c66..b8fff2cb71a 100644 --- a/packages/flame/test/camera/viewports/fixed_aspect_ratio_viewport_test.dart +++ b/packages/flame/test/camera/viewports/fixed_aspect_ratio_viewport_test.dart @@ -71,9 +71,10 @@ void main() { testWithFlameGame('hit testing', (game) async { final world = World(); + final viewport = FixedAspectRatioViewport(aspectRatio: 1); final camera = CameraComponent( world: world, - viewport: FixedAspectRatioViewport(aspectRatio: 1), + viewport: viewport, ); game.addAll([world, camera]); game.onGameResize(Vector2(100, 200)); @@ -81,7 +82,9 @@ void main() { bool hit(double x, double y) { final components = game.componentsAtPoint(Vector2(x, y)).toList(); - return components.isNotEmpty && components.first == world; + return components.isNotEmpty && + components.first == viewport && + components[1] == world; } for (final x in [0.0, 5.0, 50.0, 100.0]) { 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 0f65e305e05..c649f8253cc 100644 --- a/packages/flame/test/events/component_mixins/tap_callbacks_test.dart +++ b/packages/flame/test/events/component_mixins/tap_callbacks_test.dart @@ -181,6 +181,57 @@ void main() { expect(game.tapCancelEvent, equals(0)); }, ); + + testWithFlameGame( + 'viewport components should get events before world', + (game) async { + final component = _TapCallbacksComponent() + ..x = 10 + ..y = 10 + ..width = 10 + ..height = 10; + final hudComponent = _TapCallbacksComponent() + ..x = 10 + ..y = 10 + ..width = 10 + ..height = 10; + final world = World(); + final cameraComponent = CameraComponent(world: world) + ..viewfinder.anchor = Anchor.topLeft; + + await game.ensureAddAll([world, cameraComponent]); + await world.ensureAdd(component); + await cameraComponent.viewport.ensureAdd(hudComponent); + final dispatcher = game.firstChild()!; + + dispatcher.onTapDown( + createTapDownEvents( + game: game, + localPosition: const Offset(12, 12), + globalPosition: const Offset(12, 12), + ), + ); + + expect(hudComponent.tapDownEvent, equals(1)); + expect(hudComponent.tapUpEvent, equals(0)); + expect(hudComponent.tapCancelEvent, equals(0)); + + expect(component.tapDownEvent, equals(0)); + expect(component.tapUpEvent, equals(0)); + expect(component.tapCancelEvent, equals(0)); + + dispatcher.onTapUp( + createTapUpEvents( + game: game, + localPosition: const Offset(12, 12), + globalPosition: const Offset(12, 12), + ), + ); + + expect(hudComponent.tapUpEvent, equals(1)); + expect(component.tapUpEvent, equals(0)); + }, + ); }); }