mirror of
				https://github.com/flame-engine/flame.git
				synced 2025-11-01 01:18:38 +08:00 
			
		
		
		
	Possibility to initialize all PositionComponents from onLoad (#1113)
				
					
				
			* Fix ParallaxComponent constructor * Fix sizing bug parallax_component * Unify TextComponent and TextBoxComponent * Fix tests * Update PositionComponent docs * Add changelog entry * Apply suggestions from code review Co-authored-by: Erick <erickzanardoo@gmail.com> * Fix analyze issue * Apply suggestions from code review Co-authored-by: Luan Nico <luanpotter27@gmail.com> * Fix line length in components.md Co-authored-by: Erick <erickzanardoo@gmail.com> Co-authored-by: Luan Nico <luanpotter27@gmail.com>
This commit is contained in:
		| @ -8,7 +8,7 @@ This diagram might look intimidating, but don't worry, it is not as complex as i | |||||||
| All components inherit from the abstract class `Component`. | All components inherit from the abstract class `Component`. | ||||||
|  |  | ||||||
| If you want to skip reading about abstract classes you can jump directly to | If you want to skip reading about abstract classes you can jump directly to | ||||||
| [PositionComponent](#positioncomponent). | [](#positioncomponent). | ||||||
|  |  | ||||||
| Every `Component` has a few methods that you can optionally implement, which are used by the | Every `Component` has a few methods that you can optionally implement, which are used by the | ||||||
| `FlameGame` class. If you are not using `FlameGame`, you can use these methods on your own game loop | `FlameGame` class. If you are not using `FlameGame`, you can use these methods on your own game loop | ||||||
| @ -90,15 +90,67 @@ class GameOverPanel extends PositionComponent with HasGameRef<MyGame> { | |||||||
|  |  | ||||||
| ## PositionComponent | ## PositionComponent | ||||||
|  |  | ||||||
| This class represent a single object on the screen, being a floating rectangle or a rotating sprite. | This class represent a positioned object on the screen, being a floating rectangle or a rotating | ||||||
|  | sprite. It can also represent a group of positioned components if children are added to it. | ||||||
|  |  | ||||||
| A `PositionComponent` has a `position`, `size` and `angle`, as well as some useful methods like | The base of the `PositionComponent` is that it has a `position`, `size`, `scale`, `angle` and | ||||||
| `distance` and `angleBetween`. | `anchor` which transforms how the component is rendered. | ||||||
|  |  | ||||||
|  | ### Position | ||||||
|  |  | ||||||
|  | The `position` is just a `Vector2` which represents the position of the component's anchor in | ||||||
|  | relation to its parent; if the parent is a `FlameGame`, it is in relation to the viewport. | ||||||
|  |  | ||||||
|  | ### Size | ||||||
|  |  | ||||||
|  | The `size` of the component when the zoom level of the camera is 1.0 (no zoom, default). | ||||||
|  | The `size` is *not* in relation to the parent of the component. | ||||||
|  |  | ||||||
|  | ### Scale | ||||||
|  |  | ||||||
|  | The `scale` is how much the component and its children should be scaled. Since it is represented | ||||||
|  | by a `Vector2`, you can scale in a uniform way by changing `x` and `y` with the same amount, or in a | ||||||
|  | non-uniform way, by change `x` or `y` by different amounts. | ||||||
|  |  | ||||||
|  | ### Angle | ||||||
|  |  | ||||||
|  | The `angle` is the rotation angle around the anchor, represented as a double in radians. It is | ||||||
|  | relative to the parent's angle. | ||||||
|  |  | ||||||
|  | ### Anchor | ||||||
|  |  | ||||||
|  | The `anchor` is where on the component that the position and rotation should be defined from (the | ||||||
|  | default is `Anchor.topLeft`). So if you have the anchor set as `Anchor.center` the component's | ||||||
|  | position on the screen will be in the center of the component and if an `angle` is applied, it is | ||||||
|  | rotated around the anchor, so in this case around the center of the component. You can think of it | ||||||
|  | as the point within the component by which Flame "grabs" it. | ||||||
|  |  | ||||||
|  | ### PositionComponent children | ||||||
|  |  | ||||||
|  | All children of the `PositionComponent` will be transformed in relation to the parent, which means | ||||||
|  | that the `position`, `angle` and `scale` will be relative to the parents state. | ||||||
|  | So if you, for example, wanted to position a child 50 logical pixels above the center of the parent | ||||||
|  | you would do this: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | final parent = PositionComponent( | ||||||
|  |   position: Vector2(100, 100), | ||||||
|  |   size: Vector2(100, 100), | ||||||
|  |   anchor: Anchor.center, | ||||||
|  | ); | ||||||
|  | final child = PositionComponent(position: Vector2(0, -50)); | ||||||
|  | parent.add(child); | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Remember that most components that are rendered on the screen are `PositionComponent`s, so | ||||||
|  | this pattern can be used in for example [](#spritecomponent) and [](#spriteanimationcomponent) too. | ||||||
|  |  | ||||||
|  | ### Render PositionComponent | ||||||
|  |  | ||||||
| When implementing the `render` method for a component that extends `PositionComponent` remember to | When implementing the `render` method for a component that extends `PositionComponent` remember to | ||||||
| render from the top left corner (0.0). Your render method should not handle where on the screen your | render from the top left corner (0.0). Your render method should not handle where on the screen your | ||||||
| component should be rendered. To handle where and how your component should be rendered use the | component should be rendered. To handle where and how your component should be rendered use the | ||||||
| `position`, `angle` and `anchor` properties and flame will automatically handle the rest for you. | `position`, `angle` and `anchor` properties and Flame will automatically handle the rest for you. | ||||||
|  |  | ||||||
| If you want to know where on the screen the bounding box of the component is you can use the | If you want to know where on the screen the bounding box of the component is you can use the | ||||||
| `toRect` method. | `toRect` method. | ||||||
| @ -307,9 +359,9 @@ For a working example, check the example in the | |||||||
|  |  | ||||||
| ## ParallaxComponent | ## ParallaxComponent | ||||||
|  |  | ||||||
| This `Component` can be used to render backgrounds with a depth feeling by drawing several transparent | This `Component` can be used to render backgrounds with a depth feeling by drawing several | ||||||
| images on top of each other, where each image or animation (`ParallaxRenderer`) is moving with a | transparent images on top of each other, where each image or animation (`ParallaxRenderer`) is | ||||||
| different velocity. | moving with a different velocity. | ||||||
|  |  | ||||||
| The rationale is that when you look at the horizon and moving, closer objects seem to move faster | The rationale is that when you look at the horizon and moving, closer objects seem to move faster | ||||||
| than distant ones. | than distant ones. | ||||||
| @ -378,8 +430,8 @@ parallax.velocityMultiplierDelta = Vector2(2.0, 1.0); | |||||||
| By default, the images are aligned to the bottom left, repeated along the X-axis and scaled | 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 | proportionally so that the image covers the height of the screen. If you want to change this | ||||||
| behavior, for example if you are not making a side-scrolling game, you can set the `repeat`, | behavior, for example if you are not making a side-scrolling game, you can set the `repeat`, | ||||||
| `alignment` and `fill` parameters for each `ParallaxRenderer` and add them to `ParallaxLayer`s that you | `alignment` and `fill` parameters for each `ParallaxRenderer` and add them to `ParallaxLayer`s that | ||||||
| then pass in to the `ParallaxComponent`'s constructor. | you then pass in to the `ParallaxComponent`'s constructor. | ||||||
|  |  | ||||||
| Advanced example: | Advanced example: | ||||||
| ```dart | ```dart | ||||||
| @ -409,13 +461,16 @@ component (`game.add(parallaxComponent`). | |||||||
| Also, don't forget to add you images to the `pubspec.yaml` file as assets or they wont be found. | Also, don't forget to add you images to the `pubspec.yaml` file as assets or they wont be found. | ||||||
|  |  | ||||||
| The `Parallax` file contains an extension of the game which adds `loadParallax`, `loadParallaxLayer` | The `Parallax` file contains an extension of the game which adds `loadParallax`, `loadParallaxLayer` | ||||||
| , `loadParallaxImage` and `loadParallaxAnimation` so that it automatically uses your game's image cache instead of the global | , `loadParallaxImage` and `loadParallaxAnimation` so that it automatically uses your game's image | ||||||
| one. The same goes for the `ParallaxComponent` file, but that provides `loadParallaxComponent`. | cache instead of the global one. The same goes for the `ParallaxComponent` file, but that provides | ||||||
|  | `loadParallaxComponent`. | ||||||
|  |  | ||||||
| If you want a fullscreen `ParallaxComponent` simply omit the `size` argument and it will take the | If you want a fullscreen `ParallaxComponent` simply omit the `size` argument and it will take the | ||||||
| size of the game, it will also resize to fullscreen when the game changes size or orientation. | size of the game, it will also resize to fullscreen when the game changes size or orientation. | ||||||
|  |  | ||||||
| Flame provides two kinds of `ParallaxRenderer`: `ParallaxImage` and `ParallaxAnimation`, `ParallaxImage` is a static image renderer and `ParallaxAnimation` is, as it's name implies, an animation and frame based renderer. | Flame provides two kinds of `ParallaxRenderer`: `ParallaxImage` and `ParallaxAnimation`, | ||||||
|  | `ParallaxImage` is a static image renderer and `ParallaxAnimation` is, as it's name implies, an | ||||||
|  | animation and frame based renderer. | ||||||
| It is also possible to create custom renderers by extending the `ParallaxRenderer` class. | It is also possible to create custom renderers by extending the `ParallaxRenderer` class. | ||||||
|  |  | ||||||
| Three example implementations can be found in the | Three example implementations can be found in the | ||||||
| @ -431,8 +486,8 @@ the shape of the specific component, it also takes all the arguments that can be | |||||||
| There are three implementations of `ShapeComponent`, which are the following: | There are three implementations of `ShapeComponent`, which are the following: | ||||||
|  |  | ||||||
| ### CircleComponent | ### CircleComponent | ||||||
| A `CircleComponent` can be created only by defining its `radius`, but you most likely want to pass it | A `CircleComponent` can be created only by defining its `radius`, but you most likely want to pass | ||||||
| a `position` and maybe `paint` (the default is white) too. | it a `position` and maybe `paint` (the default is white) too. | ||||||
|  |  | ||||||
| Example: | Example: | ||||||
| ```dart | ```dart | ||||||
|  | |||||||
| @ -105,11 +105,11 @@ class JoystickAdvancedGame extends FlameGame with HasDraggables, HasTappables { | |||||||
|       style: TextStyle(color: BasicPalette.white.color), |       style: TextStyle(color: BasicPalette.white.color), | ||||||
|     ); |     ); | ||||||
|     speedText = TextComponent( |     speedText = TextComponent( | ||||||
|       'Speed: 0', |       text: 'Speed: 0', | ||||||
|       textRenderer: _regular, |       textRenderer: _regular, | ||||||
|     )..isHud = true; |     )..isHud = true; | ||||||
|     directionText = TextComponent( |     directionText = TextComponent( | ||||||
|       'Direction: idle', |       text: 'Direction: idle', | ||||||
|       textRenderer: _regular, |       textRenderer: _regular, | ||||||
|     )..isHud = true; |     )..isHud = true; | ||||||
|  |  | ||||||
|  | |||||||
| @ -20,8 +20,8 @@ class AdvancedParallaxGame extends FlameGame { | |||||||
|         velocityMultiplier: Vector2(e.value, 1.0), |         velocityMultiplier: Vector2(e.value, 1.0), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|     final parallax = ParallaxComponent.fromParallax( |     final parallax = ParallaxComponent( | ||||||
|       Parallax( |       parallax: Parallax( | ||||||
|         await Future.wait(layers), |         await Future.wait(layers), | ||||||
|         baseVelocity: Vector2(20, 0), |         baseVelocity: Vector2(20, 0), | ||||||
|       ), |       ), | ||||||
|  | |||||||
| @ -39,7 +39,7 @@ class AnimationParallaxGame extends FlameGame { | |||||||
|       baseVelocity: Vector2(20, 0), |       baseVelocity: Vector2(20, 0), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     final parallaxComponent = ParallaxComponent.fromParallax(parallax); |     final parallaxComponent = ParallaxComponent(parallax: parallax); | ||||||
|     add(parallaxComponent); |     add(parallaxComponent); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -65,6 +65,6 @@ class SandBoxLayerParallaxGame extends FlameGame { | |||||||
|       baseVelocity: Vector2(20, 0), |       baseVelocity: Vector2(20, 0), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     add(ParallaxComponent.fromParallax(parallax)); |     add(ParallaxComponent(parallax: parallax)); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -30,7 +30,7 @@ final _shaded = TextPaint( | |||||||
| class MyTextBox extends TextBoxComponent { | class MyTextBox extends TextBoxComponent { | ||||||
|   MyTextBox(String text) |   MyTextBox(String text) | ||||||
|       : super( |       : super( | ||||||
|           text, |           text: text, | ||||||
|           textRenderer: _box, |           textRenderer: _box, | ||||||
|           boxConfig: TextBoxConfig( |           boxConfig: TextBoxConfig( | ||||||
|             maxWidth: 400, |             maxWidth: 400, | ||||||
| @ -52,26 +52,26 @@ class TextGame extends FlameGame { | |||||||
|   Future<void> onLoad() async { |   Future<void> onLoad() async { | ||||||
|     await super.onLoad(); |     await super.onLoad(); | ||||||
|     add( |     add( | ||||||
|       TextComponent('Hello, Flame', textRenderer: _regular) |       TextComponent(text: 'Hello, Flame', textRenderer: _regular) | ||||||
|         ..anchor = Anchor.topCenter |         ..anchor = Anchor.topCenter | ||||||
|         ..x = size.x / 2 |         ..x = size.x / 2 | ||||||
|         ..y = 32.0, |         ..y = 32.0, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     add( |     add( | ||||||
|       TextComponent('Text with shade', textRenderer: _shaded) |       TextComponent(text: 'Text with shade', textRenderer: _shaded) | ||||||
|         ..anchor = Anchor.topRight |         ..anchor = Anchor.topRight | ||||||
|         ..position = size - Vector2.all(100), |         ..position = size - Vector2.all(100), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     add( |     add( | ||||||
|       TextComponent('center', textRenderer: _tiny) |       TextComponent(text: 'center', textRenderer: _tiny) | ||||||
|         ..anchor = Anchor.center |         ..anchor = Anchor.center | ||||||
|         ..position.setFrom(size / 2), |         ..position.setFrom(size / 2), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     add( |     add( | ||||||
|       TextComponent('bottomRight', textRenderer: _tiny) |       TextComponent(text: 'bottomRight', textRenderer: _tiny) | ||||||
|         ..anchor = Anchor.bottomRight |         ..anchor = Anchor.bottomRight | ||||||
|         ..position.setFrom(size), |         ..position.setFrom(size), | ||||||
|     ); |     ); | ||||||
|  | |||||||
| @ -39,8 +39,8 @@ class SpritebatchGame extends FlameGame { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     add( |     add( | ||||||
|       SpriteBatchComponent.fromSpriteBatch( |       SpriteBatchComponent( | ||||||
|         spriteBatch, |         spriteBatch: spriteBatch, | ||||||
|         blendMode: BlendMode.srcOver, |         blendMode: BlendMode.srcOver, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|  | |||||||
| @ -33,6 +33,7 @@ | |||||||
|  - Removed methods `preRender()` and `postRender()` in `Component` |  - Removed methods `preRender()` and `postRender()` in `Component` | ||||||
|  - Use `FlameTester` everywhere where it makes sense in the tests |  - Use `FlameTester` everywhere where it makes sense in the tests | ||||||
|  - Improved `IsometricTileMap` |  - Improved `IsometricTileMap` | ||||||
|  |  - Initialization of all `PositionComponent`s can be done from `onLoad` instead of the constructor | ||||||
|  - Rename `HasTappableComponents` to `HasTappables` |  - Rename `HasTappableComponents` to `HasTappables` | ||||||
|  - Rename `HasDraggableComponents` to `HasDraggables` |  - Rename `HasDraggableComponents` to `HasDraggables` | ||||||
|  - Rename `HasHoverableComponents` to `HasHoverableis` |  - Rename `HasHoverableComponents` to `HasHoverableis` | ||||||
|  | |||||||
| @ -11,13 +11,13 @@ export '../nine_tile_box.dart'; | |||||||
|  |  | ||||||
| /// This class is a thin wrapper on top of [NineTileBox] as a component. | /// This class is a thin wrapper on top of [NineTileBox] as a component. | ||||||
| class NineTileBoxComponent extends PositionComponent { | class NineTileBoxComponent extends PositionComponent { | ||||||
|   NineTileBox nineTileBox; |   NineTileBox? nineTileBox; | ||||||
|  |  | ||||||
|   /// Takes the [NineTileBox] instance used to render this box. |   /// Takes the [NineTileBox] instance used to render this box. | ||||||
|   /// |   /// | ||||||
|   /// It uses the x, y, width and height coordinates from the [PositionComponent] to render. |   /// It uses the x, y, width and height coordinates from the [PositionComponent] to render. | ||||||
|   NineTileBoxComponent( |   NineTileBoxComponent({ | ||||||
|     this.nineTileBox, { |     this.nineTileBox, | ||||||
|     Vector2? position, |     Vector2? position, | ||||||
|     Vector2? size, |     Vector2? size, | ||||||
|     Vector2? scale, |     Vector2? scale, | ||||||
| @ -33,9 +33,18 @@ class NineTileBoxComponent extends PositionComponent { | |||||||
|           priority: priority, |           priority: priority, | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   @mustCallSuper | ||||||
|  |   void onMount() { | ||||||
|  |     assert( | ||||||
|  |       nineTileBox != null, | ||||||
|  |       'The nineTileBox should be set either in the constructor or in onLoad', | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @mustCallSuper |   @mustCallSuper | ||||||
|   @override |   @override | ||||||
|   void render(Canvas c) { |   void render(Canvas c) { | ||||||
|     nineTileBox.drawRect(c, size.toRect()); |     nineTileBox?.drawRect(c, size.toRect()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ import 'position_component.dart'; | |||||||
|  |  | ||||||
| extension ParallaxComponentExtension on FlameGame { | extension ParallaxComponentExtension on FlameGame { | ||||||
|   Future<ParallaxComponent> loadParallaxComponent( |   Future<ParallaxComponent> loadParallaxComponent( | ||||||
|     List<ParallaxData> dataList, { |     Iterable<ParallaxData> dataList, { | ||||||
|     Vector2? baseVelocity, |     Vector2? baseVelocity, | ||||||
|     Vector2? velocityMultiplierDelta, |     Vector2? velocityMultiplierDelta, | ||||||
|     ImageRepeat repeat = ImageRepeat.repeatX, |     ImageRepeat repeat = ImageRepeat.repeatX, | ||||||
| @ -59,42 +59,25 @@ class ParallaxComponent<T extends FlameGame> extends PositionComponent | |||||||
|  |  | ||||||
|   /// Creates a component with an empty parallax which can be set later. |   /// Creates a component with an empty parallax which can be set later. | ||||||
|   ParallaxComponent({ |   ParallaxComponent({ | ||||||
|  |     Parallax? parallax, | ||||||
|     Vector2? position, |     Vector2? position, | ||||||
|     Vector2? size, |     Vector2? size, | ||||||
|     Vector2? scale, |     Vector2? scale, | ||||||
|     double? angle, |     double? angle, | ||||||
|     Anchor? anchor, |     Anchor? anchor, | ||||||
|     int? priority, |     int? priority, | ||||||
|   })  : isFullscreen = size == null ? true : false, |   })  : _parallax = parallax, | ||||||
|  |         isFullscreen = | ||||||
|  |             size == null && !(parallax?.isSized ?? false) ? true : false, | ||||||
|         super( |         super( | ||||||
|           position: position, |           position: position, | ||||||
|           size: size, |           size: size ?? ((parallax?.isSized ?? false) ? parallax?.size : null), | ||||||
|           scale: scale, |           scale: scale, | ||||||
|           angle: angle, |           angle: angle, | ||||||
|           anchor: anchor, |           anchor: anchor, | ||||||
|           priority: priority, |           priority: priority, | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|   /// Creates a component from a [Parallax] object. |  | ||||||
|   factory ParallaxComponent.fromParallax( |  | ||||||
|     Parallax parallax, { |  | ||||||
|     Vector2? position, |  | ||||||
|     Vector2? size, |  | ||||||
|     Vector2? scale, |  | ||||||
|     double? angle, |  | ||||||
|     Anchor? anchor, |  | ||||||
|     int? priority, |  | ||||||
|   }) { |  | ||||||
|     return ParallaxComponent( |  | ||||||
|       position: position, |  | ||||||
|       size: size ?? (parallax.isSized ? parallax.size : null), |  | ||||||
|       scale: scale, |  | ||||||
|       angle: angle, |  | ||||||
|       anchor: anchor, |  | ||||||
|       priority: priority, |  | ||||||
|     )..parallax = parallax; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @mustCallSuper |   @mustCallSuper | ||||||
|   @override |   @override | ||||||
|   void onGameResize(Vector2 size) { |   void onGameResize(Vector2 size) { | ||||||
| @ -107,6 +90,14 @@ class ParallaxComponent<T extends FlameGame> extends PositionComponent | |||||||
|     parallax?.resize(newSize); |     parallax?.resize(newSize); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void onMount() { | ||||||
|  |     assert( | ||||||
|  |       parallax != null, | ||||||
|  |       'The parallax needs to be set in either the constructor or in onLoad', | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void update(double dt) { |   void update(double dt) { | ||||||
|     super.update(dt); |     super.update(dt); | ||||||
| @ -139,7 +130,7 @@ class ParallaxComponent<T extends FlameGame> extends PositionComponent | |||||||
|   /// |   /// | ||||||
|   /// If no image cache is set, the global flame cache is used. |   /// If no image cache is set, the global flame cache is used. | ||||||
|   static Future<ParallaxComponent> load( |   static Future<ParallaxComponent> load( | ||||||
|     List<ParallaxData> dataList, { |     Iterable<ParallaxData> dataList, { | ||||||
|     Vector2? baseVelocity, |     Vector2? baseVelocity, | ||||||
|     Vector2? velocityMultiplierDelta, |     Vector2? velocityMultiplierDelta, | ||||||
|     ImageRepeat repeat = ImageRepeat.repeatX, |     ImageRepeat repeat = ImageRepeat.repeatX, | ||||||
| @ -153,8 +144,8 @@ class ParallaxComponent<T extends FlameGame> extends PositionComponent | |||||||
|     Anchor? anchor, |     Anchor? anchor, | ||||||
|     int? priority, |     int? priority, | ||||||
|   }) async { |   }) async { | ||||||
|     return ParallaxComponent.fromParallax( |     return ParallaxComponent( | ||||||
|       await Parallax.load( |       parallax: await Parallax.load( | ||||||
|         dataList, |         dataList, | ||||||
|         size: size, |         size: size, | ||||||
|         baseVelocity: baseVelocity, |         baseVelocity: baseVelocity, | ||||||
|  | |||||||
| @ -1,5 +1,7 @@ | |||||||
| import 'dart:ui'; | import 'dart:ui'; | ||||||
|  |  | ||||||
|  | import 'package:meta/meta.dart'; | ||||||
|  |  | ||||||
| import '../sprite_batch.dart'; | import '../sprite_batch.dart'; | ||||||
| import 'component.dart'; | import 'component.dart'; | ||||||
|  |  | ||||||
| @ -10,15 +12,22 @@ class SpriteBatchComponent extends Component { | |||||||
|   Paint? paint; |   Paint? paint; | ||||||
|  |  | ||||||
|   /// Creates a component with an empty sprite batch which can be set later |   /// Creates a component with an empty sprite batch which can be set later | ||||||
|   SpriteBatchComponent(); |   SpriteBatchComponent({ | ||||||
|  |     this.spriteBatch, | ||||||
|   SpriteBatchComponent.fromSpriteBatch( |  | ||||||
|     this.spriteBatch, { |  | ||||||
|     this.blendMode, |     this.blendMode, | ||||||
|     this.cullRect, |     this.cullRect, | ||||||
|     this.paint, |     this.paint, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   @mustCallSuper | ||||||
|  |   void onMount() { | ||||||
|  |     assert( | ||||||
|  |       spriteBatch != null, | ||||||
|  |       'You have to set spriteBatch in either the constructor or in onLoad', | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void render(Canvas canvas) { |   void render(Canvas canvas) { | ||||||
|     spriteBatch?.render( |     spriteBatch?.render( | ||||||
|  | |||||||
| @ -68,6 +68,15 @@ class SpriteComponent extends PositionComponent with HasPaint { | |||||||
|           priority: priority, |           priority: priority, | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   @mustCallSuper | ||||||
|  |   void onMount() { | ||||||
|  |     assert( | ||||||
|  |       sprite != null, | ||||||
|  |       'You have to set the sprite in either the constructor or in onLoad', | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @mustCallSuper |   @mustCallSuper | ||||||
|   @override |   @override | ||||||
|   void render(Canvas canvas) { |   void render(Canvas canvas) { | ||||||
|  | |||||||
| @ -45,6 +45,19 @@ class SpriteGroupComponent<T> extends PositionComponent with HasPaint { | |||||||
|  |  | ||||||
|   Sprite? get sprite => sprites?[current]; |   Sprite? get sprite => sprites?[current]; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   @mustCallSuper | ||||||
|  |   void onMount() { | ||||||
|  |     assert( | ||||||
|  |       sprites != null, | ||||||
|  |       'You have to set the sprites in either the constructor or in onLoad', | ||||||
|  |     ); | ||||||
|  |     assert( | ||||||
|  |       current != null, | ||||||
|  |       'You have to set current in either the constructor or in onLoad', | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @mustCallSuper |   @mustCallSuper | ||||||
|   @override |   @override | ||||||
|   void render(Canvas canvas) { |   void render(Canvas canvas) { | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ import 'dart:math' as math; | |||||||
| import 'dart:ui'; | import 'dart:ui'; | ||||||
|  |  | ||||||
| import 'package:flutter/widgets.dart' hide Image; | import 'package:flutter/widgets.dart' hide Image; | ||||||
|  | import 'package:meta/meta.dart'; | ||||||
|  |  | ||||||
| import '../../components.dart'; | import '../../components.dart'; | ||||||
| import '../extensions/vector2.dart'; | import '../extensions/vector2.dart'; | ||||||
| @ -45,16 +46,13 @@ class TextBoxConfig { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| class TextBoxComponent<T extends TextRenderer> extends PositionComponent { | class TextBoxComponent<T extends TextRenderer> extends TextComponent { | ||||||
|   static final Paint _imagePaint = BasicPalette.white.paint() |   static final Paint _imagePaint = BasicPalette.white.paint() | ||||||
|     ..filterQuality = FilterQuality.high; |     ..filterQuality = FilterQuality.high; | ||||||
|  |  | ||||||
|   final String _text; |  | ||||||
|   final T _textRenderer; |  | ||||||
|   final TextBoxConfig _boxConfig; |   final TextBoxConfig _boxConfig; | ||||||
|   final double pixelRatio; |   final double pixelRatio; | ||||||
|  |  | ||||||
|   late List<String> _lines; |   final List<String> _lines = []; | ||||||
|   double _maxLineWidth = 0.0; |   double _maxLineWidth = 0.0; | ||||||
|   late double _lineHeight; |   late double _lineHeight; | ||||||
|   late int _totalLines; |   late int _totalLines; | ||||||
| @ -63,14 +61,10 @@ class TextBoxComponent<T extends TextRenderer> extends PositionComponent { | |||||||
|   Image? _cache; |   Image? _cache; | ||||||
|   int? _previousChar; |   int? _previousChar; | ||||||
|  |  | ||||||
|   String get text => _text; |  | ||||||
|  |  | ||||||
|   TextRenderer get renderer => _textRenderer; |  | ||||||
|  |  | ||||||
|   TextBoxConfig get boxConfig => _boxConfig; |   TextBoxConfig get boxConfig => _boxConfig; | ||||||
|  |  | ||||||
|   TextBoxComponent( |   TextBoxComponent({ | ||||||
|     String text, { |     String? text, | ||||||
|     T? textRenderer, |     T? textRenderer, | ||||||
|     TextBoxConfig? boxConfig, |     TextBoxConfig? boxConfig, | ||||||
|     double? pixelRatio, |     double? pixelRatio, | ||||||
| @ -79,24 +73,35 @@ class TextBoxComponent<T extends TextRenderer> extends PositionComponent { | |||||||
|     double? angle, |     double? angle, | ||||||
|     Anchor? anchor, |     Anchor? anchor, | ||||||
|     int? priority, |     int? priority, | ||||||
|   })  : _text = text, |   })  : _boxConfig = boxConfig ?? TextBoxConfig(), | ||||||
|         _boxConfig = boxConfig ?? TextBoxConfig(), |  | ||||||
|         _textRenderer = textRenderer ?? TextRenderer.createDefault<T>(), |  | ||||||
|         pixelRatio = pixelRatio ?? window.devicePixelRatio, |         pixelRatio = pixelRatio ?? window.devicePixelRatio, | ||||||
|         super( |         super( | ||||||
|  |           text: text, | ||||||
|  |           textRenderer: textRenderer, | ||||||
|           position: position, |           position: position, | ||||||
|           scale: scale, |           scale: scale, | ||||||
|           angle: angle, |           angle: angle, | ||||||
|           anchor: anchor, |           anchor: anchor, | ||||||
|           priority: priority, |           priority: priority, | ||||||
|         ) { |         ); | ||||||
|     _lines = []; |  | ||||||
|  |   @override | ||||||
|  |   @mustCallSuper | ||||||
|  |   Future<void> onLoad() async { | ||||||
|  |     await super.onLoad(); | ||||||
|  |     await redraw(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   @internal | ||||||
|  |   void updateBounds() { | ||||||
|  |     _lines.clear(); | ||||||
|     double? lineHeight; |     double? lineHeight; | ||||||
|     text.split(' ').forEach((word) { |     text.split(' ').forEach((word) { | ||||||
|       final possibleLine = _lines.isEmpty ? word : '${_lines.last} $word'; |       final possibleLine = _lines.isEmpty ? word : '${_lines.last} $word'; | ||||||
|       lineHeight ??= _textRenderer.measureTextHeight(possibleLine); |       lineHeight ??= textRenderer.measureTextHeight(possibleLine); | ||||||
|  |  | ||||||
|       final textWidth = _textRenderer.measureTextWidth(possibleLine); |       final textWidth = textRenderer.measureTextWidth(possibleLine); | ||||||
|       if (textWidth <= _boxConfig.maxWidth - _boxConfig.margins.horizontal) { |       if (textWidth <= _boxConfig.maxWidth - _boxConfig.margins.horizontal) { | ||||||
|         if (_lines.isNotEmpty) { |         if (_lines.isNotEmpty) { | ||||||
|           _lines.last = possibleLine; |           _lines.last = possibleLine; | ||||||
| @ -114,19 +119,13 @@ class TextBoxComponent<T extends TextRenderer> extends PositionComponent { | |||||||
|     size = _recomputeSize(); |     size = _recomputeSize(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Future<void> onLoad() async { |  | ||||||
|     await super.onLoad(); |  | ||||||
|     await redraw(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   void _updateMaxWidth(double w) { |   void _updateMaxWidth(double w) { | ||||||
|     if (w > _maxLineWidth) { |     if (w > _maxLineWidth) { | ||||||
|       _maxLineWidth = w; |       _maxLineWidth = w; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   double get totalCharTime => _text.length * _boxConfig.timePerChar; |   double get totalCharTime => text.length * _boxConfig.timePerChar; | ||||||
|  |  | ||||||
|   bool get finished => _lifeTime > totalCharTime + _boxConfig.dismissDelay; |   bool get finished => _lifeTime > totalCharTime + _boxConfig.dismissDelay; | ||||||
|  |  | ||||||
| @ -151,7 +150,7 @@ class TextBoxComponent<T extends TextRenderer> extends PositionComponent { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   double getLineWidth(String line, int charCount) { |   double getLineWidth(String line, int charCount) { | ||||||
|     return _textRenderer.measureTextWidth( |     return textRenderer.measureTextWidth( | ||||||
|       line.substring(0, math.min(charCount, line.length)), |       line.substring(0, math.min(charCount, line.length)), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @ -222,7 +221,7 @@ class TextBoxComponent<T extends TextRenderer> extends PositionComponent { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   void _drawLine(Canvas c, String line, double dy) { |   void _drawLine(Canvas c, String line, double dy) { | ||||||
|     _textRenderer.render(c, line, Vector2(_boxConfig.margins.left, dy)); |     textRenderer.render(c, line, Vector2(_boxConfig.margins.left, dy)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> redraw() async { |   Future<void> redraw() async { | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ class TextComponent<T extends TextRenderer> extends PositionComponent { | |||||||
|   set text(String text) { |   set text(String text) { | ||||||
|     if (_text != text) { |     if (_text != text) { | ||||||
|       _text = text; |       _text = text; | ||||||
|       _updateBox(); |       updateBounds(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @ -25,11 +25,11 @@ class TextComponent<T extends TextRenderer> extends PositionComponent { | |||||||
|  |  | ||||||
|   set textRenderer(T textRenderer) { |   set textRenderer(T textRenderer) { | ||||||
|     _textRenderer = textRenderer; |     _textRenderer = textRenderer; | ||||||
|     _updateBox(); |     updateBounds(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   TextComponent( |   TextComponent({ | ||||||
|     this._text, { |     String? text, | ||||||
|     T? textRenderer, |     T? textRenderer, | ||||||
|     Vector2? position, |     Vector2? position, | ||||||
|     Vector2? size, |     Vector2? size, | ||||||
| @ -37,7 +37,8 @@ class TextComponent<T extends TextRenderer> extends PositionComponent { | |||||||
|     double? angle, |     double? angle, | ||||||
|     Anchor? anchor, |     Anchor? anchor, | ||||||
|     int? priority, |     int? priority, | ||||||
|   })  : _textRenderer = textRenderer ?? TextRenderer.createDefault<T>(), |   })  : _text = text ?? '', | ||||||
|  |         _textRenderer = textRenderer ?? TextRenderer.createDefault<T>(), | ||||||
|         super( |         super( | ||||||
|           position: position, |           position: position, | ||||||
|           size: size, |           size: size, | ||||||
| @ -46,15 +47,15 @@ class TextComponent<T extends TextRenderer> extends PositionComponent { | |||||||
|           anchor: anchor, |           anchor: anchor, | ||||||
|           priority: priority, |           priority: priority, | ||||||
|         ) { |         ) { | ||||||
|     _updateBox(); |     updateBounds(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void _updateBox() { |   @internal | ||||||
|  |   void updateBounds() { | ||||||
|     final expectedSize = textRenderer.measureText(_text); |     final expectedSize = textRenderer.measureText(_text); | ||||||
|     size.setValues(expectedSize.x, expectedSize.y); |     size.setValues(expectedSize.x, expectedSize.y); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @mustCallSuper |  | ||||||
|   @override |   @override | ||||||
|   void render(Canvas canvas) { |   void render(Canvas canvas) { | ||||||
|     _textRenderer.render(canvas, text, Vector2.zero()); |     _textRenderer.render(canvas, text, Vector2.zero()); | ||||||
|  | |||||||
| @ -472,7 +472,7 @@ class Parallax { | |||||||
|   /// used can also be passed in. |   /// used can also be passed in. | ||||||
|   /// If no image cache is set, the global flame cache is used. |   /// If no image cache is set, the global flame cache is used. | ||||||
|   static Future<Parallax> load( |   static Future<Parallax> load( | ||||||
|     List<ParallaxData> dataList, { |     Iterable<ParallaxData> dataList, { | ||||||
|     Vector2? size, |     Vector2? size, | ||||||
|     Vector2? baseVelocity, |     Vector2? baseVelocity, | ||||||
|     Vector2? velocityMultiplierDelta, |     Vector2? velocityMultiplierDelta, | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ void main() { | |||||||
|   group('TextBoxComponent', () { |   group('TextBoxComponent', () { | ||||||
|     test('size is properly computed', () async { |     test('size is properly computed', () async { | ||||||
|       final c = TextBoxComponent( |       final c = TextBoxComponent( | ||||||
|         'The quick brown fox jumps over the lazy dog.', |         text: 'The quick brown fox jumps over the lazy dog.', | ||||||
|         boxConfig: TextBoxConfig( |         boxConfig: TextBoxConfig( | ||||||
|           maxWidth: 100.0, |           maxWidth: 100.0, | ||||||
|         ), |         ), | ||||||
| @ -21,7 +21,7 @@ void main() { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     flameGame.test('onLoad waits for cache to be done', (game) async { |     flameGame.test('onLoad waits for cache to be done', (game) async { | ||||||
|       final c = TextBoxComponent('foo bar'); |       final c = TextBoxComponent(text: 'foo bar'); | ||||||
|  |  | ||||||
|       await game.ensureAdd(c); |       await game.ensureAdd(c); | ||||||
|  |  | ||||||
|  | |||||||
| @ -157,27 +157,14 @@ void main() { | |||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   flameGame.test('remove depend SpriteComponent.shouldRemove', (game) async { |   flameGame.test('removes PositionComponent when shouldRemove is true', | ||||||
|     // addLater here |       (game) async { | ||||||
|     await game.ensureAdd(SpriteComponent()..shouldRemove = true); |     await game.ensureAdd(PositionComponent()..shouldRemove = true); | ||||||
|     expect(game.children.length, equals(1)); |     expect(game.children.length, equals(1)); | ||||||
|  |  | ||||||
|     // remove effected here |  | ||||||
|     game.update(0); |     game.update(0); | ||||||
|     expect(game.children.isEmpty, equals(true)); |     expect(game.children.isEmpty, equals(true)); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   flameGame.test( |  | ||||||
|     'remove depend SpriteAnimationComponent.shouldRemove', |  | ||||||
|     (game) async { |  | ||||||
|       await game.ensureAdd(SpriteAnimationComponent()..shouldRemove = true); |  | ||||||
|       expect(game.children.length, equals(1)); |  | ||||||
|  |  | ||||||
|       game.update(0); |  | ||||||
|       expect(game.children.isEmpty, equals(true)); |  | ||||||
|     }, |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   flameGame.test('clear removes all components', (game) async { |   flameGame.test('clear removes all components', (game) async { | ||||||
|     final components = List.generate(3, (index) => Component()); |     final components = List.generate(3, (index) => Component()); | ||||||
|     await game.ensureAddAll(components); |     await game.ensureAddAll(components); | ||||||
|  | |||||||
| @ -45,7 +45,7 @@ void main() { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     test('change parameters of text component', () { |     test('change parameters of text component', () { | ||||||
|       final tc = TextComponent<TextPaint>('foo'); |       final tc = TextComponent<TextPaint>(text: 'foo'); | ||||||
|       tc.textRenderer = tc.textRenderer.copyWith( |       tc.textRenderer = tc.textRenderer.copyWith( | ||||||
|         (c) => c.copyWith(fontSize: 200), |         (c) => c.copyWith(fontSize: 200), | ||||||
|       ); |       ); | ||||||
| @ -55,12 +55,12 @@ void main() { | |||||||
|     test('custom renderer', () { |     test('custom renderer', () { | ||||||
|       TextRenderer.defaultRenderersRegistry[_CustomTextRenderer] = |       TextRenderer.defaultRenderersRegistry[_CustomTextRenderer] = | ||||||
|           () => _CustomTextRenderer(); |           () => _CustomTextRenderer(); | ||||||
|       final tc = TextComponent<_CustomTextRenderer>('foo'); |       final tc = TextComponent<_CustomTextRenderer>(text: 'foo'); | ||||||
|       expect(tc.textRenderer, isA<_CustomTextRenderer>()); |       expect(tc.textRenderer, isA<_CustomTextRenderer>()); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     test('text component size is set', () { |     test('text component size is set', () { | ||||||
|       final t = TextComponent('foobar'); |       final t = TextComponent(text: 'foobar'); | ||||||
|       expect(t.size, isNot(equals(Vector2.zero()))); |       expect(t.size, isNot(equals(Vector2.zero()))); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|  | |||||||
| @ -42,7 +42,10 @@ class TapableBall extends Ball with Tappable { | |||||||
|   Future<void> onLoad() async { |   Future<void> onLoad() async { | ||||||
|     super.onLoad(); |     super.onLoad(); | ||||||
|     _textPaint = TextPaint(style: _textStyle); |     _textPaint = TextPaint(style: _textStyle); | ||||||
|     textComponent = TextComponent(counter.toString(), textRenderer: _textPaint); |     textComponent = TextComponent( | ||||||
|  |       text: counter.toString(), | ||||||
|  |       textRenderer: _textPaint, | ||||||
|  |     ); | ||||||
|     add(textComponent); |     add(textComponent); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Lukas Klingsbo
					Lukas Klingsbo