mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 10:38:17 +08:00
refactor!: The method onLoad() now returns FutureOr<void> (#2228)
Before this PR, the return type of onLoad() was Future<void>?, after this PR the return type is FutureOr<void> -- both for classes Component and Game.
Reasons:
The use of FutureOr is more idiomatic in Dart, this class was specifically created in order to be used in situations like ours.
This makes learning Flame easier for beginners, since you no longer need to explain what asynchronous programming is from the start (and onLoad() is one of the first methods the user encounters in Flame).
The code can be cleaner in case when onLoad doesn't need to be async.
With new approach, the onLoad() method can be overridden as either
@override
Future<void> onLoad() async { ... }
or
@override
void onLoad() { ... }
Of course, it can also be overridden as
@override
FutureOr<void> onLoad() { ... }
but this is rare, only for components that are designed to be further subclassed, or for mixins.
The documentation was updated to show the new recommended usage.
This commit is contained in:
@ -88,7 +88,7 @@ Example:
|
||||
```dart
|
||||
class MyGame extends FlameGame {
|
||||
@override
|
||||
Future<void> onLoad() {
|
||||
void onLoad() {
|
||||
final myComponent = PositionComponent(priority: 5);
|
||||
add(myComponent);
|
||||
}
|
||||
@ -135,7 +135,7 @@ class GameOverPanel extends PositionComponent {
|
||||
GameOverPanel(this.spriteImage);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
void onLoad() {
|
||||
final gameOverText = GameOverText(spriteImage); // GameOverText is a Component
|
||||
final gameOverButton = GameOverButton(spriteImage); // GameOverRestart is a SpriteComponent
|
||||
|
||||
@ -163,7 +163,7 @@ constructor. This approach more closely resembles the standard Flutter API:
|
||||
```dart
|
||||
class MyGame extends FlameGame {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
void onLoad() {
|
||||
add(
|
||||
PositionComponent(
|
||||
position: Vector2(30, 0),
|
||||
@ -198,7 +198,7 @@ Example:
|
||||
```dart
|
||||
class MyComponent extends Component with ParentIsA<MyParentComponent> {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
void onLoad() {
|
||||
// parent is of type MyParentComponent
|
||||
print(parent.myValue);
|
||||
}
|
||||
@ -221,7 +221,7 @@ Example:
|
||||
```dart
|
||||
class MyComponent extends Component with HasAncestor<MyAncestorComponent> {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
void onLoad() {
|
||||
// ancestor is of type MyAncestorComponent.
|
||||
print(ancestor.myValue);
|
||||
}
|
||||
@ -247,7 +247,7 @@ Example:
|
||||
|
||||
```dart
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
void onLoad() {
|
||||
children.register<PositionComponent>();
|
||||
}
|
||||
```
|
||||
@ -442,10 +442,10 @@ class MyGame extends FlameGame {
|
||||
final player = SpriteComponent(size: size, sprite: sprite);
|
||||
|
||||
// Vector2(0.0, 0.0) by default, can also be set in the constructor
|
||||
player.position = ...
|
||||
player.position = Vector2(10, 20);
|
||||
|
||||
// 0 by default, can also be set in the constructor
|
||||
player.angle = ...
|
||||
player.angle = 0;
|
||||
|
||||
// Adds the component
|
||||
add(player);
|
||||
@ -461,32 +461,36 @@ This class is used to represent a Component that has sprites that run in a singl
|
||||
This will create a simple three frame animation using 3 different images:
|
||||
|
||||
```dart
|
||||
final sprites = [0, 1, 2]
|
||||
.map((i) => Sprite.load('player_$i.png'));
|
||||
final animation = SpriteAnimation.spriteList(
|
||||
await Future.wait(sprites),
|
||||
stepTime: 0.01,
|
||||
);
|
||||
this.player = SpriteAnimationComponent(
|
||||
animation: animation,
|
||||
size: Vector2.all(64.0),
|
||||
);
|
||||
Future<void> onLoad() async {
|
||||
final sprites = [0, 1, 2]
|
||||
.map((i) => Sprite.load('player_$i.png'));
|
||||
final animation = SpriteAnimation.spriteList(
|
||||
await Future.wait(sprites),
|
||||
stepTime: 0.01,
|
||||
);
|
||||
this.player = SpriteAnimationComponent(
|
||||
animation: animation,
|
||||
size: Vector2.all(64.0),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
If you have a sprite sheet, you can use the `sequenced` constructor from the `SpriteAnimationData`
|
||||
class (check more details on [Images > Animation](rendering/images.md#animation)):
|
||||
|
||||
```dart
|
||||
final size = Vector2.all(64.0);
|
||||
final data = SpriteAnimationData.sequenced(
|
||||
textureSize: size,
|
||||
amount: 2,
|
||||
stepTime: 0.1,
|
||||
);
|
||||
this.player = SpriteAnimationComponent.fromFrameData(
|
||||
await images.load('player.png'),
|
||||
data,
|
||||
);
|
||||
Future<void> onLoad() async {
|
||||
final size = Vector2.all(64.0);
|
||||
final data = SpriteAnimationData.sequenced(
|
||||
textureSize: size,
|
||||
amount: 2,
|
||||
stepTime: 0.1,
|
||||
);
|
||||
this.player = SpriteAnimationComponent.fromFrameData(
|
||||
await images.load('player.png'),
|
||||
data,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
If you are not using `FlameGame`, don't forget this component needs to be updated, because the
|
||||
@ -580,7 +584,7 @@ class ButtonComponent extends SpriteGroupComponent<ButtonState>
|
||||
@override
|
||||
Future<void>? onLoad() async {
|
||||
final pressedSprite = await gameRef.loadSprite(/* omitted */);
|
||||
final unpressedSprite = await gameRef.loadSprite(/* omitted /*);
|
||||
final unpressedSprite = await gameRef.loadSprite(/* omitted */);
|
||||
|
||||
sprites = {
|
||||
ButtonState.pressed: pressedSprite,
|
||||
@ -604,12 +608,14 @@ This component uses an instance of `Svg` class to represent a Component that has
|
||||
rendered in the game:
|
||||
|
||||
```dart
|
||||
final svg = await Svg.load('android.svg');
|
||||
final android = SvgComponent.fromSvg(
|
||||
svg,
|
||||
position: Vector2.all(100),
|
||||
size: Vector2.all(100),
|
||||
);
|
||||
Future<void> onLoad() async {
|
||||
final svg = await Svg.load('android.svg');
|
||||
final android = SvgComponent.fromSvg(
|
||||
svg,
|
||||
position: Vector2.all(100),
|
||||
size: Vector2.all(100),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -711,7 +717,7 @@ class MyParallaxComponent extends ParallaxComponent<MyGame> {
|
||||
|
||||
class MyGame extends FlameGame {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
void onLoad() {
|
||||
add(MyParallaxComponent());
|
||||
}
|
||||
}
|
||||
@ -726,20 +732,24 @@ They simplest way is to set the named optional parameters `baseVelocity` and
|
||||
background images along the X-axis with a faster speed the "closer" the image is:
|
||||
|
||||
```dart
|
||||
final parallaxComponent = await loadParallaxComponent(
|
||||
_dataList,
|
||||
baseVelocity: Vector2(20, 0),
|
||||
velocityMultiplierDelta: Vector2(1.8, 1.0),
|
||||
);
|
||||
Future<void> onLoad() async {
|
||||
final parallaxComponent = await loadParallaxComponent(
|
||||
_dataList,
|
||||
baseVelocity: Vector2(20, 0),
|
||||
velocityMultiplierDelta: Vector2(1.8, 1.0),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You can set the baseSpeed and layerDelta at any time, for example if your character jumps or your
|
||||
game speeds up.
|
||||
|
||||
```dart
|
||||
final parallax = parallaxComponent.parallax;
|
||||
parallax.baseSpeed = Vector2(100, 0);
|
||||
parallax.velocityMultiplierDelta = Vector2(2.0, 1.0);
|
||||
Future<void> onLoad() async {
|
||||
final parallax = parallaxComponent.parallax;
|
||||
parallax.baseSpeed = Vector2(100, 0);
|
||||
parallax.velocityMultiplierDelta = Vector2(2.0, 1.0);
|
||||
}
|
||||
```
|
||||
|
||||
By default, the images are aligned to the bottom left, repeated along the X-axis and scaled
|
||||
@ -997,20 +1007,23 @@ then add animation. Removing the stack will not remove the tiles from the map.
|
||||
> **Note**: This currently only supports position based effects.
|
||||
|
||||
```dart
|
||||
final stack = map.tileMap.tileStack(4, 0, named: {'floor_under'});
|
||||
stack.add(
|
||||
SequenceEffect(
|
||||
[
|
||||
MoveEffect.by(
|
||||
Vector2(5, 0),
|
||||
NoiseEffectController(duration: 1, frequency: 20),
|
||||
),
|
||||
MoveEffect.by(Vector2.zero(), LinearEffectController(2)),
|
||||
],
|
||||
repeatCount: 3,
|
||||
)..onComplete = () => stack.removeFromParent(),
|
||||
);
|
||||
map.add(stack);
|
||||
void onLoad() {
|
||||
final stack = map.tileMap.tileStack(4, 0, named: {'floor_under'});
|
||||
stack.add(
|
||||
SequenceEffect(
|
||||
[
|
||||
MoveEffect.by(
|
||||
Vector2(5, 0),
|
||||
NoiseEffectController(duration: 1, frequency: 20),
|
||||
),
|
||||
MoveEffect.by(Vector2.zero(), LinearEffectController(2)),
|
||||
],
|
||||
repeatCount: 3,
|
||||
)
|
||||
..onComplete = () => stack.removeFromParent(),
|
||||
);
|
||||
map.add(stack);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -1111,19 +1124,20 @@ be used:
|
||||
class MyGame extends FlameGame {
|
||||
int lives = 2;
|
||||
|
||||
Future<void> onLoad() {
|
||||
final playerNotifier = componentsNotifier<Player>()
|
||||
..addListener(() {
|
||||
final player = playerNotifier.single;
|
||||
if (player == null) {
|
||||
lives--;
|
||||
if (lives == 0) {
|
||||
add(GameOverComponent());
|
||||
} else {
|
||||
add(Player());
|
||||
}
|
||||
}
|
||||
});
|
||||
@override
|
||||
void onLoad() {
|
||||
final playerNotifier = componentsNotifier<Player>()
|
||||
..addListener(() {
|
||||
final player = playerNotifier.single;
|
||||
if (player == null) {
|
||||
lives--;
|
||||
if (lives == 0) {
|
||||
add(GameOverComponent());
|
||||
} else {
|
||||
add(Player());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -1152,16 +1166,17 @@ Then our hud component could look like:
|
||||
```dart
|
||||
class Hud extends PositionComponent with HasGameRef {
|
||||
|
||||
Future<void> onLoad() {
|
||||
final playerNotifier = gameRef.componentsNotifier<Player>()
|
||||
..addListener(() {
|
||||
final player = playerNotifier.single;
|
||||
if (player != null) {
|
||||
if (player.health <= .5) {
|
||||
add(BlinkEffect());
|
||||
}
|
||||
}
|
||||
});
|
||||
@override
|
||||
void onLoad() {
|
||||
final playerNotifier = gameRef.componentsNotifier<Player>()
|
||||
..addListener(() {
|
||||
final player = playerNotifier.single;
|
||||
if (player != null) {
|
||||
if (player.health <= .5) {
|
||||
add(BlinkEffect());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -79,10 +79,9 @@ class CalculatePrimeNumber extends PositionComponent
|
||||
DiscardNewBackPressureStrategy();
|
||||
|
||||
@override
|
||||
Future<void>? onLoad() {
|
||||
void onLoad() {
|
||||
width = 200;
|
||||
height = 70;
|
||||
return super.onLoad();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -97,7 +97,7 @@ class Bezel extends PositionComponent {
|
||||
late final Paint specularPaint;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
void onLoad() {
|
||||
rim = Path()..addOval(Rect.fromLTRB(-radius, -radius, radius, radius));
|
||||
final outer = radius + rimWidth / 2;
|
||||
final inner = radius - rimWidth / 2;
|
||||
|
||||
@ -12,12 +12,11 @@ class BouncingBallExample extends FlameGame with HasCollisionDetection {
|
||||
collides with the screen boundaries and then update it to bounce off these boundaries.
|
||||
''';
|
||||
@override
|
||||
Future<void>? onLoad() {
|
||||
void onLoad() {
|
||||
addAll([
|
||||
ScreenHitbox(),
|
||||
Ball(),
|
||||
]);
|
||||
return super.onLoad();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ This examples showcases how raycast APIs can be used to detect hits within certa
|
||||
)..positionType = PositionType.viewport;
|
||||
|
||||
@override
|
||||
Future<void>? onLoad() {
|
||||
void onLoad() {
|
||||
camera.viewport = FixedResolutionViewport(Vector2(320, 180));
|
||||
|
||||
_addMovingWall();
|
||||
@ -49,8 +49,6 @@ This examples showcases how raycast APIs can be used to detect hits within certa
|
||||
origin: _character.absolutePosition,
|
||||
direction: Vector2(1, 0),
|
||||
);
|
||||
|
||||
return super.onLoad();
|
||||
}
|
||||
|
||||
void _addMovingWall() {
|
||||
|
||||
@ -409,18 +409,17 @@ class Component {
|
||||
/// ```
|
||||
///
|
||||
/// Alternatively, if your [onLoad] function doesn't use any `await`ing, then
|
||||
/// you can declare it as a regular method and then return `null`:
|
||||
/// you can declare it as a regular method returning `void`:
|
||||
/// ```dart
|
||||
/// @override
|
||||
/// Future<void>? onLoad() {
|
||||
/// void onLoad() {
|
||||
/// // your code here
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The engine ensures that this method will be called exactly once during
|
||||
/// the lifetime of the [Component] object. Do not call this method manually.
|
||||
Future<void>? onLoad() => null;
|
||||
FutureOr<void> onLoad() => null;
|
||||
|
||||
/// Called when the component is added to its parent.
|
||||
///
|
||||
@ -530,14 +529,14 @@ class Component {
|
||||
/// try to add it to multiple parents, or even to the same parent multiple
|
||||
/// times. If you need to change the parent of a component, use the
|
||||
/// [changeParent] method.
|
||||
Future<void>? add(Component component) => component.addToParent(this);
|
||||
FutureOr<void> add(Component component) => component.addToParent(this);
|
||||
|
||||
/// A convenience method to [add] multiple children at once.
|
||||
Future<void> addAll(Iterable<Component> components) {
|
||||
final futures = <Future<void>>[];
|
||||
for (final component in components) {
|
||||
final future = add(component);
|
||||
if (future != null) {
|
||||
if (future is Future) {
|
||||
futures.add(future);
|
||||
}
|
||||
}
|
||||
@ -545,7 +544,7 @@ class Component {
|
||||
}
|
||||
|
||||
/// Adds this component as a child of [parent] (see [add] for details).
|
||||
Future<void>? addToParent(Component parent) {
|
||||
FutureOr<void> addToParent(Component parent) {
|
||||
assert(
|
||||
_parent == null,
|
||||
'$this cannot be added to $parent because it already has a parent: '
|
||||
@ -556,7 +555,6 @@ class Component {
|
||||
if (!isLoaded && (parent.findGame()?.hasLayout ?? false)) {
|
||||
return _startLoading();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Removes a component from the component tree.
|
||||
@ -760,7 +758,7 @@ class Component {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void>? _startLoading() {
|
||||
FutureOr<void> _startLoading() {
|
||||
assert(_state == _initial);
|
||||
assert(_parent != null);
|
||||
assert(_parent!.findGame() != null);
|
||||
@ -768,11 +766,10 @@ class Component {
|
||||
_setLoadingBit();
|
||||
onGameResize(_parent!.findGame()!.canvasSize);
|
||||
final onLoadFuture = onLoad();
|
||||
if (onLoadFuture == null) {
|
||||
_finishLoading();
|
||||
return null;
|
||||
if (onLoadFuture is Future) {
|
||||
return onLoadFuture.then((dynamic _) => _finishLoading());
|
||||
} else {
|
||||
return onLoadFuture.then((_) => _finishLoading());
|
||||
_finishLoading();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flame/cache.dart';
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/extensions.dart';
|
||||
@ -51,12 +53,12 @@ abstract class Game {
|
||||
Vector2? _size;
|
||||
|
||||
/// This variable ensures that Game's [onLoad] is called no more than once.
|
||||
late final Future<void>? _onLoadFuture = onLoad();
|
||||
late final FutureOr<void> _onLoadFuture = onLoad();
|
||||
|
||||
bool _debugOnLoadStarted = false;
|
||||
|
||||
@internal
|
||||
Future<void>? get onLoadFuture {
|
||||
FutureOr<void> get onLoadFuture {
|
||||
assert(
|
||||
() {
|
||||
_debugOnLoadStarted = true;
|
||||
@ -69,7 +71,7 @@ abstract class Game {
|
||||
/// To be used for tests that needs to evaluate the game after it has been
|
||||
/// loaded by the game widget.
|
||||
@visibleForTesting
|
||||
Future<void>? toBeLoaded() {
|
||||
FutureOr<void> toBeLoaded() {
|
||||
assert(
|
||||
_debugOnLoadStarted,
|
||||
'Make sure the game has passed to a mounted '
|
||||
@ -159,7 +161,7 @@ abstract class Game {
|
||||
///
|
||||
/// The engine ensures that this method will be called exactly once during
|
||||
/// the lifetime of the [Game] instance. Do not call this method manually.
|
||||
Future<void>? onLoad() => null;
|
||||
FutureOr<void> onLoad() => null;
|
||||
|
||||
void onMount() {}
|
||||
|
||||
|
||||
@ -16,13 +16,12 @@ class WorkerOvermindHud extends PositionComponent with Tappable {
|
||||
ComputeType computeType = ComputeType.isolate;
|
||||
|
||||
@override
|
||||
Future<void>? onLoad() {
|
||||
void onLoad() {
|
||||
x = 10;
|
||||
y = 10;
|
||||
width = 210;
|
||||
height = 80;
|
||||
positionType = PositionType.viewport;
|
||||
return super.onLoad();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/effects.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -40,7 +42,7 @@ mixin Movable on PositionComponent, HasGameRef<ColonistsGame> {
|
||||
|
||||
@override
|
||||
@mustCallSuper
|
||||
Future<void>? onLoad() {
|
||||
FutureOr<void> onLoad() {
|
||||
anchor = Anchor.center;
|
||||
return super.onLoad();
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ publish_to: 'none'
|
||||
version: 0.0.1+1
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.2 <3.0.0'
|
||||
sdk: '>=2.17.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
flame: ^1.5.0
|
||||
|
||||
@ -50,7 +50,7 @@ class SkillsAnimationComponent extends RiveComponent with Tappable {
|
||||
SMIInput<double>? _levelInput;
|
||||
|
||||
@override
|
||||
Future<void>? onLoad() {
|
||||
void onLoad() {
|
||||
final controller = StateMachineController.fromArtboard(
|
||||
artboard,
|
||||
"Designer's Test",
|
||||
@ -60,7 +60,6 @@ class SkillsAnimationComponent extends RiveComponent with Tappable {
|
||||
_levelInput = controller.findInput<double>('Level');
|
||||
_levelInput?.value = 0;
|
||||
}
|
||||
return super.onLoad();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user