Merge pull request #473 from flame-engine/spydon.vector-for-position-component

Use Vector2 for position and size on PositionComponent
This commit is contained in:
Lukas Klingsbo
2020-10-03 16:19:49 +02:00
committed by GitHub
41 changed files with 272 additions and 294 deletions

View File

@ -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.

View File

@ -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<Sprite> 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<Sprite> 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.

View File

@ -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);

View File

@ -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);
}

View File

@ -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);

View File

@ -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) {

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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));
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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.',

View File

@ -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<MyGame> {
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<MyGame> {
@override
void onMount() {
width = height = gameRef.squareSize;
size = Vector2.all(gameRef.squareSize);
anchor = Anchor.center;
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}
}

View File

@ -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));
}
}
}

View File

@ -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());
}
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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,
);

View File

@ -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

View File

@ -148,7 +148,7 @@ class TextBoxComponent extends PositionComponent with Resizable {
Future<Image> _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());
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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<Vector2> pts) {
final double minx = pts.map((e) => e.x).reduce(min);

View File

@ -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());
}

View File

@ -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<FlareAnimationLayer> _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();

View File

@ -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(

View File

@ -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<SpriteAnimationFrame>(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<double> 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<SpriteAnimationFrame>(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);

View File

@ -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,
);
}

View File

@ -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);
});
});
}