import 'dart:math'; import 'dart:ui'; import 'package:flutter/painting.dart'; import '../svg.dart'; import '../sprite.dart'; import '../position.dart'; import '../anchor.dart'; /// This represents a Component for your game. /// /// Components can be bullets flying on the screen, a spaship or your player's fighter. /// Anything that either renders or updates can be added to the list on [BaseGame]. It will deal with calling those methods for you. /// Components also have other methods that can help you out if you want to overwrite them. abstract class Component { /// This method is called periodically by the game engine to request that your component updates itself. /// /// The time [t] in seconds (with microseconds precision provided by Flutter) since the last update cycle. /// This time can vary according to hardware capacity, so make sure to update your state considering this. /// All components on [BaseGame] are always updated by the same amount. The time each one takes to update adds up to the next update cycle. void update(double t); /// Renders this component on the provided Canvas [c]. void render(Canvas c); /// This is a hook called by [BaseGame] to let this component know that the screen (or flame draw area) has been update. /// /// It receives the new size. /// You can use the [Resizable] mixin if you want an implementation of this hook that keeps track of the current size. void resize(Size size) {} /// Whether this component has been loaded yet. If not loaded, [BaseGame] will not try to render it. /// /// Sprite based components can use this to let [BaseGame] know not to try to render when the [Sprite] has not been loaded yet. /// Note that for a more consistent experience, you can pre-load all your assets beforehand with Flame.images.loadAll. bool loaded() => true; /// Wether this should be destroyed or not. /// /// It will be called once per component per loop, and if it returns true, [BaseGame] will mark your component for deletion and remove it before the next loop. bool destroy() => false; /// Whether this component is HUD object or not. /// /// HUD objects ignore the [BaseGame.camera] when rendered (so their position coordinates are considered relative to the device screen). bool isHud() => false; /// Render priority of this component. This allows you to control the order in which your components are rendered. /// /// Components are always updated and rendered in the order defined by this number. /// The smaller the priority, the sooner your component will be updated/rendered. /// It can be any integer (negative, zero, or positive). /// If two components share the same priority, they will probably be drawn in the order they were added. int priority() => 0; } /// 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; Position toPosition() => Position(x, y); void setByPosition(Position position) { this.x = position.x; this.y = position.y; } Position toSize() => Position(width, height); void setBySize(Position size) { this.width = size.x; this.height = size.y; } Rect toRect() => Rect.fromLTWH(x, y, width, height); void setByRect(Rect rect) { this.x = rect.left; this.y = rect.top; this.width = rect.width; this.height = rect.height; } double angleBetween(PositionComponent c) { return (atan2(c.x - this.x, this.y - c.y) - pi / 2) % (2 * pi); } double distance(PositionComponent c) { return sqrt(pow(this.y - c.y, 2) + pow(this.x - c.x, 2)); } void prepareCanvas(Canvas canvas) { canvas.translate(x, y); canvas.rotate(angle); double dx = -anchor.relativePosition.dx * width; double dy = -anchor.relativePosition.dy * height; canvas.translate(dx, dy); } } /// A [PositionComponent] that renders a single [Sprite] at the designated position, scaled to have the designated size and rotated to the designated angle. /// /// This is the most commonly used child of [Component]. class SpriteComponent extends PositionComponent { Sprite sprite; 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 render(Canvas canvas) { prepareCanvas(canvas); sprite.render(canvas, width, height); } @override bool loaded() { return sprite != null && sprite.loaded() && x != null && y != null; } @override void update(double t) {} } class SvgComponent extends PositionComponent { Svg svg; SvgComponent.fromSvg(double width, double height, this.svg) { this.width = width; this.height = height; } @override render(Canvas canvas) { prepareCanvas(canvas); svg.render(canvas, width, height); } @override bool loaded() { return svg != null && svg.loaded() && x != null && y != null; } @override void update(double t) {} }