diff --git a/examples/lib/stories/animations/benchmark_example.dart b/examples/lib/stories/animations/benchmark_example.dart index 04d2c28e0..7f141592c 100644 --- a/examples/lib/stories/animations/benchmark_example.dart +++ b/examples/lib/stories/animations/benchmark_example.dart @@ -21,16 +21,16 @@ starts to drop in FPS, this is without any sprite batching and such. Future onLoad() async { await camera.viewport.addAll([ FpsTextComponent( - position: size - Vector2(0, 50), + position: size - Vector2(10, 50), anchor: Anchor.bottomRight, ), emberCounter = TextComponent( - position: size - Vector2(0, 25), + position: size - Vector2(10, 25), anchor: Anchor.bottomRight, priority: 1, ), ]); - world.add(Ember(size: emberSize, position: size / 2)); + world.add(Ember(size: emberSize)); children.register(); } diff --git a/examples/lib/stories/camera_and_viewport/camera_and_viewport.dart b/examples/lib/stories/camera_and_viewport/camera_and_viewport.dart index a77fe252e..5c10b71d1 100644 --- a/examples/lib/stories/camera_and_viewport/camera_and_viewport.dart +++ b/examples/lib/stories/camera_and_viewport/camera_and_viewport.dart @@ -2,6 +2,7 @@ import 'package:dashbook/dashbook.dart'; import 'package:examples/commons/commons.dart'; import 'package:examples/stories/camera_and_viewport/camera_component_example.dart'; import 'package:examples/stories/camera_and_viewport/camera_component_properties_example.dart'; +import 'package:examples/stories/camera_and_viewport/camera_follow_and_world_bounds.dart'; import 'package:examples/stories/camera_and_viewport/coordinate_systems_example.dart'; import 'package:examples/stories/camera_and_viewport/fixed_resolution_example.dart'; import 'package:examples/stories/camera_and_viewport/follow_component_example.dart'; @@ -74,5 +75,12 @@ void addCameraAndViewportStories(Dashbook dashbook) { 'camera_and_viewport/camera_component_properties_example.dart', ), info: CameraComponentPropertiesExample.description, + ) + ..add( + 'Follow and World bounds', + (_) => GameWidget(game: CameraFollowAndWorldBoundsExample()), + codeLink: + baseLink('camera_and_viewport/camera_follow_and_world_bounds.dart'), + info: CameraFollowAndWorldBoundsExample.description, ); } diff --git a/examples/lib/stories/experimental/camera_follow_and_world_bounds.dart b/examples/lib/stories/camera_and_viewport/camera_follow_and_world_bounds.dart similarity index 100% rename from examples/lib/stories/experimental/camera_follow_and_world_bounds.dart rename to examples/lib/stories/camera_and_viewport/camera_follow_and_world_bounds.dart diff --git a/examples/lib/stories/camera_and_viewport/fixed_resolution_example.dart b/examples/lib/stories/camera_and_viewport/fixed_resolution_example.dart index e38cd9b87..d01075390 100644 --- a/examples/lib/stories/camera_and_viewport/fixed_resolution_example.dart +++ b/examples/lib/stories/camera_and_viewport/fixed_resolution_example.dart @@ -14,22 +14,18 @@ class FixedResolutionExample extends FlameGame Resize the window or change device orientation to see the difference. '''; - final Vector2 viewportResolution; - FixedResolutionExample({ - required this.viewportResolution, - }); + required Vector2 viewportResolution, + }) : super( + camera: CameraComponent.withFixedResolution( + width: viewportResolution.x, + height: viewportResolution.y, + ), + ); @override Future onLoad() async { final flameSprite = await loadSprite('layers/player.png'); - final world = World(); - final cameraComponent = CameraComponent.withFixedResolution( - width: viewportResolution.x, - height: viewportResolution.y, - world: world, - ); - addAll([world, cameraComponent]); world.add(Background()); world.add( diff --git a/examples/lib/stories/camera_and_viewport/follow_component_example.dart b/examples/lib/stories/camera_and_viewport/follow_component_example.dart index 93ced007f..949638b69 100644 --- a/examples/lib/stories/camera_and_viewport/follow_component_example.dart +++ b/examples/lib/stories/camera_and_viewport/follow_component_example.dart @@ -23,20 +23,19 @@ class FollowComponentExample extends FlameGame respects the camera transformation. '''; - FollowComponentExample({required this.viewportResolution}); + FollowComponentExample({required this.viewportResolution}) + : super( + camera: CameraComponent.withFixedResolution( + width: viewportResolution.x, + height: viewportResolution.y, + ), + ); late MovableEmber ember; final Vector2 viewportResolution; @override Future onLoad() async { - final world = World(); - camera = CameraComponent.withFixedResolution( - width: viewportResolution.x, - height: viewportResolution.y, - world: world, - ); - world.add(Map()); world.add(ember = MovableEmber()); camera.setBounds(Map.bounds); @@ -58,6 +57,8 @@ class MovableEmber extends Ember final Vector2 velocity = Vector2.zero(); late final TextComponent positionText; late final Vector2 textPosition; + late final maxPosition = Vector2.all(Map.size - size.x / 2); + late final minPosition = -maxPosition; MovableEmber() : super(priority: 2); @@ -78,6 +79,7 @@ class MovableEmber extends Ember super.update(dt); final deltaPosition = velocity * (speed * dt); position.add(deltaPosition); + position.clamp(minPosition, maxPosition); positionText.text = '(${x.toInt()}, ${y.toInt()})'; } diff --git a/examples/lib/stories/camera_and_viewport/zoom_example.dart b/examples/lib/stories/camera_and_viewport/zoom_example.dart index 1ab206407..fd5237d1e 100644 --- a/examples/lib/stories/camera_and_viewport/zoom_example.dart +++ b/examples/lib/stories/camera_and_viewport/zoom_example.dart @@ -9,24 +9,20 @@ class ZoomExample extends FlameGame with ScrollDetector, ScaleDetector { '''; ZoomExample({ - required this.viewportResolution, - }); - - final Vector2 viewportResolution; - late SpriteComponent flame; + required Vector2 viewportResolution, + }) : super( + camera: CameraComponent.withFixedResolution( + width: viewportResolution.x, + height: viewportResolution.y, + ), + ); @override Future onLoad() async { final flameSprite = await loadSprite('flame.png'); - camera = CameraComponent.withFixedResolution( - world: world, - width: viewportResolution.x, - height: viewportResolution.y, - ); - world.add( - flame = SpriteComponent( + SpriteComponent( sprite: flameSprite, size: Vector2(149, 211), )..anchor = Anchor.center, diff --git a/examples/lib/stories/collision_detection/quadtree_example.dart b/examples/lib/stories/collision_detection/quadtree_example.dart index 9ee02b82e..df2834531 100644 --- a/examples/lib/stories/collision_detection/quadtree_example.dart +++ b/examples/lib/stories/collision_detection/quadtree_example.dart @@ -204,23 +204,24 @@ class Player extends SpriteComponent required super.position, required super.size, required super.priority, - }) { - Sprite.load( - 'retro_tiles.png', - srcSize: Vector2.all(tileSize), - srcPosition: Vector2(tileSize * 3, tileSize), - ).then((value) { - sprite = value; - }); + }); - add(hitbox); - } - - final hitbox = RectangleHitbox(); bool canMoveLeft = true; bool canMoveRight = true; bool canMoveTop = true; bool canMoveBottom = true; + final hitbox = RectangleHitbox(); + + @override + Future onLoad() async { + sprite = await Sprite.load( + 'retro_tiles.png', + srcSize: Vector2.all(tileSize), + srcPosition: Vector2(tileSize * 3, tileSize), + ); + + add(hitbox); + } @override void onCollisionStart( diff --git a/examples/lib/stories/components/keys_example.dart b/examples/lib/stories/components/keys_example.dart index ebcddce02..8373039e4 100644 --- a/examples/lib/stories/components/keys_example.dart +++ b/examples/lib/stories/components/keys_example.dart @@ -36,9 +36,9 @@ class _KeysExampleWidgetState extends State { child: GameWidget(game: game), ), Positioned( - left: 0, + left: 20, top: 222, - width: 340, + width: 300, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/examples/lib/stories/experimental/experimental.dart b/examples/lib/stories/experimental/experimental.dart index 534f1ebdd..c2cb7cf60 100644 --- a/examples/lib/stories/experimental/experimental.dart +++ b/examples/lib/stories/experimental/experimental.dart @@ -1,21 +1,13 @@ import 'package:dashbook/dashbook.dart'; import 'package:examples/commons/commons.dart'; -import 'package:examples/stories/experimental/camera_follow_and_world_bounds.dart'; import 'package:examples/stories/experimental/shapes.dart'; import 'package:flame/game.dart'; void addExperimentalStories(Dashbook dashbook) { - dashbook.storiesOf('Experimental') - ..add( - 'Shapes', - (_) => GameWidget(game: ShapesExample()), - codeLink: baseLink('experimental/shapes.dart'), - info: ShapesExample.description, - ) - ..add( - 'Follow and World bounds', - (_) => GameWidget(game: CameraFollowAndWorldBoundsExample()), - codeLink: baseLink('experimental/camera_follow_and_world_bounds.dart'), - info: CameraFollowAndWorldBoundsExample.description, - ); + dashbook.storiesOf('Experimental').add( + 'Shapes', + (_) => GameWidget(game: ShapesExample()), + codeLink: baseLink('experimental/shapes.dart'), + info: ShapesExample.description, + ); } diff --git a/examples/lib/stories/input/gesture_hitboxes_example.dart b/examples/lib/stories/input/gesture_hitboxes_example.dart index 8e96b11df..84a657fc7 100644 --- a/examples/lib/stories/input/gesture_hitboxes_example.dart +++ b/examples/lib/stories/input/gesture_hitboxes_example.dart @@ -27,22 +27,21 @@ class _GestureHitboxesWorld extends World with TapCallbacks { final shapeSize = Vector2.all(100) + Vector2.all(50.0).scaled(_rng.nextDouble()); final shapeAngle = _rng.nextDouble() * 6; - final hitbox = () { - switch (shapeType) { - case Shapes.circle: - return CircleHitbox(); - case Shapes.rectangle: - return RectangleHitbox(); - case Shapes.polygon: - final points = [ - -Vector2.random(_rng), - Vector2.random(_rng)..x *= -1, - Vector2.random(_rng), - Vector2.random(_rng)..y *= -1, - ]; - return PolygonHitbox.relative(points, parentSize: shapeSize); - } - }(); + ShapeHitbox hitbox; + switch (shapeType) { + case Shapes.circle: + hitbox = CircleHitbox(); + case Shapes.rectangle: + hitbox = RectangleHitbox(); + case Shapes.polygon: + final points = [ + -Vector2.random(_rng), + Vector2.random(_rng)..x *= -1, + Vector2.random(_rng), + Vector2.random(_rng)..y *= -1, + ]; + hitbox = PolygonHitbox.relative(points, parentSize: shapeSize); + } return MyShapeComponent( hitbox: hitbox, position: position, diff --git a/examples/lib/stories/input/joystick_advanced_example.dart b/examples/lib/stories/input/joystick_advanced_example.dart index 4b18fb910..1eb33e9af 100644 --- a/examples/lib/stories/input/joystick_advanced_example.dart +++ b/examples/lib/stories/input/joystick_advanced_example.dart @@ -33,7 +33,7 @@ class JoystickAdvancedExample extends FlameGame with HasCollisionDetection { columns: 6, rows: 1, ); - world.add(ScreenHitbox()..anchor = camera.viewfinder.anchor); + world.add(ScreenHitbox()); joystick = JoystickComponent( knob: SpriteComponent( sprite: sheet.getSpriteById(1), @@ -179,14 +179,16 @@ class JoystickAdvancedExample extends FlameGame with HasCollisionDetection { )..add(directionText); world.add(player); - camera.viewport.add(joystick); - camera.viewport.add(flipButton); - camera.viewport.add(flopButton); - camera.viewport.add(buttonComponent); - camera.viewport.add(spriteButtonComponent); - camera.viewport.add(shapeButton); - camera.viewport.add(speedWithMargin); - camera.viewport.add(directionWithMargin); + camera.viewport.addAll([ + joystick, + flipButton, + flopButton, + buttonComponent, + spriteButtonComponent, + shapeButton, + speedWithMargin, + directionWithMargin, + ]); } @override diff --git a/examples/lib/stories/input/joystick_example.dart b/examples/lib/stories/input/joystick_example.dart index b9375a31b..aac3dc4e0 100644 --- a/examples/lib/stories/input/joystick_example.dart +++ b/examples/lib/stories/input/joystick_example.dart @@ -25,7 +25,7 @@ class JoystickExample extends FlameGame { ); player = JoystickPlayer(joystick); - add(player); - add(joystick); + world.add(player); + camera.viewport.add(joystick); } } diff --git a/packages/flame/lib/src/collisions/hitboxes/screen_hitbox.dart b/packages/flame/lib/src/collisions/hitboxes/screen_hitbox.dart index c3b09a5ea..da9e2a574 100644 --- a/packages/flame/lib/src/collisions/hitboxes/screen_hitbox.dart +++ b/packages/flame/lib/src/collisions/hitboxes/screen_hitbox.dart @@ -7,12 +7,16 @@ import 'package:flame/src/collisions/hitboxes/rectangle_hitbox.dart'; /// viewport of the game. class ScreenHitbox extends PositionComponent with CollisionCallbacks, HasGameReference { + bool _hasWorldAncestor = false; @override Future onLoad() async { await super.onLoad(); add(RectangleHitbox()); - game.camera.viewfinder.transform.addListener(_updatePosition); - _updatePosition(); + _hasWorldAncestor = findParent() != null; + if (_hasWorldAncestor) { + game.camera.viewfinder.transform.addListener(_updatePosition); + _updatePosition(); + } } void _updatePosition() { @@ -26,6 +30,8 @@ class ScreenHitbox extends PositionComponent void onGameResize(Vector2 size) { super.onGameResize(size); this.size = size; - _updatePosition(); + if (_hasWorldAncestor) { + _updatePosition(); + } } } diff --git a/packages/flame/lib/src/extensions/vector2.dart b/packages/flame/lib/src/extensions/vector2.dart index edf3f89f1..6bd695e2b 100644 --- a/packages/flame/lib/src/extensions/vector2.dart +++ b/packages/flame/lib/src/extensions/vector2.dart @@ -108,6 +108,13 @@ extension Vector2Extension on Vector2 { } } + /// Clamps this vector so that it is within or equals to the bounds defined by + /// [min] and [max]. + void clamp(Vector2 min, Vector2 max) { + x = x.clamp(min.x, max.x); + y = y.clamp(min.y, max.y); + } + /// Project this onto [other]. /// /// [other] needs to have a length > 0;