Fix parallax size (#800)

* Non-fullscreen ParallaxComponent

* Updated examples with no size needed

* Formatting

* Add docs for fullscreen parallax

* Fix formatting

* Remove need of nullable size

* Add basic parallax sizing test

* Fix formatting

* Remove unused imports

* Info text for the parallax examples
This commit is contained in:
Lukas Klingsbo
2021-05-20 16:58:49 +02:00
committed by GitHub
parent bc5decd193
commit 8f7c773f82
12 changed files with 141 additions and 34 deletions

View File

@ -355,7 +355,6 @@ final layers = images.map((image) => ParallaxLayer(await image, velocityMultipli
final parallaxComponent = ParallaxComponent.fromParallax( final parallaxComponent = ParallaxComponent.fromParallax(
Parallax( Parallax(
await Future.wait(layers), await Future.wait(layers),
size, // size is a property on the Game class
baseVelocity: Vector2(50, 0), baseVelocity: Vector2(50, 0),
), ),
); );
@ -376,6 +375,9 @@ The `Parallax` file contains an extension of the game which adds `loadParallax`,
and `loadParallaxImage` so that it automatically uses your game's image cache instead of the global and `loadParallaxImage` 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
size of the game, it will also resize to fullscreen when the game changes size or orientation.
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).

View File

@ -22,7 +22,6 @@ class AdvancedParallaxGame extends BaseGame {
final parallax = ParallaxComponent.fromParallax( final parallax = ParallaxComponent.fromParallax(
Parallax( Parallax(
await Future.wait(layers), await Future.wait(layers),
size,
baseVelocity: Vector2(20, 0), baseVelocity: Vector2(20, 0),
), ),
); );

View File

@ -21,7 +21,6 @@ class MyParallaxComponent extends ParallaxComponent
'parallax/trees.png', 'parallax/trees.png',
'parallax/foreground-trees.png', 'parallax/foreground-trees.png',
], ],
size,
baseVelocity: Vector2(20, 0), baseVelocity: Vector2(20, 0),
velocityMultiplierDelta: Vector2(1.8, 1.0), velocityMultiplierDelta: Vector2(1.8, 1.0),
); );

View File

@ -22,7 +22,7 @@ class NoFCSParallaxGame extends Game {
'parallax/trees.png', 'parallax/trees.png',
'parallax/foreground-trees.png', 'parallax/foreground-trees.png',
], ],
size, size: size,
baseVelocity: Vector2(20, 0), baseVelocity: Vector2(20, 0),
velocityMultiplierDelta: Vector2(1.8, 1.0), velocityMultiplierDelta: Vector2(1.8, 1.0),
); );

View File

@ -6,6 +6,7 @@ import 'advanced.dart';
import 'basic.dart'; import 'basic.dart';
import 'component.dart'; import 'component.dart';
import 'no_fcs.dart'; import 'no_fcs.dart';
import 'small_parallax.dart';
void addParallaxStories(Dashbook dashbook) { void addParallaxStories(Dashbook dashbook) {
dashbook.storiesOf('Parallax') dashbook.storiesOf('Parallax')
@ -13,20 +14,33 @@ void addParallaxStories(Dashbook dashbook) {
'Basic', 'Basic',
(_) => GameWidget(game: BasicParallaxGame()), (_) => GameWidget(game: BasicParallaxGame()),
codeLink: baseLink('parallax/basic.dart'), codeLink: baseLink('parallax/basic.dart'),
info: 'Shows the simplest way to use a fullscreen ParallaxComponent',
) )
..add( ..add(
'Component', 'Component',
(_) => GameWidget(game: ComponentParallaxGame()), (_) => GameWidget(game: ComponentParallaxGame()),
codeLink: baseLink('parallax/component.dart'), codeLink: baseLink('parallax/component.dart'),
info: 'Shows how to do initiation and loading of assets from within an '
'extended ParallaxComponent',
)
..add(
'Non-fullscreen',
(_) => GameWidget(game: SmallParallaxGame()),
codeLink: baseLink('parallax/small_parallax.dart'),
info: 'Shows how to create a smaller parallax in the center of the '
'screen',
) )
..add( ..add(
'No FCS', 'No FCS',
(_) => GameWidget(game: NoFCSParallaxGame()), (_) => GameWidget(game: NoFCSParallaxGame()),
codeLink: baseLink('parallax/no_fcs.dart'), codeLink: baseLink('parallax/no_fcs.dart'),
info: "Shows how to use the parallax without Flame's component system",
) )
..add( ..add(
'Advanced', 'Advanced',
(_) => GameWidget(game: AdvancedParallaxGame()), (_) => GameWidget(game: AdvancedParallaxGame()),
codeLink: baseLink('parallax/advanced.dart'), codeLink: baseLink('parallax/advanced.dart'),
info: 'Shows how to create a parallax with different velocity deltas on '
'each layer',
); );
} }

View File

@ -0,0 +1,22 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart';
class SmallParallaxGame extends BaseGame {
@override
Future<void> onLoad() async {
final component = await loadParallaxComponent(
[
'parallax/bg.png',
'parallax/mountain-far.png',
'parallax/mountains.png',
'parallax/trees.png',
'parallax/foreground-trees.png',
],
size: Vector2.all(200),
baseVelocity: Vector2(20, 0),
velocityMultiplierDelta: Vector2(1.8, 1.0),
)
..position = size / 2;
add(component);
}
}

View File

@ -7,6 +7,8 @@
- Add possibility to dynamically change priority of components - Add possibility to dynamically change priority of components
- Add onCollisionEnd to make it possible for the user to easily detect when a collision ends - Add onCollisionEnd to make it possible for the user to easily detect when a collision ends
- Adding test coverage to packages - Adding test coverage to packages
- Possibility to have non-fullscreen ParallaxComponent
- No need to send size in ParallaxComponent.fromParallax since Parallax already contains it
- Fix Text Rendering not working properly - Fix Text Rendering not working properly
- Add more useful methods to the IsometricTileMap component - Add more useful methods to the IsometricTileMap component

View File

@ -20,10 +20,9 @@ extension ParallaxComponentExtension on Game {
Alignment alignment = Alignment.bottomLeft, Alignment alignment = Alignment.bottomLeft,
LayerFill fill = LayerFill.height, LayerFill fill = LayerFill.height,
}) async { }) async {
final componentSize = size ?? this.size;
final component = await ParallaxComponent.load( final component = await ParallaxComponent.load(
paths, paths,
size: componentSize, size: size,
baseVelocity: baseVelocity, baseVelocity: baseVelocity,
velocityMultiplierDelta: velocityMultiplierDelta, velocityMultiplierDelta: velocityMultiplierDelta,
repeat: repeat, repeat: repeat,
@ -32,13 +31,14 @@ extension ParallaxComponentExtension on Game {
images: images, images: images,
); );
return component..size.setFrom(componentSize); return component;
} }
} }
/// 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 ParallaxComponent extends PositionComponent { class ParallaxComponent extends PositionComponent {
bool isFullscreen = true;
Parallax? _parallax; Parallax? _parallax;
Parallax? get parallax => _parallax; Parallax? get parallax => _parallax;
@ -51,21 +51,31 @@ class ParallaxComponent extends PositionComponent {
ParallaxComponent({ ParallaxComponent({
Vector2? position, Vector2? position,
Vector2? size, Vector2? size,
}) : super(position: position, size: size); }) : super(position: position, size: size) {
if (size != null) {
isFullscreen = false;
}
}
/// Creates a component from a [Parallax] object. /// Creates a component from a [Parallax] object.
ParallaxComponent.fromParallax( factory ParallaxComponent.fromParallax(
this._parallax, { Parallax parallax, {
Vector2? position, Vector2? position,
Vector2? size, }) {
}) : super(position: position, size: size); return ParallaxComponent(
position: position,
size: parallax.isSized ? parallax.size : null,
)..parallax = parallax;
}
@mustCallSuper @mustCallSuper
@override @override
void onGameResize(Vector2 size) { void onGameResize(Vector2 size) {
super.onGameResize(size); super.onGameResize(size);
this.size.setFrom(size); if (isFullscreen) {
parallax?.resize(size); this.size.setFrom(size);
parallax?.resize(size);
}
} }
@override @override
@ -111,7 +121,7 @@ class ParallaxComponent extends PositionComponent {
final component = ParallaxComponent.fromParallax( final component = ParallaxComponent.fromParallax(
await Parallax.load( await Parallax.load(
paths, paths,
size, size: size,
baseVelocity: baseVelocity, baseVelocity: baseVelocity,
velocityMultiplierDelta: velocityMultiplierDelta, velocityMultiplierDelta: velocityMultiplierDelta,
repeat: repeat, repeat: repeat,

View File

@ -13,8 +13,8 @@ import 'game/game.dart';
extension ParallaxExtension on Game { extension ParallaxExtension on Game {
Future<Parallax> loadParallax( Future<Parallax> loadParallax(
List<String> paths, List<String> paths, {
Vector2? size, { Vector2? size,
Vector2? baseVelocity, Vector2? baseVelocity,
Vector2? velocityMultiplierDelta, Vector2? velocityMultiplierDelta,
ImageRepeat repeat = ImageRepeat.repeatX, ImageRepeat repeat = ImageRepeat.repeatX,
@ -23,7 +23,7 @@ extension ParallaxExtension on Game {
}) { }) {
return Parallax.load( return Parallax.load(
paths, paths,
size, size: size,
baseVelocity: baseVelocity, baseVelocity: baseVelocity,
velocityMultiplierDelta: velocityMultiplierDelta, velocityMultiplierDelta: velocityMultiplierDelta,
repeat: repeat, repeat: repeat,
@ -193,6 +193,9 @@ class ParallaxLayer {
} }
void render(Canvas canvas) { void render(Canvas canvas) {
if (_paintArea.isEmpty) {
return;
}
paintImage( paintImage(
canvas: canvas, canvas: canvas,
image: parallaxImage.image, image: parallaxImage.image,
@ -235,17 +238,27 @@ enum LayerFill { height, width, none }
/// 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 {
late Vector2 baseVelocity; late Vector2 baseVelocity;
Vector2 _size = Vector2.zero();
late Rect _clipRect; late Rect _clipRect;
final List<ParallaxLayer> layers; final List<ParallaxLayer> layers;
bool isSized = false;
late final Vector2 _size;
/// Do not modify this directly, since the layers won't be resized if you do
Vector2 get size => _size;
set size(Vector2 newSize) {
resize(newSize);
}
Parallax( Parallax(
this.layers, this.layers, {
Vector2? size, { Vector2? size,
Vector2? baseVelocity, Vector2? baseVelocity,
}) { }) {
this.baseVelocity = baseVelocity ?? Vector2.zero(); this.baseVelocity = baseVelocity ?? Vector2.zero();
resize(size ?? _size); if (size != null) {
resize(size);
}
} }
/// The base offset of the parallax, can be used in an outer update loop /// The base offset of the parallax, can be used in an outer update loop
@ -255,11 +268,15 @@ class Parallax {
/// If the `ParallaxComponent` isn't used your own wrapper needs to call this /// If the `ParallaxComponent` isn't used your own wrapper needs to call this
/// on creation. /// on creation.
void resize(Vector2 newSize) { void resize(Vector2 newSize) {
if (newSize != _size) { if (!isSized) {
_size = newSize; _size = Vector2.zero();
_clipRect = newSize.toRect();
layers.forEach((layer) => layer.resize(newSize));
} }
if (newSize != _size || !isSized) {
_size.setFrom(newSize);
_clipRect = _size.toRect();
layers.forEach((layer) => layer.resize(_size));
}
isSized |= true;
} }
void update(double dt) { void update(double dt) {
@ -288,8 +305,8 @@ 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<String> paths, {
Vector2? size, { Vector2? size,
Vector2? baseVelocity, Vector2? baseVelocity,
Vector2? velocityMultiplierDelta, Vector2? velocityMultiplierDelta,
ImageRepeat repeat = ImageRepeat.repeatX, ImageRepeat repeat = ImageRepeat.repeatX,
@ -322,7 +339,7 @@ class Parallax {
); );
return Parallax( return Parallax(
layers, layers,
size, size: size,
baseVelocity: baseVelocity, baseVelocity: baseVelocity,
); );
} }

View File

@ -4,7 +4,7 @@ import 'package:test/test.dart';
void main() { void main() {
group('Anchor', () { group('Anchor', () {
test('can parse to and from string', () async { test('can parse to and from string', () {
expect(Anchor.center.toString(), 'center'); expect(Anchor.center.toString(), 'center');
expect(Anchor.valueOf('topRight'), Anchor.topRight); expect(Anchor.valueOf('topRight'), Anchor.topRight);
@ -16,12 +16,12 @@ void main() {
} }
}); });
test('can parse custom anchor', () async { test('can parse custom anchor', () {
expect(const Anchor(0.2, 0.2).toString(), 'Anchor(0.2, 0.2)'); expect(const Anchor(0.2, 0.2).toString(), 'Anchor(0.2, 0.2)');
expect(Anchor.valueOf('Anchor(0.2, 0.2)'), const Anchor(0.2, 0.2)); expect(Anchor.valueOf('Anchor(0.2, 0.2)'), const Anchor(0.2, 0.2));
}); });
test('fail to parse invalid anchor', () async { test('fail to parse invalid anchor', () {
expect( expect(
() => Anchor.valueOf('foobar'), () => Anchor.valueOf('foobar'),
throwsA(const TypeMatcher<AssertionError>()), throwsA(const TypeMatcher<AssertionError>()),

View File

@ -0,0 +1,42 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:test/test.dart';
class ParallaxGame extends BaseGame {
late final ParallaxComponent parallaxComponent;
late final Vector2? parallaxSize;
ParallaxGame({this.parallaxSize}) {
onResize(Vector2.all(500));
}
@override
Future<void> onLoad() async {
parallaxComponent = await loadParallaxComponent(
[],
size: parallaxSize,
baseVelocity: Vector2(20, 0),
velocityMultiplierDelta: Vector2(1.8, 1.0),
);
add(parallaxComponent);
}
}
void main() {
group('parallax test', () {
test('can have non-fullscreen ParallaxComponent', () async {
final parallaxSize = Vector2.all(100);
final game = ParallaxGame(parallaxSize: parallaxSize.clone());
await game.onLoad();
game.update(0);
expect(game.parallaxComponent.size, parallaxSize);
});
test('can have fullscreen ParallaxComponent', () async {
final game = ParallaxGame();
await game.onLoad();
game.update(0);
expect(game.parallaxComponent.size, game.size);
});
});
}

View File

@ -3,8 +3,8 @@ import 'dart:io';
void main(args) { void main(args) {
final coverageSummary = args[0] as String; final coverageSummary = args[0] as String;
final currentRaw = coverageSummary.replaceFirstMapped( final currentRaw = coverageSummary.replaceFirstMapped(
RegExp(r".* (\d+\.\d+)%.*"), RegExp(r".* (\d+\.\d+)%.*"),
(matches) => '${matches[1]}', (matches) => '${matches[1]}',
); );
final current = double.parse(currentRaw); final current = double.parse(currentRaw);