diff --git a/CHANGELOG.md b/CHANGELOG.md index a2a37c047..0a3356a96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Remove Position class in favor of new Vector2 extension - Remove Box2D as a dependency - Use isRelative on effects + - Use Vector2 for position and size on PositionComponent ## 0.27.0 - Improved the accuracy of the `FPSCounter` by using Flutter's internal frame timings. diff --git a/doc/components.md b/doc/components.md index 23db48678..e528b7928 100644 --- a/doc/components.md +++ b/doc/components.md @@ -4,7 +4,7 @@ This class represent a single object on the screen, being a floating rectangle o The base abstract class has the common expected methods update and render to be implemented. -The intermediate inheritance `PositionComponent` adds `x`, `y`, `width`, `height` and `angle` to your Components, as well as some useful methods like distance and angleBetween. +The intermediate inheritance `PositionComponent` adds `position`, `size` and `angle` to your Components, as well as some useful methods like distance and angleBetween. The most commonly used implementation, `SpriteComponent`, can be created with a `Sprite`: @@ -13,15 +13,14 @@ The most commonly used implementation, `SpriteComponent`, can be created with a Sprite sprite = Sprite('player.png'); - const size = 128.0; - var player = SpriteComponent.fromSprite(size, size, sprite); // width, height, sprite + final size = Vector2.all(128.0); + var player = SpriteComponent.fromSprite(size, sprite); // screen coordinates - player.x = ... // 0 by default - player.y = ... // 0 by default + player.position = ... // Vector2(0.0, 0.0) by default player.angle = ... // 0 by default - player.render(canvas); // it will render only if the image is loaded and the x, y, width and height parameters are not null + player.render(canvas); // it will render only if the image is loaded and the position and size parameters are not null ``` In the event that you want to easily change the direction of your components rendering, you can also use @@ -55,14 +54,16 @@ This component uses an instance of the [Animation](/doc/images.md#Animation) cla This will create a simple three frame animation ```dart - List sprites = [0, 1, 2].map((i) => new Sprite('player_${i}.png')).toList(); - this.player = AnimationComponent(64.0, 64.0, new Animation.spriteList(sprites, stepTime: 0.01)); + List sprites = [0, 1, 2].map((i) => Sprite('player_${i}.png')).toList(); + final size = Vector2.all(64.0); + this.player = AnimationComponent(size, new Animation.spriteList(sprites, stepTime: 0.01)); ``` If you have a sprite sheet, you can use the `sequenced` constructor, identical to the one provided by the `Animation` class (check more details in [the appropriate section](/doc/images.md#Animation)): ```dart - this.player = AnimationComponent.sequenced(64.0, 64.0, 'player.png', 2); + final size = Vector2.all(64.0); + this.player = AnimationComponent.sequenced(size, 'player.png', 2); ``` If you are not using `BaseGame`, don't forget this component needs to be update'd even if static, because the animation object needs to be ticked to move the frames. @@ -105,8 +106,7 @@ it also can receive a FlareController that can play multiple animations and cont } final fileName = 'assets/george_washington.flr'; - final width = 1776; - final height = 1804; + final size = Vector2(1776, 1804); final controller = WashingtonController(); //instantiate controller FlareActorComponent flareAnimation = FlareActorComponent( @@ -190,8 +190,8 @@ This creates a static background, if you want it to move you have to set the nam You can set the baseSpeed and layerDelta at any time, for example if your character jumps or your game speeds up. ```dart - this.bg.baseSpeed = Offset(100, 0); - this.bg.layerDelta = Offset(40, 0); + this.bg.baseSpeed = Vector2(100, 0); + this.bg.layerDelta = Vector2(40, 0); ``` By default the images are aligned to the bottom left, repeated along the X-axis and scaled proportionally so that the image covers the height of the screen. If you want to change this behaviour, for example if you are not making a side scrolling game, you can set the `repeat`, `alignment` and `fill` parameters for each ParallaxImage. @@ -203,7 +203,7 @@ Advanced example: ParallaxImage('planets.jpg', repeat: ImageRepeat.repeatY, alignment: Alignment.bottomLeft, fill: LayerFill.none), ParallaxImage('dust.jpg', repeat: ImageRepeat.repeatX, alignment: Alignment.topRight, fill: LayerFill.height), ]; - this.bg = ParallaxComponent(images, baseSpeed: Offset(50, 0), layerDelta: Offset(20, 0)); + this.bg = ParallaxComponent(images, baseSpeed: Vector2(50, 0), layerDelta: Vector2(20, 0)); ``` * The stars image in this example will be repeatedly drawn in both axis, align in the center and be scaled to fill the screen width. diff --git a/doc/examples/animations/lib/main.dart b/doc/examples/animations/lib/main.dart index 2f23ce38b..a209d48cf 100644 --- a/doc/examples/animations/lib/main.dart +++ b/doc/examples/animations/lib/main.dart @@ -20,32 +20,26 @@ class MyGame extends BaseGame with TapDetector { final animation = SpriteAnimation.sequenced( 'chopper.png', 4, - textureWidth: 48, - textureHeight: 48, + textureSize: Vector2.all(48), stepTime: 0.15, loop: true, ); void addAnimation(double x, double y) { - const textureWidth = 291.0; - const textureHeight = 178.0; + final size = Vector2(291, 178); final animationComponent = SpriteAnimationComponent.sequenced( - 291, - 178, + size, 'creature.png', 18, amountPerRow: 10, - textureWidth: textureWidth, - textureHeight: textureHeight, + textureSize: size, stepTime: 0.15, loop: false, destroyOnFinish: true, ); - animationComponent.x = x - textureWidth / 2; - animationComponent.y = y - textureHeight / 2; - + animationComponent.position = animationComponent.position - size / 2; add(animationComponent); } @@ -57,18 +51,17 @@ class MyGame extends BaseGame with TapDetector { MyGame(Vector2 screenSize) { size = screenSize; - const s = 100.0; - final animationComponent = SpriteAnimationComponent(s, s, animation); - animationComponent.x = size.x / 2 - s; - animationComponent.y = s; + final spriteSize = Vector2.all(100.0); + final animationComponent = SpriteAnimationComponent(spriteSize, animation); + animationComponent.x = size.x / 2 - spriteSize.x; + animationComponent.y = spriteSize.y; final reversedAnimationComponent = SpriteAnimationComponent( - s, - s, + spriteSize, animation.reversed(), ); reversedAnimationComponent.x = size.x / 2; - reversedAnimationComponent.y = s; + reversedAnimationComponent.y = spriteSize.y; add(animationComponent); add(reversedAnimationComponent); diff --git a/doc/examples/aseprite/lib/main.dart b/doc/examples/aseprite/lib/main.dart index 1f15dd87b..3a511530a 100644 --- a/doc/examples/aseprite/lib/main.dart +++ b/doc/examples/aseprite/lib/main.dart @@ -22,8 +22,9 @@ class MyGame extends BaseGame { 'chopper.png', 'chopper.json', ); - final animationComponent = SpriteAnimationComponent(200, 200, animation) - ..setPosition(size / 2 - Vector2(100, 100)); + final spriteSize = Vector2.all(200); + final animationComponent = SpriteAnimationComponent(spriteSize, animation) + ..position = size / 2 - Vector2.all(100); add(animationComponent); } diff --git a/doc/examples/audiopool/lib/main.dart b/doc/examples/audiopool/lib/main.dart index ebcac152d..de6868de7 100644 --- a/doc/examples/audiopool/lib/main.dart +++ b/doc/examples/audiopool/lib/main.dart @@ -29,7 +29,7 @@ class MyGame extends BaseGame with TapDetector { @override void render(Canvas canvas) { - canvas.drawRect(size.toOriginRect(), black); + canvas.drawRect(size.toRect(), black); final p = size / 2; regular.render(canvas, 'hit me!', p, anchor: Anchor.center); super.render(canvas); diff --git a/doc/examples/debug/lib/main.dart b/doc/examples/debug/lib/main.dart index 158bc5ade..9af8d3a13 100644 --- a/doc/examples/debug/lib/main.dart +++ b/doc/examples/debug/lib/main.dart @@ -20,7 +20,7 @@ class AndroidComponent extends SpriteComponent with Resizable { int xDirection = 1; int yDirection = 1; - AndroidComponent() : super.square(100, 'android.png'); + AndroidComponent() : super.fromImagePath(Vector2.all(100), 'android.png'); @override void update(double dt) { diff --git a/doc/examples/effects/combined_effects/lib/main.dart b/doc/examples/effects/combined_effects/lib/main.dart index 2e927a4be..c1c35a307 100644 --- a/doc/examples/effects/combined_effects/lib/main.dart +++ b/doc/examples/effects/combined_effects/lib/main.dart @@ -24,8 +24,8 @@ class MyGame extends BaseGame with TapDetector { MyGame() { final green = Paint()..color = const Color(0xAA338833); final red = Paint()..color = const Color(0xAA883333); - greenSquare = Square(green, 100, 100); - final redSquare = Square(red, 100, 100); + greenSquare = Square(green, Vector2.all(100)); + final redSquare = Square(red, Vector2.all(100)); add(greenSquare); add(redSquare); } diff --git a/doc/examples/effects/combined_effects/lib/square.dart b/doc/examples/effects/combined_effects/lib/square.dart index 9692f706b..93154bf4d 100644 --- a/doc/examples/effects/combined_effects/lib/square.dart +++ b/doc/examples/effects/combined_effects/lib/square.dart @@ -3,14 +3,14 @@ import 'package:flame/components/position_component.dart'; import 'dart:ui'; +import 'package:flame/extensions/vector2.dart'; + class Square extends PositionComponent { final Paint _paint; - Square(this._paint, double x, double y, {double angle = 0.0}) { - width = 100; - height = 100; - this.x = x; - this.y = y; + Square(this._paint, Vector2 position, {double angle = 0.0}) { + size = Vector2.all(100.0); + this.position = position; this.angle = angle; anchor = Anchor.center; } @@ -18,6 +18,6 @@ class Square extends PositionComponent { @override void render(Canvas canvas) { super.render(canvas); - canvas.drawRect(toOriginRect(), _paint); + canvas.drawRect(size.toRect(), _paint); } } diff --git a/doc/examples/effects/infinite_effects/lib/main.dart b/doc/examples/effects/infinite_effects/lib/main.dart index ad1218f65..50e3422ec 100644 --- a/doc/examples/effects/infinite_effects/lib/main.dart +++ b/doc/examples/effects/infinite_effects/lib/main.dart @@ -26,9 +26,9 @@ class MyGame extends BaseGame with TapDetector { final green = Paint()..color = const Color(0xAA338833); final red = Paint()..color = const Color(0xAA883333); final orange = Paint()..color = const Color(0xAABB6633); - greenSquare = Square(green, 100, 100); - redSquare = Square(red, 200, 200); - orangeSquare = Square(orange, 200, 400); + greenSquare = Square(green, Vector2.all(100)); + redSquare = Square(red, Vector2.all(200)); + orangeSquare = Square(orange, Vector2(200, 400)); add(greenSquare); add(redSquare); add(orangeSquare); diff --git a/doc/examples/effects/infinite_effects/lib/square.dart b/doc/examples/effects/infinite_effects/lib/square.dart index 9692f706b..93154bf4d 100644 --- a/doc/examples/effects/infinite_effects/lib/square.dart +++ b/doc/examples/effects/infinite_effects/lib/square.dart @@ -3,14 +3,14 @@ import 'package:flame/components/position_component.dart'; import 'dart:ui'; +import 'package:flame/extensions/vector2.dart'; + class Square extends PositionComponent { final Paint _paint; - Square(this._paint, double x, double y, {double angle = 0.0}) { - width = 100; - height = 100; - this.x = x; - this.y = y; + Square(this._paint, Vector2 position, {double angle = 0.0}) { + size = Vector2.all(100.0); + this.position = position; this.angle = angle; anchor = Anchor.center; } @@ -18,6 +18,6 @@ class Square extends PositionComponent { @override void render(Canvas canvas) { super.render(canvas); - canvas.drawRect(toOriginRect(), _paint); + canvas.drawRect(size.toRect(), _paint); } } diff --git a/doc/examples/effects/sequence_effect/lib/main.dart b/doc/examples/effects/sequence_effect/lib/main.dart index 0727f1eff..03ca751c4 100644 --- a/doc/examples/effects/sequence_effect/lib/main.dart +++ b/doc/examples/effects/sequence_effect/lib/main.dart @@ -23,7 +23,7 @@ class MyGame extends BaseGame with TapDetector { MyGame() { final green = Paint()..color = const Color(0xAA338833); - greenSquare = Square(green, 100, 100); + greenSquare = Square(green, Vector2.all(100)); add(greenSquare); } diff --git a/doc/examples/effects/sequence_effect/lib/square.dart b/doc/examples/effects/sequence_effect/lib/square.dart index 9692f706b..93154bf4d 100644 --- a/doc/examples/effects/sequence_effect/lib/square.dart +++ b/doc/examples/effects/sequence_effect/lib/square.dart @@ -3,14 +3,14 @@ import 'package:flame/components/position_component.dart'; import 'dart:ui'; +import 'package:flame/extensions/vector2.dart'; + class Square extends PositionComponent { final Paint _paint; - Square(this._paint, double x, double y, {double angle = 0.0}) { - width = 100; - height = 100; - this.x = x; - this.y = y; + Square(this._paint, Vector2 position, {double angle = 0.0}) { + size = Vector2.all(100.0); + this.position = position; this.angle = angle; anchor = Anchor.center; } @@ -18,6 +18,6 @@ class Square extends PositionComponent { @override void render(Canvas canvas) { super.render(canvas); - canvas.drawRect(toOriginRect(), _paint); + canvas.drawRect(size.toRect(), _paint); } } diff --git a/doc/examples/effects/simple/lib/square.dart b/doc/examples/effects/simple/lib/square.dart index a8fad314c..b34fab6b7 100644 --- a/doc/examples/effects/simple/lib/square.dart +++ b/doc/examples/effects/simple/lib/square.dart @@ -3,18 +3,19 @@ import 'package:flame/components/position_component.dart'; import 'dart:ui'; +import 'package:flame/extensions/vector2.dart'; + class Square extends PositionComponent { static final _paint = Paint()..color = const Color(0xFFFFFFFF); Square() { - width = 100; - height = 100; + size = Vector2.all(100); anchor = Anchor.center; } @override void render(Canvas canvas) { super.render(canvas); - canvas.drawRect(toOriginRect(), _paint); + canvas.drawRect(size.toRect(), _paint); } } diff --git a/doc/examples/gestures/lib/main_tapables.dart b/doc/examples/gestures/lib/main_tapables.dart index dbafba2a1..898f69a83 100644 --- a/doc/examples/gestures/lib/main_tapables.dart +++ b/doc/examples/gestures/lib/main_tapables.dart @@ -1,3 +1,4 @@ +import 'package:flame/extensions/vector2.dart'; import 'package:flutter/material.dart'; import 'package:flame/game.dart'; import 'package:flame/components/position_component.dart'; @@ -21,16 +22,15 @@ class TapableSquare extends PositionComponent with Tapable { bool _beenPressed = false; - TapableSquare({double y = 100, double x = 100}) { - width = height = 100; - this.x = x; - this.y = y; + TapableSquare({Vector2 position}) { + size = Vector2.all(100); + this.position = position ?? Vector2.all(100); } @override void render(Canvas canvas) { super.render(canvas); - canvas.drawRect(toOriginRect(), _beenPressed ? _grey : _white); + canvas.drawRect(size.toRect(), _beenPressed ? _grey : _white); } @override @@ -51,7 +51,7 @@ class TapableSquare extends PositionComponent with Tapable { class MyGame extends BaseGame with HasTapableComponents { MyGame() { - add(TapableSquare(y: 100)); - add(TapableSquare(y: 250)); + add(TapableSquare()); + add(TapableSquare()..y = 250); } } diff --git a/doc/examples/isometric/lib/main.dart b/doc/examples/isometric/lib/main.dart index 769834026..9874ff04e 100644 --- a/doc/examples/isometric/lib/main.dart +++ b/doc/examples/isometric/lib/main.dart @@ -25,7 +25,8 @@ class Selector extends SpriteComponent { bool show = false; Selector(double s) - : super.fromSprite(s, s, Sprite('selector.png', width: 32, height: 32)); + : super.fromSprite( + Vector2.all(s), Sprite('selector.png', size: Vector2.all(32.0))); @override void render(Canvas canvas) { @@ -81,6 +82,6 @@ class MyGame extends BaseGame with MouseMovementDetector { final screenPosition = event.position.toVector2(); final block = base.getBlock(screenPosition); selector.show = base.containsBlock(block); - selector.setPosition(base.getBlockPosition(block) + topLeft); + selector.position = base.getBlockPosition(block) + topLeft; } } diff --git a/doc/examples/render_flip/lib/main.dart b/doc/examples/render_flip/lib/main.dart index b3cb089f5..56e4c8c2b 100644 --- a/doc/examples/render_flip/lib/main.dart +++ b/doc/examples/render_flip/lib/main.dart @@ -16,13 +16,12 @@ class MyGame extends BaseGame { final animation = SpriteAnimation.sequenced( 'chopper.png', 4, - textureWidth: 48, - textureHeight: 48, + textureSize: Vector2.all(48), stepTime: 0.15, ); SpriteAnimationComponent buildAnimation() { - final ac = SpriteAnimationComponent(100, 100, animation); + final ac = SpriteAnimationComponent(Vector2.all(100), animation); ac.x = size.x / 2 - ac.width / 2; return ac; } diff --git a/doc/examples/sound/lib/main.dart b/doc/examples/sound/lib/main.dart index 873109208..bcb4b5623 100644 --- a/doc/examples/sound/lib/main.dart +++ b/doc/examples/sound/lib/main.dart @@ -21,7 +21,7 @@ class Ball extends PositionComponent { @override void render(Canvas c) { super.render(c); - c.drawOval(toOriginRect(), paint); + c.drawOval(size.toRect(), paint); } @override @@ -50,11 +50,8 @@ class MyGame extends BaseGame { Flame.audio.load('boin.mp3'); Flame.audio.loop('music.mp3', volume: 0.4); - add( - Ball(size) - ..y = (size.y / 2) - 50 - ..width = 100 - ..height = 100, - ); + add(Ball(size) + ..y = (size.y / 2) - 50 + ..size = Vector2.all(100)); } } diff --git a/doc/examples/sprites/lib/main.dart b/doc/examples/sprites/lib/main.dart index 685dd0752..cc7011b02 100644 --- a/doc/examples/sprites/lib/main.dart +++ b/doc/examples/sprites/lib/main.dart @@ -27,8 +27,10 @@ class MyGame extends BaseGame { void initSprites() async { final r = Random(); - List.generate(500, (i) => SpriteComponent.square(32, 'test.png')) - .forEach((sprite) { + List.generate( + 500, + (i) => SpriteComponent.fromImagePath(Vector2.all(32), 'test.png'), + ).forEach((sprite) { sprite.x = r.nextInt(size.x.toInt()).toDouble(); sprite.y = r.nextInt(size.y.toInt()).toDouble(); add(sprite); diff --git a/doc/examples/spritesheet/lib/main.dart b/doc/examples/spritesheet/lib/main.dart index 743d62a50..f079b8bf0 100644 --- a/doc/examples/spritesheet/lib/main.dart +++ b/doc/examples/spritesheet/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:flame/extensions/vector2.dart'; import 'package:flutter/material.dart'; import 'package:flame/components/sprite_animation_component.dart'; import 'package:flame/components/sprite_component.dart'; @@ -55,12 +56,14 @@ class MyGame extends BaseGame { final vampireAnimation = spriteSheet.createAnimation(0, stepTime: 0.1, to: 7); final ghostAnimation = spriteSheet.createAnimation(1, stepTime: 0.1, to: 7); + final spriteSize = Vector2(80, 90); - final vampireComponent = SpriteAnimationComponent(80, 90, vampireAnimation) - ..x = 150 - ..y = 100; + final vampireComponent = + SpriteAnimationComponent(spriteSize, vampireAnimation) + ..x = 150 + ..y = 100; - final ghostComponent = SpriteAnimationComponent(80, 90, ghostAnimation) + final ghostComponent = SpriteAnimationComponent(spriteSize, ghostAnimation) ..x = 150 ..y = 220; @@ -69,12 +72,12 @@ class MyGame extends BaseGame { // Some plain sprites final vampireSpriteComponent = - SpriteComponent.fromSprite(80, 90, spriteSheet.getSprite(0, 0)) + SpriteComponent.fromSprite(spriteSize, spriteSheet.getSprite(0, 0)) ..x = 50 ..y = 100; final ghostSpriteComponent = - SpriteComponent.fromSprite(80, 90, spriteSheet.getSprite(1, 0)) + SpriteComponent.fromSprite(spriteSize, spriteSheet.getSprite(1, 0)) ..x = 50 ..y = 220; diff --git a/doc/examples/text/lib/main.dart b/doc/examples/text/lib/main.dart index 09d3d7f1b..328f2491a 100644 --- a/doc/examples/text/lib/main.dart +++ b/doc/examples/text/lib/main.dart @@ -44,11 +44,11 @@ class MyGame extends BaseGame { add(TextComponent('center', config: tiny) ..anchor = Anchor.center - ..setPosition(size / 2)); + ..position = size / 2); add(TextComponent('bottomRight', config: tiny) ..anchor = Anchor.bottomRight - ..setPosition(size)); + ..position = size); add(MyTextBox( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eget ligula eu lectus lobortis condimentum.', diff --git a/example/lib/main.dart b/example/lib/main.dart index e9eea1bff..7484d7174 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:math' as math; import 'dart:ui'; import 'package:flame/anchor.dart'; +import 'package:flame/extensions/vector2.dart'; import 'package:flame/gestures.dart'; import 'package:flame/components/position_component.dart'; import 'package:flame/components/mixins/has_game_ref.dart'; @@ -31,7 +32,7 @@ class Square extends PositionComponent with HasGameRef { void render(Canvas c) { super.render(c); - c.drawRect(toOriginRect(), white); + c.drawRect(size.toRect(), white); c.drawRect(const Rect.fromLTWH(0, 0, 3, 3), red); c.drawRect(Rect.fromLTWH(width / 2, height / 2, 3, 3), blue); } @@ -45,7 +46,7 @@ class Square extends PositionComponent with HasGameRef { @override void onMount() { - width = height = gameRef.squareSize; + size = Vector2.all(gameRef.squareSize); anchor = Anchor.center; } } diff --git a/lib/anchor.dart b/lib/anchor.dart index 4e5d20657..ff63335e5 100644 --- a/lib/anchor.dart +++ b/lib/anchor.dart @@ -19,7 +19,6 @@ class Anchor { const Anchor(this.x, this.y); Vector2 translate(Vector2 p, Vector2 size) { - return p - relativePosition - ..multiply(size); + return p - (relativePosition..multiply(size)); } } diff --git a/lib/components/flare_component.dart b/lib/components/flare_component.dart index 1b5884f36..5f6855448 100644 --- a/lib/components/flare_component.dart +++ b/lib/components/flare_component.dart @@ -1,5 +1,6 @@ import 'dart:ui'; +import 'package:flame/extensions/vector2.dart'; import 'package:flutter/foundation.dart'; import '../flare_animation.dart'; @@ -12,18 +13,14 @@ class FlareComponent extends PositionComponent { FlareComponent( String fileName, String animation, - double width, - double height, + Vector2 size, ) { - this.width = width; - this.height = height; + super.size = size; FlareAnimation.load(fileName).then((loadedFlareAnimation) { _flareAnimation = loadedFlareAnimation; - _flareAnimation.updateAnimation(animation); - _flareAnimation.width = width; - _flareAnimation.height = height; + _flareAnimation.size = size; }); } @@ -33,6 +30,14 @@ class FlareComponent extends PositionComponent { } } + @override + set size(Vector2 newSize) { + super.size = newSize; + if (loaded()) { + _flareAnimation.size = size; + } + } + @override bool loaded() => _flareAnimation != null; @@ -50,20 +55,4 @@ class FlareComponent extends PositionComponent { _flareAnimation.update(dt); } } - - @override - set width(_width) { - super.width = _width; - if (loaded()) { - _flareAnimation.width = width; - } - } - - @override - set height(_height) { - super.height = _height; - if (loaded()) { - _flareAnimation.height = height; - } - } } diff --git a/lib/components/isometric_tile_map_component.dart b/lib/components/isometric_tile_map_component.dart index 39742a994..cb5de536a 100644 --- a/lib/components/isometric_tile_map_component.dart +++ b/lib/components/isometric_tile_map_component.dart @@ -93,17 +93,14 @@ class IsometricTileMapComponent extends PositionComponent { void render(Canvas c) { super.render(c); - final size = Vector2( - effectiveTileSize.toDouble(), - effectiveTileSize.toDouble(), - ); + final size = Vector2.all(effectiveTileSize.toDouble()); for (int i = 0; i < matrix.length; i++) { for (int j = 0; j < matrix[i].length; j++) { final element = matrix[i][j]; if (element != -1) { final sprite = tileset.getTile(element); final p = getBlockPositionInts(j, i); - sprite.renderRect(c, p.toRect(size)); + sprite.renderRect(c, p.toPositionedRect(size)); } } } diff --git a/lib/components/nine_tile_box_component.dart b/lib/components/nine_tile_box_component.dart index caf0d306c..88e489ddd 100644 --- a/lib/components/nine_tile_box_component.dart +++ b/lib/components/nine_tile_box_component.dart @@ -2,6 +2,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; +import '../extensions/vector2.dart'; import '../nine_tile_box.dart'; import 'position_component.dart'; @@ -18,6 +19,6 @@ class NineTileBoxComponent extends PositionComponent { @override void render(Canvas c) { super.render(c); - nineTileBox.drawRect(c, toOriginRect()); + nineTileBox.drawRect(c, size.toRect()); } } diff --git a/lib/components/parallax_component.dart b/lib/components/parallax_component.dart index c16dbce19..b6c6befac 100644 --- a/lib/components/parallax_component.dart +++ b/lib/components/parallax_component.dart @@ -93,7 +93,7 @@ class ParallaxLayer { // Size of the area to paint the images on final paintSize = count..multiply(_imageSize); - _paintArea = paintSize.toOriginRect(); + _paintArea = paintSize.toRect(); } void update(Vector2 delta) { diff --git a/lib/components/position_component.dart b/lib/components/position_component.dart index 888d5a391..69d596ead 100644 --- a/lib/components/position_component.dart +++ b/lib/components/position_component.dart @@ -1,5 +1,4 @@ -import 'dart:ui'; -import 'dart:math'; +import 'dart:ui' hide Offset; import 'package:meta/meta.dart'; import 'package:ordered_set/comparing.dart'; @@ -9,14 +8,15 @@ import '../anchor.dart'; import '../effects/effects.dart'; import '../game.dart'; import '../text_config.dart'; +import '../extensions/offset.dart'; import '../extensions/vector2.dart'; import 'component.dart'; /// A [Component] implementation that represents a component that has a /// specific, possibly dynamic position on the screen. /// -/// It represents a rectangle of dimension ([width], [height]), on the position -/// ([x], [y]), rotate around its center with angle [angle]. +/// It represents a rectangle of dimension [size], on the [position], +/// rotated around its [anchor] with angle [angle]. /// /// It also uses the [anchor] property to properly position itself. /// @@ -25,26 +25,43 @@ import 'component.dart'; /// They are translated by this component's (x,y). They do not need to fit /// within this component's (width, height). abstract class PositionComponent extends Component { - /// X position of this component on the screen (measured from the top left corner). - double x = 0.0; + /// The position of this component on the screen (relative to the anchor). + Vector2 position = Vector2.zero(); - /// Y position of this component on the screen (measured from the top left corner). - double y = 0.0; + /// X position of this component on the screen (relative to the anchor). + double get x => position.x; + set x(double x) => position.x = x; + + /// Y position of this component on the screen (relative to the anchor). + double get y => position.y; + set y(double y) => position.y = y; + + /// The size that this component is rendered with. + /// This is not necessarily the source size of the asset. + Vector2 size = Vector2.zero(); + + /// Width (size) that this component is rendered with. + double get width => size.x; + set width(double width) => size.x = width; + + /// Height (size) that this component is rendered with. + double get height => size.y; + set height(double height) => size.y = height; + + /// Get the top left position regardless of the anchor + Vector2 get topLeftPosition => anchor.translate(position, size); + + /// Set the top left position regardless of the anchor + set topLeftPosition(Vector2 position) { + this.position = position + (anchor.relativePosition..multiply(size)); + } /// Angle (with respect to the x-axis) this component should be rendered with. /// It is rotated around its anchor. double angle = 0.0; - /// Width (size) that this component is rendered with. - /// This is not necessarily the source width of the asset. - double width = 0.0; - - /// Height (size) that this component is rendered with. - /// This is not necessarily the source height of the asset. - double height = 0.0; - /// Anchor point for this component. This is where flame "grabs it". - /// The [x], [y] coordinates are relative to this point inside the component. + /// The [position] is relative to this point inside the component. /// The [angle] is rotated around this point. Anchor anchor = Anchor.topLeft; @@ -72,51 +89,23 @@ abstract class PositionComponent extends Component { TextConfig get debugTextConfig => TextConfig(color: debugColor, fontSize: 12); - Vector2 get position => Vector2(x, y); - void setPosition(Vector2 position) { - x = position.x; - y = position.y; - } - - Vector2 get size => Vector2(width, height); - void setBySize(Vector2 size) { - width = size.x; - height = size.y; - } - - /// Returns the size of this component starting at (0, 0). - /// Effectively this is it's position with respect to itself. - /// Use this if the canvas is already translated by (x, y). - Rect toOriginRect() => Rect.fromLTWH(0, 0, width, height); - /// Returns the relative position/size of this component. /// Relative because it might be translated by their parents (which is not considered here). - Rect toRect() => Rect.fromLTWH( - x - anchor.relativePosition.x * width, - y - anchor.relativePosition.y * height, - width, - height, - ); + Rect toRect() => topLeftPosition.toPositionedRect(size); - /// Mutates x, y, width and height using the provided [rect] as basis. + /// Mutates position and size using the provided [rect] as basis. /// This is a relative rect, same definition that [toRect] use (therefore both methods are compatible, i.e. setByRect ∘ toRect = identity). void setByRect(Rect rect) { - x = rect.left + anchor.relativePosition.x * rect.width; - y = rect.top + anchor.relativePosition.y * rect.height; - width = rect.width; - height = rect.height; + size.setValues(rect.width, rect.height); + topLeftPosition = rect.topLeft.toVector2(); } - double angleBetween(PositionComponent c) { - return (atan2(c.x - x, y - c.y) - pi / 2) % (2 * pi); - } + double angleTo(PositionComponent c) => position.angleTo(c.position); - double distance(PositionComponent c) { - return c.position.distanceTo(position); - } + double distance(PositionComponent c) => position.distanceTo(c.position); void renderDebugMode(Canvas canvas) { - canvas.drawRect(toOriginRect(), _debugPaint); + canvas.drawRect(size.toRect(), _debugPaint); debugTextConfig.render( canvas, 'x: ${x.toStringAsFixed(2)} y:${y.toStringAsFixed(2)}', @@ -137,9 +126,9 @@ abstract class PositionComponent extends Component { canvas.translate(x, y); canvas.rotate(angle); - final double dx = -anchor.relativePosition.x * width; - final double dy = -anchor.relativePosition.y * height; - canvas.translate(dx, dy); + final Vector2 delta = -anchor.relativePosition + ..multiply(size); + canvas.translate(delta.x, delta.y); // Handle inverted rendering by moving center and flipping. if (renderFlipX || renderFlipY) { diff --git a/lib/components/sprite_animation_component.dart b/lib/components/sprite_animation_component.dart index f912d8332..c18ea274c 100644 --- a/lib/components/sprite_animation_component.dart +++ b/lib/components/sprite_animation_component.dart @@ -1,5 +1,6 @@ import 'dart:ui'; +import 'package:flame/extensions/vector2.dart'; import 'package:flutter/foundation.dart'; import '../sprite_animation.dart'; @@ -11,41 +12,34 @@ class SpriteAnimationComponent extends PositionComponent { bool destroyOnFinish = false; SpriteAnimationComponent( - double width, - double height, + Vector2 size, this.animation, { this.destroyOnFinish = false, }) { - this.width = width; - this.height = height; + super.size.setFrom(size); } SpriteAnimationComponent.empty(); SpriteAnimationComponent.sequenced( - double width, - double height, + Vector2 size, String imagePath, int amount, { int amountPerRow, - double textureX = 0.0, - double textureY = 0.0, - double textureWidth, - double textureHeight, + Vector2 texturePosition, + Vector2 textureSize, double stepTime, bool loop = true, this.destroyOnFinish = false, }) { - this.width = width; - this.height = height; + super.size.setFrom(size); + texturePosition ??= Vector2.zero(); animation = SpriteAnimation.sequenced( imagePath, amount, amountPerRow: amountPerRow, - textureX: textureX, - textureY: textureY, - textureWidth: textureWidth, - textureHeight: textureHeight, + texturePosition: texturePosition, + textureSize: textureSize, stepTime: stepTime ?? 0.1, loop: loop, ); diff --git a/lib/components/sprite_component.dart b/lib/components/sprite_component.dart index 9d6b80571..8ad04bec1 100644 --- a/lib/components/sprite_component.dart +++ b/lib/components/sprite_component.dart @@ -1,5 +1,6 @@ import 'dart:ui'; +import 'package:flame/extensions/vector2.dart'; import 'package:flutter/foundation.dart'; import '../sprite.dart'; @@ -22,15 +23,11 @@ class SpriteComponent extends PositionComponent { SpriteComponent(); - SpriteComponent.square(double size, String imagePath) - : this.rectangle(size, size, imagePath); + SpriteComponent.fromImagePath(Vector2 size, String imagePath) + : this.fromSprite(size, Sprite(imagePath)); - SpriteComponent.rectangle(double width, double height, String imagePath) - : this.fromSprite(width, height, Sprite(imagePath)); - - SpriteComponent.fromSprite(double width, double height, this.sprite) { - this.width = width; - this.height = height; + SpriteComponent.fromSprite(Vector2 size, this.sprite) { + super.size.setFrom(size); } @mustCallSuper diff --git a/lib/components/text_box_component.dart b/lib/components/text_box_component.dart index 27510d717..0e00110c5 100644 --- a/lib/components/text_box_component.dart +++ b/lib/components/text_box_component.dart @@ -148,7 +148,7 @@ class TextBoxComponent extends PositionComponent with Resizable { Future _redrawCache() { final PictureRecorder recorder = PictureRecorder(); - final Canvas c = Canvas(recorder, toOriginRect()); + final Canvas c = Canvas(recorder, size.toRect()); _fullRender(c); return recorder.endRecording().toImage(width.toInt(), height.toInt()); } diff --git a/lib/components/text_component.dart b/lib/components/text_component.dart index 08fe5260a..3db84da42 100644 --- a/lib/components/text_component.dart +++ b/lib/components/text_component.dart @@ -35,8 +35,7 @@ class TextComponent extends PositionComponent { void _updateBox() { _tp = config.toTextPainter(_text); - width = _tp.width; - height = _tp.height; + size.setValues(_tp.width, _tp.height); } @mustCallSuper diff --git a/lib/effects/move_effect.dart b/lib/effects/move_effect.dart index 2be20c5db..3b2a5f919 100644 --- a/lib/effects/move_effect.dart +++ b/lib/effects/move_effect.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:flutter/animation.dart'; import 'package:meta/meta.dart'; @@ -43,6 +41,6 @@ class MoveEffect extends PositionComponentEffect { void update(double dt) { super.update(dt); final double progress = curve?.transform(percentage) ?? 1.0; - component.setPosition(_startPosition + _delta * progress); + component.position = _startPosition + _delta * progress; } } diff --git a/lib/effects/scale_effect.dart b/lib/effects/scale_effect.dart index cbdff2cfe..93a6b8936 100644 --- a/lib/effects/scale_effect.dart +++ b/lib/effects/scale_effect.dart @@ -41,6 +41,6 @@ class ScaleEffect extends PositionComponentEffect { void update(double dt) { super.update(dt); final double progress = curve?.transform(percentage) ?? 1.0; - component.setBySize(_startSize + _delta * progress); + component.size = _startSize + _delta * progress; } } diff --git a/lib/effects/sequence_effect.dart b/lib/effects/sequence_effect.dart index 561adb0d6..ce7dd306f 100644 --- a/lib/effects/sequence_effect.dart +++ b/lib/effects/sequence_effect.dart @@ -31,8 +31,8 @@ class SequenceEffect extends PositionComponentEffect { final originalAngle = _comp.angle; effects.forEach((effect) { effect.reset(); - _comp.setBySize(endSize); - _comp.setPosition(endPosition); + _comp.size = endSize; + _comp.position = endPosition; _comp.angle = endAngle; effect.initialize(_comp); endSize = effect.endSize; @@ -43,8 +43,8 @@ class SequenceEffect extends PositionComponentEffect { 0, (time, effect) => time + effect.totalTravelTime, ); - component.setBySize(originalSize); - component.setPosition(originalPosition); + component.size = originalSize; + component.position = originalPosition; component.angle = originalAngle; currentEffect = effects.first; _currentWasAlternating = currentEffect.isAlternating; diff --git a/lib/extensions/rect.dart b/lib/extensions/rect.dart index d25f9d4ca..5cdcdea1b 100644 --- a/lib/extensions/rect.dart +++ b/lib/extensions/rect.dart @@ -16,11 +16,6 @@ extension RectExtension on Rect { // Until [extension] will allow static methods we need to keep these functions // in a utility class class RectFactory { - /// Creates a [Rect] with the size of [Size] - static Rect fromSize(Size size) { - return Rect.fromLTWH(0, 0, size.width, size.height); - } - /// Creates bounds in from of a [Rect] from a list of [Vector2] static Rect fromBounds(List pts) { final double minx = pts.map((e) => e.x).reduce(min); diff --git a/lib/extensions/vector2.dart b/lib/extensions/vector2.dart index 85085db92..38dbb1b78 100644 --- a/lib/extensions/vector2.dart +++ b/lib/extensions/vector2.dart @@ -15,11 +15,12 @@ extension Vector2Extension on Vector2 { /// Creates a [Point] from the [Vector2] Point toPoint() => Point(x, y); - /// Creates a [Rect] starting from [x, y] and having size [to]. - Rect toRect(Vector2 to) => Rect.fromLTWH(x, y, to.x, to.y); + /// Creates a [Rect] starting from [x, y] and having the size of the + /// argument [Vector2] + Rect toPositionedRect(Vector2 size) => Rect.fromLTWH(x, y, size.x, size.y); - /// Creates a [Rect] starting in origin and having size [to]. - Rect toOriginRect() => Rect.fromLTWH(0, 0, x, y); + /// Creates a [Rect] starting in origin and going the [Vector2] + Rect toRect() => Rect.fromLTWH(0, 0, x, y); /// Linearly interpolate towards another Vector2 void lerp(Vector2 to, double t) { @@ -43,4 +44,7 @@ extension Vector2Extension on Vector2 { scale(newLength.abs() / l); } } + + /// Create a Vector2 with ints as input + static Vector2 fromInts(int x, int y) => Vector2(x.toDouble(), y.toDouble()); } diff --git a/lib/flare_animation.dart b/lib/flare_animation.dart index 27f16c372..b44d06786 100644 --- a/lib/flare_animation.dart +++ b/lib/flare_animation.dart @@ -5,6 +5,7 @@ import "package:flare_flutter/flare.dart"; import "package:flare_flutter/flare_actor.dart"; import "flame.dart"; +import "extensions/vector2.dart"; @Deprecated("Use flame_flare package instead") class FlareAnimation { @@ -13,7 +14,8 @@ class FlareAnimation { String _animationName; final List _animationLayers = []; - double _width = 0.0, _height = 0.0, _xScale = 0.0, _yScale = 0.0; + final Vector2 _size = Vector2.zero(); + final Vector2 _scale = Vector2.zero(); Picture _picture; @@ -32,23 +34,15 @@ class FlareAnimation { return FlareAnimation(artboard); } - double get width { - return _width; + double get width => size.x; + double get height => size.y; + + set size(Vector2 newSize) { + _size.setFrom(newSize); + _scale.setValues(_size.x / _artboard.width, _size.y / _artboard.height); } - double get height { - return _height; - } - - set width(double newWidth) { - _width = newWidth; - _xScale = _width / _artboard.width; - } - - set height(double newHeight) { - _height = newHeight; - _yScale = _height / _artboard.height; - } + Vector2 get size => _size; void updateAnimation(String animation) { _animationName = animation; @@ -73,10 +67,8 @@ class FlareAnimation { if (_picture == null) { return; } - canvas.save(); canvas.translate(x, y); - canvas.drawPicture(_picture); canvas.restore(); } @@ -125,7 +117,7 @@ class FlareAnimation { final r = PictureRecorder(); final c = Canvas(r); - c.scale(_xScale, _yScale); + c.scale(_scale.x, _scale.y); _artboard.draw(c); _picture = r.endRecording(); diff --git a/lib/sprite.dart b/lib/sprite.dart index 53d5b0d85..9ba7b8dc2 100644 --- a/lib/sprite.dart +++ b/lib/sprite.dart @@ -12,16 +12,14 @@ class Sprite { Sprite( String fileName, { - double x = 0.0, - double y = 0.0, - double width, - double height, + Vector2 position, + Vector2 size, }) { + position ??= Vector2.zero(); Flame.images.load(fileName).then((img) { - width ??= img.width.toDouble(); - height ??= img.height.toDouble(); + size ??= Vector2(img.width.toDouble(), img.height.toDouble()); image = img; - src = Rect.fromLTWH(x, y, width, height); + src = position.toPositionedRect(size); }); } @@ -100,7 +98,7 @@ class Sprite { return; } size ??= this.size; - renderRect(canvas, p.toRect(size), overridePaint: overridePaint); + renderRect(canvas, p.toPositionedRect(size), overridePaint: overridePaint); } void render( diff --git a/lib/sprite_animation.dart b/lib/sprite_animation.dart index 1fbc7ce34..ef2f6656c 100644 --- a/lib/sprite_animation.dart +++ b/lib/sprite_animation.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'extensions/vector2.dart'; import 'flame.dart'; import 'sprite.dart'; @@ -75,22 +76,23 @@ class SpriteAnimation { String imagePath, int amount, { int amountPerRow, - double textureX = 0.0, - double textureY = 0.0, - double textureWidth, - double textureHeight, + Vector2 texturePosition, + Vector2 textureSize, double stepTime = 0.1, this.loop = true, }) : assert(amountPerRow == null || amount >= amountPerRow) { amountPerRow ??= amount; + texturePosition ??= Vector2.zero(); frames = List(amount); - for (var i = 0; i < amount; i++) { + for (int i = 0; i < amount; i++) { + final position = Vector2( + texturePosition.x + (i % amountPerRow) * textureSize.x, + texturePosition.y + (i ~/ amountPerRow) * textureSize.y, + ); final Sprite sprite = Sprite( imagePath, - x: textureX + (i % amountPerRow) * textureWidth, - y: textureY + (i ~/ amountPerRow) * textureHeight, - width: textureWidth, - height: textureHeight, + position: position, + size: textureSize, ); frames[i] = SpriteAnimationFrame(sprite, stepTime); } @@ -102,21 +104,21 @@ class SpriteAnimation { int amount, List stepTimes, { int amountPerRow, - double textureX = 0.0, - double textureY = 0.0, - double textureWidth, - double textureHeight, + Vector2 texturePosition, + Vector2 textureSize, this.loop = true, }) : assert(amountPerRow == null || amount >= amountPerRow) { amountPerRow ??= amount; frames = List(amount); - for (var i = 0; i < amount; i++) { + for (int i = 0; i < amount; i++) { + final position = Vector2( + texturePosition.x + (i % amountPerRow) * textureSize.x, + texturePosition.y + (i ~/ amountPerRow) * textureSize.y, + ); final Sprite sprite = Sprite( imagePath, - x: textureX + (i % amountPerRow) * textureWidth, - y: textureY + (i ~/ amountPerRow) * textureHeight, - width: textureWidth, - height: textureHeight, + position: position, + size: textureSize, ); frames[i] = SpriteAnimationFrame(sprite, stepTimes[i]); } @@ -145,10 +147,8 @@ class SpriteAnimation { final Sprite sprite = Sprite( imagePath, - x: x.toDouble(), - y: y.toDouble(), - width: width.toDouble(), - height: height.toDouble(), + position: Vector2Extension.fromInts(x, y), + size: Vector2Extension.fromInts(width, height), ); return SpriteAnimationFrame(sprite, stepTime); diff --git a/lib/spritesheet.dart b/lib/spritesheet.dart index df78fce96..c222a19cb 100644 --- a/lib/spritesheet.dart +++ b/lib/spritesheet.dart @@ -1,8 +1,10 @@ import 'dart:ui'; import 'package:meta/meta.dart'; + import 'sprite.dart'; import 'sprite_animation.dart'; +import 'extensions/vector2.dart'; /// Utility class to help extract animations and sprites from a spritesheet image class SpriteSheet { @@ -36,12 +38,11 @@ class SpriteSheet { int x, int y, ) { + final size = Vector2(textureWidth.toDouble(), textureHeight.toDouble()); return Sprite( imageName, - x: (x * textureWidth).toDouble(), - y: (y * textureHeight).toDouble(), - width: textureWidth.toDouble(), - height: textureHeight.toDouble(), + position: Vector2(x.toDouble(), y.toDouble())..multiply(size), + size: size, ); } diff --git a/test/components/component_test.dart b/test/components/component_test.dart index 0fee7b762..e209fbf90 100644 --- a/test/components/component_test.dart +++ b/test/components/component_test.dart @@ -1,5 +1,6 @@ import 'dart:ui'; +import 'package:flame/anchor.dart'; import 'package:flame/components/position_component.dart'; import 'package:flame/components/sprite_component.dart'; import 'package:flame/extensions/vector2.dart'; @@ -9,38 +10,35 @@ void main() { group('component test', () { test('test get/set x/y or position', () { final PositionComponent c = SpriteComponent(); - c.x = 2.2; - c.y = 3.4; - expect(c.position.x, 2.2); - expect(c.position.y, 3.4); + c.position = Vector2(2.2, 3.4); + expect(c.x, 2.2); + expect(c.y, 3.4); - c.setPosition(Vector2(1.0, 0.0)); + c.position = Vector2(1.0, 0.0); expect(c.x, 1.0); expect(c.y, 0.0); }); test('test get/set width/height or size', () { final PositionComponent c = SpriteComponent(); - c.width = 2.2; - c.height = 3.4; + c.size = Vector2(2.2, 3.4); expect(c.size.x, 2.2); expect(c.size.y, 3.4); - c.setBySize(Vector2(1.0, 0.0)); + c.size = Vector2(1.0, 0.0); expect(c.width, 1.0); expect(c.height, 0.0); }); test('test get/set rect', () { final PositionComponent c = SpriteComponent(); - c.x = 0.0; - c.y = 1.0; - c.width = 2.0; - c.height = 2.0; - expect(c.toRect().left, 0.0); - expect(c.toRect().top, 1.0); - expect(c.toRect().width, 2.0); - expect(c.toRect().height, 2.0); + c.position = Vector2(0.0, 1.0); + c.size = Vector2(2.0, 2.0); + final rect = c.toRect(); + expect(rect.left, 0.0); + expect(rect.top, 1.0); + expect(rect.width, 2.0); + expect(rect.height, 2.0); c.setByRect(const Rect.fromLTWH(10.0, 10.0, 1.0, 1.0)); expect(c.x, 10.0); @@ -48,5 +46,33 @@ void main() { expect(c.width, 1.0); expect(c.height, 1.0); }); + + test('test get/set rect with anchor', () { + final PositionComponent c = SpriteComponent(); + c.position = Vector2(0.0, 1.0); + c.size = Vector2(2.0, 2.0); + c.anchor = Anchor.center; + final rect = c.toRect(); + expect(rect.left, -1.0); + expect(rect.top, 0.0); + expect(rect.width, 2.0); + expect(rect.height, 2.0); + + c.setByRect(const Rect.fromLTWH(10.0, 10.0, 1.0, 1.0)); + expect(c.x, 10.5); + expect(c.y, 10.5); + expect(c.width, 1.0); + expect(c.height, 1.0); + }); + + test('test get/set anchorPosition', () { + final PositionComponent c = SpriteComponent(); + c.position = Vector2(0.0, 1.0); + c.size = Vector2(2.0, 2.0); + c.anchor = Anchor.center; + final anchorPosition = c.topLeftPosition; + expect(anchorPosition.x, -1.0); + expect(anchorPosition.y, 0.0); + }); }); }