mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-02 03:15:43 +08:00
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:
@ -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).
|
||||||
|
|
||||||
|
|||||||
@ -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),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
22
examples/lib/stories/parallax/small_parallax.dart
Normal file
22
examples/lib/stories/parallax/small_parallax.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>()),
|
||||||
|
|||||||
42
packages/flame/test/components/parallax_test.dart
Normal file
42
packages/flame/test/components/parallax_test.dart
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user