diff --git a/doc/examples/debug/lib/main.dart b/doc/examples/debug/lib/main.dart index 5fb0639d5..80f12ddf5 100644 --- a/doc/examples/debug/lib/main.dart +++ b/doc/examples/debug/lib/main.dart @@ -1,7 +1,7 @@ import 'package:flame/game.dart'; import 'package:flame/flame.dart'; import 'package:flame/position.dart'; -import 'package:flame/components/component.dart'; +import 'package:flame/components/sprite_component.dart'; import 'package:flame/components/mixins/resizable.dart'; import 'package:flame/text_config.dart'; diff --git a/doc/examples/effects/combined_effects/lib/square.dart b/doc/examples/effects/combined_effects/lib/square.dart index a74ef7c8d..320517fb3 100644 --- a/doc/examples/effects/combined_effects/lib/square.dart +++ b/doc/examples/effects/combined_effects/lib/square.dart @@ -1,5 +1,5 @@ import 'package:flame/anchor.dart'; -import 'package:flame/components/component.dart'; +import 'package:flame/components/position_component.dart'; import 'dart:ui'; diff --git a/doc/examples/effects/infinite_effects/lib/square.dart b/doc/examples/effects/infinite_effects/lib/square.dart index a74ef7c8d..320517fb3 100644 --- a/doc/examples/effects/infinite_effects/lib/square.dart +++ b/doc/examples/effects/infinite_effects/lib/square.dart @@ -1,5 +1,5 @@ import 'package:flame/anchor.dart'; -import 'package:flame/components/component.dart'; +import 'package:flame/components/position_component.dart'; import 'dart:ui'; diff --git a/doc/examples/effects/sequence_effect/lib/square.dart b/doc/examples/effects/sequence_effect/lib/square.dart index a74ef7c8d..320517fb3 100644 --- a/doc/examples/effects/sequence_effect/lib/square.dart +++ b/doc/examples/effects/sequence_effect/lib/square.dart @@ -1,5 +1,5 @@ import 'package:flame/anchor.dart'; -import 'package:flame/components/component.dart'; +import 'package:flame/components/position_component.dart'; import 'dart:ui'; diff --git a/doc/examples/effects/simple/lib/square.dart b/doc/examples/effects/simple/lib/square.dart index e02a1ae76..77d09acc8 100644 --- a/doc/examples/effects/simple/lib/square.dart +++ b/doc/examples/effects/simple/lib/square.dart @@ -1,4 +1,4 @@ -import 'package:flame/components/component.dart'; +import 'package:flame/components/position_component.dart'; import 'dart:ui'; diff --git a/doc/examples/gestures/lib/main_tapables.dart b/doc/examples/gestures/lib/main_tapables.dart index d229ee665..b810598c6 100644 --- a/doc/examples/gestures/lib/main_tapables.dart +++ b/doc/examples/gestures/lib/main_tapables.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flame/game.dart'; -import 'package:flame/components/component.dart'; +import 'package:flame/components/position_component.dart'; import 'package:flame/components/mixins/tapable.dart'; void main() { diff --git a/doc/examples/sound/lib/main.dart b/doc/examples/sound/lib/main.dart index 5cd05d64c..14421a1d4 100644 --- a/doc/examples/sound/lib/main.dart +++ b/doc/examples/sound/lib/main.dart @@ -1,6 +1,6 @@ import 'package:flame/flame.dart'; import 'package:flame/game.dart'; -import 'package:flame/components/component.dart'; +import 'package:flame/components/position_component.dart'; import 'package:flutter/material.dart'; void main() async { diff --git a/doc/examples/sprites/lib/main.dart b/doc/examples/sprites/lib/main.dart index accd74fe9..aa6ced156 100644 --- a/doc/examples/sprites/lib/main.dart +++ b/doc/examples/sprites/lib/main.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:flame/components/component.dart'; +import 'package:flame/components/sprite_component.dart'; import 'package:flame/flame.dart'; import 'package:flame/game.dart'; import 'package:flutter/material.dart'; diff --git a/doc/examples/spritesheet/lib/main.dart b/doc/examples/spritesheet/lib/main.dart index 1dcf40f8d..5845239d2 100644 --- a/doc/examples/spritesheet/lib/main.dart +++ b/doc/examples/spritesheet/lib/main.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flame/components/animation_component.dart'; -import 'package:flame/components/component.dart'; +import 'package:flame/components/sprite_component.dart'; import 'package:flame/flame.dart'; import 'package:flame/game.dart'; import 'package:flame/spritesheet.dart'; diff --git a/example/lib/main.dart b/example/lib/main.dart index fa70e444b..9004d1fcc 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -3,7 +3,7 @@ import 'dart:ui'; import 'package:flame/anchor.dart'; import 'package:flame/gestures.dart'; -import 'package:flame/components/component.dart'; +import 'package:flame/components/position_component.dart'; import 'package:flame/components/mixins/has_game_ref.dart'; import 'package:flame/game.dart'; import 'package:flame/palette.dart'; diff --git a/lib/components/animation_component.dart b/lib/components/animation_component.dart index c33662dbf..8366cfd49 100644 --- a/lib/components/animation_component.dart +++ b/lib/components/animation_component.dart @@ -1,7 +1,7 @@ import 'dart:ui'; -import 'component.dart'; import '../animation.dart'; +import 'position_component.dart'; class AnimationComponent extends PositionComponent { Animation animation; diff --git a/lib/components/flare_component.dart b/lib/components/flare_component.dart index f61b6dc5b..bf265290d 100644 --- a/lib/components/flare_component.dart +++ b/lib/components/flare_component.dart @@ -1,7 +1,7 @@ import 'dart:ui'; -import 'component.dart'; -import 'package:flame/flare_animation.dart'; +import '../flare_animation.dart'; +import 'position_component.dart'; class FlareComponent extends PositionComponent { FlareAnimation _flareAnimation; diff --git a/lib/components/nine_tile_box_component.dart b/lib/components/nine_tile_box_component.dart index d583ca1cc..5a90377bf 100644 --- a/lib/components/nine_tile_box_component.dart +++ b/lib/components/nine_tile_box_component.dart @@ -1,7 +1,8 @@ import 'dart:ui'; -import 'package:flame/components/component.dart'; -import 'package:flame/nine_tile_box.dart'; +import 'component.dart'; +import 'position_component.dart'; +import '../nine_tile_box.dart'; /// This class is a thin wrapper on top of [NineTileBox] as a component. class NineTileBoxComponent extends PositionComponent { diff --git a/lib/components/parallax_component.dart b/lib/components/parallax_component.dart index 1c550e754..e73007139 100644 --- a/lib/components/parallax_component.dart +++ b/lib/components/parallax_component.dart @@ -1,10 +1,11 @@ import 'dart:async'; import 'dart:ui'; -import 'package:flame/components/component.dart'; -import 'package:flame/flame.dart'; import 'package:flutter/painting.dart'; +import '../flame.dart'; +import 'position_component.dart'; + /// Specifications with a path to an image and how it should be drawn in /// relation to the device screen class ParallaxImage { diff --git a/lib/components/position_component.dart b/lib/components/position_component.dart new file mode 100644 index 000000000..481f65fd3 --- /dev/null +++ b/lib/components/position_component.dart @@ -0,0 +1,119 @@ +import 'dart:ui'; +import 'dart:math'; + +import 'package:meta/meta.dart'; + +import '../anchor.dart'; +import '../effects/effects.dart'; +import '../position.dart'; +import '../text_config.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 also uses the [anchor] property to properly position itself. +abstract class PositionComponent extends Component { + double x = 0.0, y = 0.0, angle = 0.0; + double width = 0.0, height = 0.0; + Anchor anchor = Anchor.topLeft; + bool renderFlipX = false; + bool renderFlipY = false; + bool debugMode = false; + final List _effects = []; + + Color get debugColor => const Color(0xFFFF00FF); + + Paint get _debugPaint => Paint() + ..color = debugColor + ..style = PaintingStyle.stroke; + + TextConfig get debugTextConfig => TextConfig(color: debugColor, fontSize: 12); + + Position toPosition() => Position(x, y); + void setByPosition(Position position) { + x = position.x; + y = position.y; + } + + Position toSize() => Position(width, height); + void setBySize(Position size) { + width = size.x; + height = size.y; + } + + Rect toRect() => Rect.fromLTWH(x - anchor.relativePosition.dx * width, + y - anchor.relativePosition.dy * height, width, height); + void setByRect(Rect rect) { + x = rect.left + anchor.relativePosition.dx * rect.width; + y = rect.top + anchor.relativePosition.dy * rect.height; + width = rect.width; + height = rect.height; + } + + double angleBetween(PositionComponent c) { + return (atan2(c.x - x, y - c.y) - pi / 2) % (2 * pi); + } + + double distance(PositionComponent c) { + return sqrt(pow(y - c.y, 2) + pow(x - c.x, 2)); + } + + void renderDebugMode(Canvas canvas) { + canvas.drawRect(Rect.fromLTWH(0.0, 0.0, width, height), _debugPaint); + debugTextConfig.render( + canvas, + "x: ${x.toStringAsFixed(2)} y:${y.toStringAsFixed(2)}", + Position(-50, -15)); + + final Rect rect = toRect(); + final dx = rect.right; + final dy = rect.bottom; + debugTextConfig.render( + canvas, + "x:${dx.toStringAsFixed(2)} y:${dy.toStringAsFixed(2)}", + Position(width - 50, height)); + } + + void prepareCanvas(Canvas canvas) { + canvas.translate(x, y); + + canvas.rotate(angle); + final double dx = -anchor.relativePosition.dx * width; + final double dy = -anchor.relativePosition.dy * height; + canvas.translate(dx, dy); + + // Handle inverted rendering by moving center and flipping. + if (renderFlipX || renderFlipY) { + canvas.translate(width / 2, height / 2); + canvas.scale(renderFlipX ? -1.0 : 1.0, renderFlipY ? -1.0 : 1.0); + canvas.translate(-width / 2, -height / 2); + } + + if (debugMode) { + renderDebugMode(canvas); + } + } + + void addEffect(PositionComponentEffect effect) { + _effects.add(effect..initialize(this)); + } + + void removeEffect(PositionComponentEffect effect) { + effect.dispose(); + } + + void clearEffects() { + _effects.forEach(removeEffect); + } + + @mustCallSuper + @override + void update(double dt) { + _effects.forEach((e) => e.update(dt)); + _effects.removeWhere((e) => e.hasFinished()); + } +} diff --git a/lib/components/sprite_component.dart b/lib/components/sprite_component.dart new file mode 100644 index 000000000..75181621e --- /dev/null +++ b/lib/components/sprite_component.dart @@ -0,0 +1,40 @@ +import 'dart:ui'; + +import '../sprite.dart'; +import 'component.dart'; +import 'position_component.dart'; + +/// A [PositionComponent] that renders a single [Sprite] at the designated +/// position, scaled to have the designated size and rotated to the specified +/// angle. +/// +/// This a commonly used child of [Component]. +class SpriteComponent extends PositionComponent { + Sprite sprite; + Paint overridePaint; + + SpriteComponent(); + + SpriteComponent.square(double size, String imagePath) + : this.rectangle(size, size, 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; + } + + @override + void render(Canvas canvas) { + prepareCanvas(canvas); + sprite.render(canvas, + width: width, height: height, overridePaint: overridePaint); + } + + @override + bool loaded() { + return sprite != null && sprite.loaded() && x != null && y != null; + } +} diff --git a/lib/components/text_box_component.dart b/lib/components/text_box_component.dart index 02e9be5e8..49c9e176a 100644 --- a/lib/components/text_box_component.dart +++ b/lib/components/text_box_component.dart @@ -1,13 +1,14 @@ import 'dart:async'; -import 'dart:ui'; import 'dart:math' as math; +import 'dart:ui'; + import 'package:flutter/widgets.dart' as widgets; -import 'component.dart'; -import 'mixins/resizable.dart'; -import '../text_config.dart'; import '../palette.dart'; import '../position.dart'; +import '../text_config.dart'; +import 'mixins/resizable.dart'; +import 'position_component.dart'; class TextBoxConfig { final double maxWidth; diff --git a/lib/components/text_component.dart b/lib/components/text_component.dart index 4e52ca28b..bd4a41758 100644 --- a/lib/components/text_component.dart +++ b/lib/components/text_component.dart @@ -2,8 +2,8 @@ import 'dart:ui'; import 'package:flutter/painting.dart'; -import 'component.dart'; import '../text_config.dart'; +import 'position_component.dart'; class TextComponent extends PositionComponent { String _text; diff --git a/lib/effects/combined_effect.dart b/lib/effects/combined_effect.dart index 74bebf49b..2d3b5422d 100644 --- a/lib/effects/combined_effect.dart +++ b/lib/effects/combined_effect.dart @@ -1,9 +1,9 @@ import 'dart:math'; -import 'package:flame/components/component.dart'; import 'package:meta/meta.dart'; import './effects.dart'; +import '../components/position_component.dart'; class CombinedEffect extends PositionComponentEffect { final List effects; diff --git a/lib/effects/effects.dart b/lib/effects/effects.dart index 658351182..fb8f31f20 100644 --- a/lib/effects/effects.dart +++ b/lib/effects/effects.dart @@ -1,13 +1,14 @@ import 'dart:math'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import '../components/component.dart'; +import '../components/position_component.dart'; import '../position.dart'; export './move_effect.dart'; -export './scale_effect.dart'; export './rotate_effect.dart'; +export './scale_effect.dart'; export './sequence_effect.dart'; abstract class PositionComponentEffect { diff --git a/lib/effects/sequence_effect.dart b/lib/effects/sequence_effect.dart index 953ce83ca..9e6cce400 100644 --- a/lib/effects/sequence_effect.dart +++ b/lib/effects/sequence_effect.dart @@ -1,7 +1,7 @@ -import 'package:flame/components/component.dart'; import 'package:meta/meta.dart'; import './effects.dart'; +import '../components/position_component.dart'; class SequenceEffect extends PositionComponentEffect { final List effects; diff --git a/lib/game/base_game.dart b/lib/game/base_game.dart index c8246a7e9..9fb79288a 100644 --- a/lib/game/base_game.dart +++ b/lib/game/base_game.dart @@ -1,16 +1,17 @@ import 'dart:math' as math; import 'dart:ui'; -import 'package:flame/components/composed_component.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart' hide WidgetBuilder; -import 'package:flutter/foundation.dart'; import 'package:ordered_set/comparing.dart'; import 'package:ordered_set/ordered_set.dart'; import '../components/component.dart'; +import '../components/composed_component.dart'; import '../components/mixins/has_game_ref.dart'; import '../components/mixins/tapable.dart'; +import '../components/position_component.dart'; import '../position.dart'; import 'game.dart';