mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-02 11:43:19 +08:00
Adding animation support to parallax (#835)
* Adding animation support to parallax * Solving workaround * Fixing image composition add assert * adding docs, linting and a better example * lint * Apply suggestions from code review Co-authored-by: Jochum van der Ploeg <jochum@vdploeg.net> * Update examples/lib/stories/parallax/sandbox_layer.dart * Update doc/components.md * Update examples/lib/stories/parallax/sandbox_layer.dart Co-authored-by: Luan Nico <luanpotter27@gmail.com> * Update examples/lib/stories/parallax/animation.dart * formating * Update .min_coverage Co-authored-by: Jochum van der Ploeg <jochum@vdploeg.net> Co-authored-by: Luan Nico <luanpotter27@gmail.com>
This commit is contained in:
@ -276,8 +276,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 transparent
|
||||||
images on top of each other, where each image is moving with a different velocity.
|
images on top of each other, where each image or animation (`ParallaxRenderer`) is 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.
|
||||||
@ -289,7 +290,10 @@ The simplest `ParallaxComponent` is created like this:
|
|||||||
```dart
|
```dart
|
||||||
@override
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
final parallaxComponent = await loadParallaxComponent(['bg.png', 'trees.png']);
|
final parallaxComponent = await loadParallaxComponent([
|
||||||
|
ParallaxImageData('bg.png'),
|
||||||
|
ParallaxImageData('trees.png'),
|
||||||
|
]);
|
||||||
add(parallax);
|
add(parallax);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -300,7 +304,10 @@ A ParallaxComponent can also "load itself" by implementing the `onLoad` method:
|
|||||||
class MyParallaxComponent extends ParallaxComponent with HasGameRef<MyGame> {
|
class MyParallaxComponent extends ParallaxComponent with HasGameRef<MyGame> {
|
||||||
@override
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
parallax = await gameRef.loadParallax(['bg.png', 'trees.png']);
|
parallax = await gameRef.loadParallax([
|
||||||
|
ParallaxImageData('bg.png'),
|
||||||
|
ParallaxImageData('trees.png'),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +330,7 @@ For example if you want to move your background images along the X-axis with a f
|
|||||||
|
|
||||||
```dart
|
```dart
|
||||||
final parallaxComponent = await loadParallaxComponent(
|
final parallaxComponent = await loadParallaxComponent(
|
||||||
_paths,
|
_dataList,
|
||||||
baseVelocity: Vector2(20, 0),
|
baseVelocity: Vector2(20, 0),
|
||||||
velocityMultiplierDelta: Vector2(1.8, 1.0),
|
velocityMultiplierDelta: Vector2(1.8, 1.0),
|
||||||
);
|
);
|
||||||
@ -341,7 +348,7 @@ 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 `ParallaxImage` and add them to `ParallaxLayer`s that you
|
`alignment` and `fill` parameters for each `ParallaxRenderer` and add them to `ParallaxLayer`s that you
|
||||||
then pass in to the `ParallaxComponent`'s constructor.
|
then pass in to the `ParallaxComponent`'s constructor.
|
||||||
|
|
||||||
Advanced example:
|
Advanced example:
|
||||||
@ -372,12 +379,15 @@ 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`
|
||||||
and `loadParallaxImage` 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 cache instead of the global
|
||||||
one. The same goes for the `ParallaxComponent` file, but that provides `loadParallaxComponent`.
|
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.
|
||||||
|
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
|
||||||
[examples directory](https://github.com/flame-engine/flame/tree/main/examples/lib/stories/parallax).
|
[examples directory](https://github.com/flame-engine/flame/tree/main/examples/lib/stories/parallax).
|
||||||
|
|
||||||
|
|||||||
BIN
examples/assets/images/parallax/airplane.png
Normal file
BIN
examples/assets/images/parallax/airplane.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
examples/assets/images/parallax/city.png
Normal file
BIN
examples/assets/images/parallax/city.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 378 B |
BIN
examples/assets/images/parallax/heavy_clouded.png
Normal file
BIN
examples/assets/images/parallax/heavy_clouded.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
BIN
examples/assets/images/parallax/rain.png
Normal file
BIN
examples/assets/images/parallax/rain.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@ -15,7 +15,7 @@ class AdvancedParallaxGame extends BaseGame {
|
|||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
final layers = _layersMeta.entries.map(
|
final layers = _layersMeta.entries.map(
|
||||||
(e) => loadParallaxLayer(
|
(e) => loadParallaxLayer(
|
||||||
e.key,
|
ParallaxImageData(e.key),
|
||||||
velocityMultiplier: Vector2(e.value, 1.0),
|
velocityMultiplier: Vector2(e.value, 1.0),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
44
examples/lib/stories/parallax/animation.dart
Normal file
44
examples/lib/stories/parallax/animation.dart
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flame/parallax.dart';
|
||||||
|
import 'package:flutter/painting.dart';
|
||||||
|
|
||||||
|
class AnimationParallaxGame extends BaseGame {
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
final cityLayer = await loadParallaxLayer(
|
||||||
|
ParallaxImageData('parallax/city.png'),
|
||||||
|
);
|
||||||
|
|
||||||
|
final rainLayer = await loadParallaxLayer(
|
||||||
|
ParallaxAnimationData(
|
||||||
|
'parallax/rain.png',
|
||||||
|
SpriteAnimationData.sequenced(
|
||||||
|
amount: 4,
|
||||||
|
stepTime: 0.3,
|
||||||
|
textureSize: Vector2(80, 160),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
velocityMultiplier: Vector2(2, 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
final cloudsLayer = await loadParallaxLayer(
|
||||||
|
ParallaxImageData('parallax/heavy_clouded.png'),
|
||||||
|
velocityMultiplier: Vector2(4, 0),
|
||||||
|
fill: LayerFill.none,
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
);
|
||||||
|
|
||||||
|
final parallax = Parallax(
|
||||||
|
[
|
||||||
|
cityLayer,
|
||||||
|
rainLayer,
|
||||||
|
cloudsLayer,
|
||||||
|
],
|
||||||
|
baseVelocity: Vector2(20, 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
final parallaxComponent = ParallaxComponent.fromParallax(parallax);
|
||||||
|
add(parallaxComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,14 @@
|
|||||||
import 'package:flame/components.dart';
|
import 'package:flame/components.dart';
|
||||||
import 'package:flame/game.dart';
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flame/parallax.dart';
|
||||||
|
|
||||||
class BasicParallaxGame extends BaseGame {
|
class BasicParallaxGame extends BaseGame {
|
||||||
final _imageNames = [
|
final _imageNames = [
|
||||||
'parallax/bg.png',
|
ParallaxImageData('parallax/bg.png'),
|
||||||
'parallax/mountain-far.png',
|
ParallaxImageData('parallax/mountain-far.png'),
|
||||||
'parallax/mountains.png',
|
ParallaxImageData('parallax/mountains.png'),
|
||||||
'parallax/trees.png',
|
ParallaxImageData('parallax/trees.png'),
|
||||||
'parallax/foreground-trees.png',
|
ParallaxImageData('parallax/foreground-trees.png'),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -15,11 +15,11 @@ class MyParallaxComponent extends ParallaxComponent
|
|||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
parallax = await gameRef.loadParallax(
|
parallax = await gameRef.loadParallax(
|
||||||
[
|
[
|
||||||
'parallax/bg.png',
|
ParallaxImageData('parallax/bg.png'),
|
||||||
'parallax/mountain-far.png',
|
ParallaxImageData('parallax/mountain-far.png'),
|
||||||
'parallax/mountains.png',
|
ParallaxImageData('parallax/mountains.png'),
|
||||||
'parallax/trees.png',
|
ParallaxImageData('parallax/trees.png'),
|
||||||
'parallax/foreground-trees.png',
|
ParallaxImageData('parallax/foreground-trees.png'),
|
||||||
],
|
],
|
||||||
baseVelocity: Vector2(20, 0),
|
baseVelocity: Vector2(20, 0),
|
||||||
velocityMultiplierDelta: Vector2(1.8, 1.0),
|
velocityMultiplierDelta: Vector2(1.8, 1.0),
|
||||||
|
|||||||
@ -16,11 +16,11 @@ class NoFCSParallaxGame extends Game {
|
|||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
parallax = await loadParallax(
|
parallax = await loadParallax(
|
||||||
[
|
[
|
||||||
'parallax/bg.png',
|
ParallaxImageData('parallax/bg.png'),
|
||||||
'parallax/mountain-far.png',
|
ParallaxImageData('parallax/mountain-far.png'),
|
||||||
'parallax/mountains.png',
|
ParallaxImageData('parallax/mountains.png'),
|
||||||
'parallax/trees.png',
|
ParallaxImageData('parallax/trees.png'),
|
||||||
'parallax/foreground-trees.png',
|
ParallaxImageData('parallax/foreground-trees.png'),
|
||||||
],
|
],
|
||||||
size: size,
|
size: size,
|
||||||
baseVelocity: Vector2(20, 0),
|
baseVelocity: Vector2(20, 0),
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
import 'package:dashbook/dashbook.dart';
|
import 'package:dashbook/dashbook.dart';
|
||||||
import 'package:flame/game.dart';
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flame/parallax.dart';
|
||||||
|
import 'package:flutter/painting.dart';
|
||||||
|
|
||||||
import '../../commons/commons.dart';
|
import '../../commons/commons.dart';
|
||||||
import 'advanced.dart';
|
import 'advanced.dart';
|
||||||
|
import 'animation.dart';
|
||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
import 'component.dart';
|
import 'component.dart';
|
||||||
import 'no_fcs.dart';
|
import 'no_fcs.dart';
|
||||||
|
import 'sandbox_layer.dart';
|
||||||
import 'small_parallax.dart';
|
import 'small_parallax.dart';
|
||||||
|
|
||||||
void addParallaxStories(Dashbook dashbook) {
|
void addParallaxStories(Dashbook dashbook) {
|
||||||
@ -23,6 +27,12 @@ void addParallaxStories(Dashbook dashbook) {
|
|||||||
info: 'Shows how to do initiation and loading of assets from within an '
|
info: 'Shows how to do initiation and loading of assets from within an '
|
||||||
'extended ParallaxComponent',
|
'extended ParallaxComponent',
|
||||||
)
|
)
|
||||||
|
..add(
|
||||||
|
'Animation',
|
||||||
|
(_) => GameWidget(game: AnimationParallaxGame()),
|
||||||
|
codeLink: baseLink('parallax/animation.dart'),
|
||||||
|
info: 'Shows how to use animations in a parallax',
|
||||||
|
)
|
||||||
..add(
|
..add(
|
||||||
'Non-fullscreen',
|
'Non-fullscreen',
|
||||||
(_) => GameWidget(game: SmallParallaxGame()),
|
(_) => GameWidget(game: SmallParallaxGame()),
|
||||||
@ -42,5 +52,45 @@ void addParallaxStories(Dashbook dashbook) {
|
|||||||
codeLink: baseLink('parallax/advanced.dart'),
|
codeLink: baseLink('parallax/advanced.dart'),
|
||||||
info: 'Shows how to create a parallax with different velocity deltas on '
|
info: 'Shows how to create a parallax with different velocity deltas on '
|
||||||
'each layer',
|
'each layer',
|
||||||
|
)
|
||||||
|
..add(
|
||||||
|
'Layer sandbox',
|
||||||
|
(context) {
|
||||||
|
return GameWidget(
|
||||||
|
game: SandBoxLayerParallaxGame(
|
||||||
|
planeSpeed: Vector2(
|
||||||
|
context.numberProperty('plane x speed', 0),
|
||||||
|
context.numberProperty('plane y speed', 0),
|
||||||
|
),
|
||||||
|
planeRepeat: context.listProperty(
|
||||||
|
'plane repeat strategy',
|
||||||
|
ImageRepeat.noRepeat,
|
||||||
|
ImageRepeat.values,
|
||||||
|
),
|
||||||
|
planeFill: context.listProperty(
|
||||||
|
'plane fill strategy',
|
||||||
|
LayerFill.none,
|
||||||
|
LayerFill.values,
|
||||||
|
),
|
||||||
|
planeAlignment: context.listProperty(
|
||||||
|
'plane alignment strategy',
|
||||||
|
Alignment.center,
|
||||||
|
[
|
||||||
|
Alignment.topLeft,
|
||||||
|
Alignment.topRight,
|
||||||
|
Alignment.center,
|
||||||
|
Alignment.topCenter,
|
||||||
|
Alignment.centerLeft,
|
||||||
|
Alignment.bottomLeft,
|
||||||
|
Alignment.bottomRight,
|
||||||
|
Alignment.bottomCenter,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
codeLink: baseLink('parallax/sandbox_layer.dart'),
|
||||||
|
info: 'In this example, properties of a layer can be changed to preview '
|
||||||
|
'the different combination of values',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
69
examples/lib/stories/parallax/sandbox_layer.dart
Normal file
69
examples/lib/stories/parallax/sandbox_layer.dart
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flame/parallax.dart';
|
||||||
|
import 'package:flutter/painting.dart';
|
||||||
|
|
||||||
|
class SandBoxLayerParallaxGame extends BaseGame {
|
||||||
|
final Vector2 planeSpeed;
|
||||||
|
final ImageRepeat planeRepeat;
|
||||||
|
final LayerFill planeFill;
|
||||||
|
final Alignment planeAlignment;
|
||||||
|
|
||||||
|
SandBoxLayerParallaxGame({
|
||||||
|
required this.planeSpeed,
|
||||||
|
required this.planeRepeat,
|
||||||
|
required this.planeFill,
|
||||||
|
required this.planeAlignment,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
final bgLayer = await loadParallaxLayer(
|
||||||
|
ParallaxImageData('parallax/bg.png'),
|
||||||
|
);
|
||||||
|
final mountainFarLayer = await loadParallaxLayer(
|
||||||
|
ParallaxImageData('parallax/mountain-far.png'),
|
||||||
|
velocityMultiplier: Vector2(1.8, 0),
|
||||||
|
);
|
||||||
|
final mountainLayer = await loadParallaxLayer(
|
||||||
|
ParallaxImageData('parallax/mountains.png'),
|
||||||
|
velocityMultiplier: Vector2(2.8, 0),
|
||||||
|
);
|
||||||
|
final treeLayer = await loadParallaxLayer(
|
||||||
|
ParallaxImageData('parallax/trees.png'),
|
||||||
|
velocityMultiplier: Vector2(3.8, 0),
|
||||||
|
);
|
||||||
|
final foregroundTreesLayer = await loadParallaxLayer(
|
||||||
|
ParallaxImageData('parallax/foreground-trees.png'),
|
||||||
|
velocityMultiplier: Vector2(4.8, 0),
|
||||||
|
);
|
||||||
|
final airplaneLayer = await loadParallaxLayer(
|
||||||
|
ParallaxAnimationData(
|
||||||
|
'parallax/airplane.png',
|
||||||
|
SpriteAnimationData.sequenced(
|
||||||
|
amount: 4,
|
||||||
|
stepTime: 0.2,
|
||||||
|
textureSize: Vector2(320, 160),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
repeat: planeRepeat,
|
||||||
|
velocityMultiplier: planeSpeed,
|
||||||
|
fill: planeFill,
|
||||||
|
alignment: planeAlignment,
|
||||||
|
);
|
||||||
|
|
||||||
|
final parallax = Parallax(
|
||||||
|
[
|
||||||
|
bgLayer,
|
||||||
|
mountainFarLayer,
|
||||||
|
mountainLayer,
|
||||||
|
treeLayer,
|
||||||
|
foregroundTreesLayer,
|
||||||
|
airplaneLayer,
|
||||||
|
],
|
||||||
|
baseVelocity: Vector2(20, 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
add(ParallaxComponent.fromParallax(parallax));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,16 +1,17 @@
|
|||||||
import 'package:flame/components.dart';
|
import 'package:flame/components.dart';
|
||||||
import 'package:flame/game.dart';
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flame/parallax.dart';
|
||||||
|
|
||||||
class SmallParallaxGame extends BaseGame {
|
class SmallParallaxGame extends BaseGame {
|
||||||
@override
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
final component = await loadParallaxComponent(
|
final component = await loadParallaxComponent(
|
||||||
[
|
[
|
||||||
'parallax/bg.png',
|
ParallaxImageData('parallax/bg.png'),
|
||||||
'parallax/mountain-far.png',
|
ParallaxImageData('parallax/mountain-far.png'),
|
||||||
'parallax/mountains.png',
|
ParallaxImageData('parallax/mountains.png'),
|
||||||
'parallax/trees.png',
|
ParallaxImageData('parallax/trees.png'),
|
||||||
'parallax/foreground-trees.png',
|
ParallaxImageData('parallax/foreground-trees.png'),
|
||||||
],
|
],
|
||||||
size: Vector2.all(200),
|
size: Vector2.all(200),
|
||||||
baseVelocity: Vector2(20, 0),
|
baseVelocity: Vector2(20, 0),
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
42.7
|
47.7
|
||||||
|
|||||||
@ -20,6 +20,9 @@
|
|||||||
- Improve error message for composed components
|
- Improve error message for composed components
|
||||||
- Add `anchor` for `ShapeComponent` constructor
|
- Add `anchor` for `ShapeComponent` constructor
|
||||||
- Fix rendering of polygons in `ShapeComponent`
|
- Fix rendering of polygons in `ShapeComponent`
|
||||||
|
- Add `SpriteAnimation` support to parallax
|
||||||
|
- Fix `Parallax` alignment for images with different width and height
|
||||||
|
- Fix `ImageComposition` image bounds validation
|
||||||
|
|
||||||
## [1.0.0-releasecandidate.11]
|
## [1.0.0-releasecandidate.11]
|
||||||
- Replace deprecated analysis option lines-of-executable-code with source-lines-of-code
|
- Replace deprecated analysis option lines-of-executable-code with source-lines-of-code
|
||||||
|
|||||||
@ -92,8 +92,8 @@ class ImageComposition {
|
|||||||
isAntiAlias ??= defaultAntiAlias;
|
isAntiAlias ??= defaultAntiAlias;
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
imageRect.contains(source.topLeft) &&
|
imageRect.topLeft <= source.topLeft &&
|
||||||
imageRect.contains(source.bottomRight),
|
imageRect.bottomRight >= source.bottomRight,
|
||||||
'Source rect should fit within in the image constraints',
|
'Source rect should fit within in the image constraints',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import 'position_component.dart';
|
|||||||
|
|
||||||
extension ParallaxComponentExtension on Game {
|
extension ParallaxComponentExtension on Game {
|
||||||
Future<ParallaxComponent> loadParallaxComponent(
|
Future<ParallaxComponent> loadParallaxComponent(
|
||||||
List<String> paths, {
|
List<ParallaxData> dataList, {
|
||||||
Vector2? size,
|
Vector2? size,
|
||||||
Vector2? baseVelocity,
|
Vector2? baseVelocity,
|
||||||
Vector2? velocityMultiplierDelta,
|
Vector2? velocityMultiplierDelta,
|
||||||
@ -22,7 +22,7 @@ extension ParallaxComponentExtension on Game {
|
|||||||
int? priority,
|
int? priority,
|
||||||
}) async {
|
}) async {
|
||||||
final component = await ParallaxComponent.load(
|
final component = await ParallaxComponent.load(
|
||||||
paths,
|
dataList,
|
||||||
size: size,
|
size: size,
|
||||||
baseVelocity: baseVelocity,
|
baseVelocity: baseVelocity,
|
||||||
velocityMultiplierDelta: velocityMultiplierDelta,
|
velocityMultiplierDelta: velocityMultiplierDelta,
|
||||||
@ -101,8 +101,9 @@ class ParallaxComponent extends PositionComponent {
|
|||||||
/// and filled), otherwise load the [ParallaxLayer]s individually and use the
|
/// and filled), otherwise load the [ParallaxLayer]s individually and use the
|
||||||
/// normal constructor.
|
/// normal constructor.
|
||||||
///
|
///
|
||||||
/// [load] takes a list of paths to all the images and a size that you want to use in the
|
/// [load] takes a list of [ParallaxData] of all the images and a size that you want to use in the
|
||||||
/// parallax.
|
/// parallax.
|
||||||
|
///
|
||||||
/// Optionally arguments for the [baseVelocity] and [velocityMultiplierDelta] can be passed
|
/// Optionally arguments for the [baseVelocity] and [velocityMultiplierDelta] can be passed
|
||||||
/// in, [baseVelocity] defines what the base velocity of the layers should be
|
/// in, [baseVelocity] defines what the base velocity of the layers should be
|
||||||
/// and [velocityMultiplierDelta] defines how the velocity should change the
|
/// and [velocityMultiplierDelta] defines how the velocity should change the
|
||||||
@ -112,9 +113,10 @@ class ParallaxComponent extends PositionComponent {
|
|||||||
/// which edge it should align with ([alignment]), which axis it should fill
|
/// which edge it should align with ([alignment]), which axis it should fill
|
||||||
/// the image on ([fill]) and [images] which is the image cache that should be
|
/// the image on ([fill]) and [images] which is the image cache that should be
|
||||||
/// 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<ParallaxComponent> load(
|
static Future<ParallaxComponent> load(
|
||||||
List<String> paths, {
|
List<ParallaxData> dataList, {
|
||||||
Vector2? size,
|
Vector2? size,
|
||||||
Vector2? baseVelocity,
|
Vector2? baseVelocity,
|
||||||
Vector2? velocityMultiplierDelta,
|
Vector2? velocityMultiplierDelta,
|
||||||
@ -126,7 +128,7 @@ class ParallaxComponent extends PositionComponent {
|
|||||||
}) async {
|
}) async {
|
||||||
final component = ParallaxComponent.fromParallax(
|
final component = ParallaxComponent.fromParallax(
|
||||||
await Parallax.load(
|
await Parallax.load(
|
||||||
paths,
|
dataList,
|
||||||
size: size,
|
size: size,
|
||||||
baseVelocity: baseVelocity,
|
baseVelocity: baseVelocity,
|
||||||
velocityMultiplierDelta: velocityMultiplierDelta,
|
velocityMultiplierDelta: velocityMultiplierDelta,
|
||||||
|
|||||||
@ -10,10 +10,11 @@ import 'extensions/rect.dart';
|
|||||||
import 'extensions/vector2.dart';
|
import 'extensions/vector2.dart';
|
||||||
import 'flame.dart';
|
import 'flame.dart';
|
||||||
import 'game/game.dart';
|
import 'game/game.dart';
|
||||||
|
import 'sprite_animation.dart';
|
||||||
|
|
||||||
extension ParallaxExtension on Game {
|
extension ParallaxExtension on Game {
|
||||||
Future<Parallax> loadParallax(
|
Future<Parallax> loadParallax(
|
||||||
List<String> paths, {
|
List<ParallaxData> dataList, {
|
||||||
Vector2? size,
|
Vector2? size,
|
||||||
Vector2? baseVelocity,
|
Vector2? baseVelocity,
|
||||||
Vector2? velocityMultiplierDelta,
|
Vector2? velocityMultiplierDelta,
|
||||||
@ -22,7 +23,7 @@ extension ParallaxExtension on Game {
|
|||||||
LayerFill fill = LayerFill.height,
|
LayerFill fill = LayerFill.height,
|
||||||
}) {
|
}) {
|
||||||
return Parallax.load(
|
return Parallax.load(
|
||||||
paths,
|
dataList,
|
||||||
size: size,
|
size: size,
|
||||||
baseVelocity: baseVelocity,
|
baseVelocity: baseVelocity,
|
||||||
velocityMultiplierDelta: velocityMultiplierDelta,
|
velocityMultiplierDelta: velocityMultiplierDelta,
|
||||||
@ -48,15 +49,32 @@ extension ParallaxExtension on Game {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<ParallaxAnimation> loadParallaxAnimation(
|
||||||
|
String path,
|
||||||
|
SpriteAnimationData animaitonData, {
|
||||||
|
ImageRepeat repeat = ImageRepeat.repeatX,
|
||||||
|
Alignment alignment = Alignment.bottomLeft,
|
||||||
|
LayerFill fill = LayerFill.height,
|
||||||
|
}) {
|
||||||
|
return ParallaxAnimation.load(
|
||||||
|
path,
|
||||||
|
animaitonData,
|
||||||
|
repeat: repeat,
|
||||||
|
alignment: alignment,
|
||||||
|
fill: fill,
|
||||||
|
images: images,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<ParallaxLayer> loadParallaxLayer(
|
Future<ParallaxLayer> loadParallaxLayer(
|
||||||
String path, {
|
ParallaxData data, {
|
||||||
ImageRepeat repeat = ImageRepeat.repeatX,
|
ImageRepeat repeat = ImageRepeat.repeatX,
|
||||||
Alignment alignment = Alignment.bottomLeft,
|
Alignment alignment = Alignment.bottomLeft,
|
||||||
LayerFill fill = LayerFill.height,
|
LayerFill fill = LayerFill.height,
|
||||||
Vector2? velocityMultiplier,
|
Vector2? velocityMultiplier,
|
||||||
}) {
|
}) {
|
||||||
return ParallaxLayer.load(
|
return ParallaxLayer.load(
|
||||||
path,
|
data,
|
||||||
velocityMultiplier: velocityMultiplier,
|
velocityMultiplier: velocityMultiplier,
|
||||||
repeat: repeat,
|
repeat: repeat,
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
@ -66,12 +84,7 @@ extension ParallaxExtension on Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specifications with a path to an image and how it should be drawn in
|
abstract class ParallaxRenderer {
|
||||||
/// relation to the device screen
|
|
||||||
class ParallaxImage {
|
|
||||||
/// The image
|
|
||||||
final Image image;
|
|
||||||
|
|
||||||
/// If and how the image should be repeated on the canvas
|
/// If and how the image should be repeated on the canvas
|
||||||
final ImageRepeat repeat;
|
final ImageRepeat repeat;
|
||||||
|
|
||||||
@ -81,12 +94,34 @@ class ParallaxImage {
|
|||||||
/// How to fill the screen with the image, always proportionally scaled.
|
/// How to fill the screen with the image, always proportionally scaled.
|
||||||
final LayerFill fill;
|
final LayerFill fill;
|
||||||
|
|
||||||
|
ParallaxRenderer({
|
||||||
|
ImageRepeat? repeat,
|
||||||
|
Alignment? alignment,
|
||||||
|
LayerFill? fill,
|
||||||
|
}) : repeat = repeat ?? ImageRepeat.repeatX,
|
||||||
|
alignment = alignment ?? Alignment.bottomLeft,
|
||||||
|
fill = fill ?? LayerFill.height;
|
||||||
|
|
||||||
|
void update(double dt);
|
||||||
|
Image get image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specifications with a path to an image and how it should be drawn in
|
||||||
|
/// relation to the device screen
|
||||||
|
class ParallaxImage extends ParallaxRenderer {
|
||||||
|
/// The image
|
||||||
|
final Image _image;
|
||||||
|
|
||||||
ParallaxImage(
|
ParallaxImage(
|
||||||
this.image, {
|
this._image, {
|
||||||
this.repeat = ImageRepeat.repeatX,
|
ImageRepeat? repeat,
|
||||||
this.alignment = Alignment.bottomLeft,
|
Alignment? alignment,
|
||||||
this.fill = LayerFill.height,
|
LayerFill? fill,
|
||||||
});
|
}) : super(
|
||||||
|
repeat: repeat,
|
||||||
|
alignment: alignment,
|
||||||
|
fill: fill,
|
||||||
|
);
|
||||||
|
|
||||||
/// Takes a path of an image, and optionally arguments for how the image should
|
/// Takes a path of an image, and optionally arguments for how the image should
|
||||||
/// repeat ([repeat]), which edge it should align with ([alignment]), which axis
|
/// repeat ([repeat]), which edge it should align with ([alignment]), which axis
|
||||||
@ -107,24 +142,96 @@ class ParallaxImage {
|
|||||||
fill: fill,
|
fill: fill,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Image get image => _image;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(_) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specifications with a SpriteAnimation and how it should be drawn in
|
||||||
|
/// relation to the device screen
|
||||||
|
class ParallaxAnimation extends ParallaxRenderer {
|
||||||
|
/// The Animation
|
||||||
|
final SpriteAnimation _animation;
|
||||||
|
|
||||||
|
/// The animation's frames prerended into images so it can be used in the parallax
|
||||||
|
final List<Image> _prerenderedFrames;
|
||||||
|
|
||||||
|
ParallaxAnimation(
|
||||||
|
this._animation,
|
||||||
|
this._prerenderedFrames, {
|
||||||
|
ImageRepeat? repeat,
|
||||||
|
Alignment? alignment,
|
||||||
|
LayerFill? fill,
|
||||||
|
}) : super(
|
||||||
|
repeat: repeat,
|
||||||
|
alignment: alignment,
|
||||||
|
fill: fill,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Takes a path of an image, a SpriteAnimationData, and optionally arguments for how the image should
|
||||||
|
/// repeat ([repeat]), which edge it should align with ([alignment]), which axis
|
||||||
|
/// it should fill the image on ([fill]) and [images] which is the image cache
|
||||||
|
/// that should be used. If no image cache is set, the global flame cache is used.
|
||||||
|
///
|
||||||
|
/// _IMPORTANT_: This method pre render all the frames of the animation into image instances
|
||||||
|
/// so it can be used inside the parallax. Just keep that in mind when using animations in
|
||||||
|
/// in parallax, the over use of it, or the use of big animations (be it in number of frames
|
||||||
|
/// or the size of the images) can lead to high use of memory.
|
||||||
|
static Future<ParallaxAnimation> load(
|
||||||
|
String path,
|
||||||
|
SpriteAnimationData animationData, {
|
||||||
|
ImageRepeat repeat = ImageRepeat.repeatX,
|
||||||
|
Alignment alignment = Alignment.bottomLeft,
|
||||||
|
LayerFill fill = LayerFill.height,
|
||||||
|
Images? images,
|
||||||
|
}) async {
|
||||||
|
images ??= Flame.images;
|
||||||
|
|
||||||
|
final animation =
|
||||||
|
await SpriteAnimation.load(path, animationData, images: images);
|
||||||
|
final prerendedFrames = await Future.wait(
|
||||||
|
animation.frames.map((frame) => frame.sprite.toImage()).toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return ParallaxAnimation(
|
||||||
|
animation,
|
||||||
|
prerendedFrames,
|
||||||
|
repeat: repeat,
|
||||||
|
alignment: alignment,
|
||||||
|
fill: fill,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Image get image => _prerenderedFrames[_animation.currentIndex];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
_animation.update(dt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents one layer in the parallax, draws out an image on a canvas in the
|
/// Represents one layer in the parallax, draws out an image on a canvas in the
|
||||||
/// manner specified by the parallaxImage
|
/// manner specified by the parallaxImage
|
||||||
class ParallaxLayer {
|
class ParallaxLayer {
|
||||||
final ParallaxImage parallaxImage;
|
final ParallaxRenderer parallaxRenderer;
|
||||||
late Vector2 velocityMultiplier;
|
late Vector2 velocityMultiplier;
|
||||||
late Rect _paintArea;
|
late Rect _paintArea;
|
||||||
late Vector2 _scroll;
|
late Vector2 _scroll;
|
||||||
late Vector2 _imageSize;
|
late Vector2 _imageSize;
|
||||||
double _scale = 1.0;
|
double _scale = 1.0;
|
||||||
|
|
||||||
/// [parallaxImage] is the representation of the image with data of how the
|
/// [parallaxRenderer] is the representation of the renderer with data of how the
|
||||||
/// image should behave.
|
/// layer should behave.
|
||||||
/// [velocityMultiplier] will be used to determine the velocity of the layer by
|
/// [velocityMultiplier] will be used to determine the velocity of the layer by
|
||||||
/// multiplying the [Parallax.baseVelocity] with the [velocityMultiplier].
|
/// multiplying the [Parallax.baseVelocity] with the [velocityMultiplier].
|
||||||
ParallaxLayer(
|
ParallaxLayer(
|
||||||
this.parallaxImage, {
|
this.parallaxRenderer, {
|
||||||
Vector2? velocityMultiplier,
|
Vector2? velocityMultiplier,
|
||||||
}) : velocityMultiplier = velocityMultiplier ?? Vector2.all(1.0);
|
}) : velocityMultiplier = velocityMultiplier ?? Vector2.all(1.0);
|
||||||
|
|
||||||
@ -134,18 +241,18 @@ class ParallaxLayer {
|
|||||||
double scale(LayerFill fill) {
|
double scale(LayerFill fill) {
|
||||||
switch (fill) {
|
switch (fill) {
|
||||||
case LayerFill.height:
|
case LayerFill.height:
|
||||||
return parallaxImage.image.height / size.y;
|
return parallaxRenderer.image.height / size.y;
|
||||||
case LayerFill.width:
|
case LayerFill.width:
|
||||||
return parallaxImage.image.width / size.x;
|
return parallaxRenderer.image.width / size.x;
|
||||||
default:
|
default:
|
||||||
return _scale;
|
return _scale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_scale = scale(parallaxImage.fill);
|
_scale = scale(parallaxRenderer.fill);
|
||||||
|
|
||||||
// The image size so that it fulfills the LayerFill parameter
|
// The image size so that it fulfills the LayerFill parameter
|
||||||
_imageSize = parallaxImage.image.size / _scale;
|
_imageSize = parallaxRenderer.image.size / _scale;
|
||||||
|
|
||||||
// Number of images that can fit on the canvas plus one
|
// Number of images that can fit on the canvas plus one
|
||||||
// to have something to scroll to without leaving canvas empty
|
// to have something to scroll to without leaving canvas empty
|
||||||
@ -156,9 +263,11 @@ class ParallaxLayer {
|
|||||||
..divide(_imageSize);
|
..divide(_imageSize);
|
||||||
|
|
||||||
// Align image to correct side of the screen
|
// Align image to correct side of the screen
|
||||||
final alignment = parallaxImage.alignment;
|
final alignment = parallaxRenderer.alignment;
|
||||||
final marginX = alignment.x == 0 ? overflow.x / 2 : alignment.x;
|
|
||||||
final marginY = alignment.y == 0 ? overflow.y / 2 : alignment.y;
|
final marginX = alignment.x * overflow.x / 2 + overflow.x / 2;
|
||||||
|
final marginY = alignment.y * overflow.y / 2 + overflow.y / 2;
|
||||||
|
|
||||||
_scroll = Vector2(marginX, marginY);
|
_scroll = Vector2(marginX, marginY);
|
||||||
|
|
||||||
// Size of the area to paint the images on
|
// Size of the area to paint the images on
|
||||||
@ -166,10 +275,11 @@ class ParallaxLayer {
|
|||||||
_paintArea = paintSize.toRect();
|
_paintArea = paintSize.toRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(Vector2 delta) {
|
void update(Vector2 delta, double dt) {
|
||||||
|
parallaxRenderer.update(dt);
|
||||||
// Scale the delta so that images that are larger don't scroll faster
|
// Scale the delta so that images that are larger don't scroll faster
|
||||||
_scroll += delta.clone()..divide(_imageSize);
|
_scroll += delta.clone()..divide(_imageSize);
|
||||||
switch (parallaxImage.repeat) {
|
switch (parallaxRenderer.repeat) {
|
||||||
case ImageRepeat.repeat:
|
case ImageRepeat.repeat:
|
||||||
_scroll = Vector2(_scroll.x % 1, _scroll.y % 1);
|
_scroll = Vector2(_scroll.x % 1, _scroll.y % 1);
|
||||||
break;
|
break;
|
||||||
@ -198,20 +308,20 @@ class ParallaxLayer {
|
|||||||
}
|
}
|
||||||
paintImage(
|
paintImage(
|
||||||
canvas: canvas,
|
canvas: canvas,
|
||||||
image: parallaxImage.image,
|
image: parallaxRenderer.image,
|
||||||
rect: _paintArea,
|
rect: _paintArea,
|
||||||
repeat: parallaxImage.repeat,
|
repeat: parallaxRenderer.repeat,
|
||||||
scale: _scale,
|
scale: _scale,
|
||||||
alignment: parallaxImage.alignment,
|
alignment: parallaxRenderer.alignment,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes a path of an image, and optionally arguments for how the image should
|
/// Takes a data of a parallax renderer, and optionally arguments for how it should
|
||||||
/// repeat ([repeat]), which edge it should align with ([alignment]), which axis
|
/// repeat ([repeat]), which edge it should align with ([alignment]), which axis
|
||||||
/// it should fill the image on ([fill]) and [images] which is the image cache
|
/// it should fill the image on ([fill]) and [images] which is the image cache
|
||||||
/// that should be used. If no image cache is set, the global flame cache is used.
|
/// that should be used. If no image cache is set, the global flame cache is used.
|
||||||
static Future<ParallaxLayer> load(
|
static Future<ParallaxLayer> load(
|
||||||
String path, {
|
ParallaxData data, {
|
||||||
Vector2? velocityMultiplier,
|
Vector2? velocityMultiplier,
|
||||||
ImageRepeat repeat = ImageRepeat.repeatX,
|
ImageRepeat repeat = ImageRepeat.repeatX,
|
||||||
Alignment alignment = Alignment.bottomLeft,
|
Alignment alignment = Alignment.bottomLeft,
|
||||||
@ -219,12 +329,11 @@ class ParallaxLayer {
|
|||||||
Images? images,
|
Images? images,
|
||||||
}) async {
|
}) async {
|
||||||
return ParallaxLayer(
|
return ParallaxLayer(
|
||||||
await ParallaxImage.load(
|
await data.load(
|
||||||
path,
|
repeat,
|
||||||
repeat: repeat,
|
alignment,
|
||||||
alignment: alignment,
|
fill,
|
||||||
fill: fill,
|
images,
|
||||||
images: images,
|
|
||||||
),
|
),
|
||||||
velocityMultiplier: velocityMultiplier,
|
velocityMultiplier: velocityMultiplier,
|
||||||
);
|
);
|
||||||
@ -234,6 +343,63 @@ class ParallaxLayer {
|
|||||||
/// How to fill the screen with the image, always proportionally scaled.
|
/// How to fill the screen with the image, always proportionally scaled.
|
||||||
enum LayerFill { height, width, none }
|
enum LayerFill { height, width, none }
|
||||||
|
|
||||||
|
abstract class ParallaxData {
|
||||||
|
Future<ParallaxRenderer> load(
|
||||||
|
ImageRepeat repeat,
|
||||||
|
Alignment alignment,
|
||||||
|
LayerFill fill,
|
||||||
|
Images? images,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains the fields and logic to load a [ParallaxImage]
|
||||||
|
class ParallaxImageData extends ParallaxData {
|
||||||
|
final String path;
|
||||||
|
|
||||||
|
ParallaxImageData(this.path);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ParallaxRenderer> load(
|
||||||
|
ImageRepeat repeat,
|
||||||
|
Alignment alignment,
|
||||||
|
LayerFill fill,
|
||||||
|
Images? images,
|
||||||
|
) {
|
||||||
|
return ParallaxImage.load(
|
||||||
|
path,
|
||||||
|
repeat: repeat,
|
||||||
|
alignment: alignment,
|
||||||
|
fill: fill,
|
||||||
|
images: images,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains the fields and logic to load a [ParallaxAnimation]
|
||||||
|
class ParallaxAnimationData extends ParallaxData {
|
||||||
|
final String path;
|
||||||
|
final SpriteAnimationData animationData;
|
||||||
|
|
||||||
|
ParallaxAnimationData(this.path, this.animationData);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ParallaxRenderer> load(
|
||||||
|
ImageRepeat repeat,
|
||||||
|
Alignment alignment,
|
||||||
|
LayerFill fill,
|
||||||
|
Images? images,
|
||||||
|
) {
|
||||||
|
return ParallaxAnimation.load(
|
||||||
|
path,
|
||||||
|
animationData,
|
||||||
|
repeat: repeat,
|
||||||
|
alignment: alignment,
|
||||||
|
fill: fill,
|
||||||
|
images: images,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A full parallax, several layers of images drawn out on the screen and each
|
/// A full parallax, several layers of images drawn out on the screen and each
|
||||||
/// layer moves with different velocities to give an effect of depth.
|
/// layer moves with different velocities to give an effect of depth.
|
||||||
class Parallax {
|
class Parallax {
|
||||||
@ -283,6 +449,7 @@ class Parallax {
|
|||||||
layers.forEach((layer) {
|
layers.forEach((layer) {
|
||||||
layer.update(
|
layer.update(
|
||||||
(baseVelocity.clone()..multiply(layer.velocityMultiplier)) * dt,
|
(baseVelocity.clone()..multiply(layer.velocityMultiplier)) * dt,
|
||||||
|
dt,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -305,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<String> paths, {
|
List<ParallaxData> dataList, {
|
||||||
Vector2? size,
|
Vector2? size,
|
||||||
Vector2? baseVelocity,
|
Vector2? baseVelocity,
|
||||||
Vector2? velocityMultiplierDelta,
|
Vector2? velocityMultiplierDelta,
|
||||||
@ -317,13 +484,12 @@ class Parallax {
|
|||||||
final velocityDelta = velocityMultiplierDelta ?? Vector2.all(1.0);
|
final velocityDelta = velocityMultiplierDelta ?? Vector2.all(1.0);
|
||||||
var depth = 0;
|
var depth = 0;
|
||||||
final layers = await Future.wait<ParallaxLayer>(
|
final layers = await Future.wait<ParallaxLayer>(
|
||||||
paths.map((path) async {
|
dataList.map((data) async {
|
||||||
final image = ParallaxImage.load(
|
final renderer = await data.load(
|
||||||
path,
|
repeat,
|
||||||
repeat: repeat,
|
alignment,
|
||||||
alignment: alignment,
|
fill,
|
||||||
fill: fill,
|
images,
|
||||||
images: images,
|
|
||||||
);
|
);
|
||||||
final velocityMultiplier =
|
final velocityMultiplier =
|
||||||
List.filled(depth, velocityDelta).fold<Vector2>(
|
List.filled(depth, velocityDelta).fold<Vector2>(
|
||||||
@ -332,7 +498,7 @@ class Parallax {
|
|||||||
);
|
);
|
||||||
++depth;
|
++depth;
|
||||||
return ParallaxLayer(
|
return ParallaxLayer(
|
||||||
await image,
|
renderer,
|
||||||
velocityMultiplier: velocityMultiplier,
|
velocityMultiplier: velocityMultiplier,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -16,6 +16,7 @@ dev_dependencies:
|
|||||||
test: ^1.16.0
|
test: ^1.16.0
|
||||||
dart_code_metrics: ^3.2.2
|
dart_code_metrics: ^3.2.2
|
||||||
dartdoc: ^0.42.0
|
dartdoc: ^0.42.0
|
||||||
|
mocktail: ^0.1.4
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.12.0 <3.0.0"
|
sdk: ">=2.12.0 <3.0.0"
|
||||||
|
|||||||
41
packages/flame/test/image_composition_test.dart
Normal file
41
packages/flame/test/image_composition_test.dart
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flame/image_composition.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
|
||||||
|
class MockImage extends Mock implements Image {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ImageComposition', () {
|
||||||
|
group('add', () {
|
||||||
|
test('breaks assertion when adding an invalid portion', () {
|
||||||
|
final image = MockImage();
|
||||||
|
final composition = ImageComposition();
|
||||||
|
|
||||||
|
when(() => image.width).thenReturn(100);
|
||||||
|
when(() => image.height).thenReturn(100);
|
||||||
|
|
||||||
|
final invalidRects = [
|
||||||
|
const Rect.fromLTWH(-10, 10, 10, 10),
|
||||||
|
const Rect.fromLTWH(10, -10, 10, 10),
|
||||||
|
const Rect.fromLTWH(110, 10, 10, 10),
|
||||||
|
const Rect.fromLTWH(0, 110, 10, 10),
|
||||||
|
const Rect.fromLTWH(0, 0, 110, 110),
|
||||||
|
const Rect.fromLTWH(20, 0, 90, 10),
|
||||||
|
const Rect.fromLTWH(0, 20, 90, 90),
|
||||||
|
const Rect.fromLTWH(0, 0, 190, 90),
|
||||||
|
const Rect.fromLTWH(0, 0, 90, 190),
|
||||||
|
];
|
||||||
|
|
||||||
|
invalidRects.forEach((r) {
|
||||||
|
expect(
|
||||||
|
() => composition.add(image, Vector2.zero(), source: r),
|
||||||
|
throwsA(isA<AssertionError>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user