feat: Children as argument to FlameGame (#1680)

Since we have already added children as an argument to Component, this adds it to the FlameGame.
This commit is contained in:
Lukas Klingsbo
2022-06-01 22:58:59 +02:00
committed by GitHub
parent 801dbba1d8
commit db336c03b6
5 changed files with 122 additions and 41 deletions

View File

@ -7,14 +7,30 @@ This diagram might look intimidating, but don't worry, it is not as complex as i
## Component ## Component
All components inherit from the abstract class `Component`. All components inherit from the abstract class `Component` and all components can have other
`Component`s as children. This is the base of what we call the Flame Component System, or FCS for
short.
If you want to skip reading about abstract classes you can jump directly to Children can be added either with the `add(Component c)` method or directly in the constructor.
[](#positioncomponent).
Example:
```dart
void main() {
final component1 = Component(children: [Component(), Component()]);
final component2 = Component();
component2.add(Component());
component2.addAll([Component(), Component()]);
}
```
The `Component()` here could of course be any subclass of `Component`.
Every `Component` has a few methods that you can optionally implement, which are used by the Every `Component` has a few methods that you can optionally implement, which are used by the
`FlameGame` class. If you are not using `FlameGame`, you can use these methods on your own game loop `FlameGame` class.
if you wish.
### Component lifecycle
![Component Lifecycle Diagram](../images/component_lifecycle.png) ![Component Lifecycle Diagram](../images/component_lifecycle.png)
@ -37,6 +53,7 @@ If the parent is not mounted yet, then this method will wait in a queue (this wi
on the rest of the game engine). on the rest of the game engine).
A component lifecycle state can be checked by a series of getters: A component lifecycle state can be checked by a series of getters:
- `isLoaded`: Returns a bool with the current loaded state - `isLoaded`: Returns a bool with the current loaded state
- `loaded`: Returns a future that will complete once the component has finished loading - `loaded`: Returns a future that will complete once the component has finished loading
- `isMounted`: Returns a bool with the current mounted state - `isMounted`: Returns a bool with the current mounted state

View File

@ -1,39 +1,27 @@
# FlameGame # FlameGame
`FlameGame` is the most basic and most commonly used `Game` class in Flame. `FlameGame` is the most most commonly used `Game` class in Flame.
The `FlameGame` class implements a `Component` based `Game`. Basically it has a list of `Component`s The `FlameGame` class implements a `Component` based `Game`. Basically it has a tree of `Component`s
and passes the `update` and `render` calls to all `Component`s that have been added to the game. and calls the `update` and `render` methods of all `Component`s that have been added to the game.
We refer to this component based system as the Flame Component System, FCS for short. We refer to this component based system as the Flame Component System, FCS for short.
Every time the game needs to be resized, for example when the orientation is changed, Components can be added to the `FlameGame` directly in the constructor with the named `children`
`FlameGame` will call all of the `Component`s `resize` methods and it will also pass this information argument, or from anywhere else with the `add`/`addAll` methods.
to the camera and viewport.
The `FlameGame.camera` controls which point in the coordinate space should be the top-left of the A simple `FlameGame` implementation which adds two components, one in `onLoad` and one directly in
screen (it defaults to [0,0] like a regular `Canvas`). the constructor can look like this:
A `FlameGame` implementation example can be seen below:
```dart ```dart
/// A component that renders the crate sprite, with a 16 x 16 size.
class MyCrate extends SpriteComponent { class MyCrate extends SpriteComponent {
// creates a component that renders the crate.png sprite, with size 16 x 16 MyCrate() : super(size: Vector2.all(16));
MyCrate() : super(size: Vector2.all(16), anchor: Anchor.center);
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
sprite = await Sprite.load('crate.png'); sprite = await Sprite.load('crate.png');
} }
@override
void onGameResize(Vector2 gameSize) {
super.onGameResize(gameSize);
// We don't need to set the position in the constructor, we can set it
// directly here since it will be called once before the first time it
// is rendered.
position = gameSize / 2;
}
} }
class MyGame extends FlameGame { class MyGame extends FlameGame {
@ -44,7 +32,7 @@ class MyGame extends FlameGame {
} }
main() { main() {
final myGame = MyGame(); final myGame = MyGame(children: [MyCrate]);
runApp( runApp(
GameWidget( GameWidget(
game: myGame, game: myGame,
@ -62,8 +50,15 @@ To remove components from the list on a `FlameGame` the `remove` or `removeAll`
The first can be used if you just want to remove one component, and the second can be used when you The first can be used if you just want to remove one component, and the second can be used when you
want to remove a list of components. want to remove a list of components.
Any component on which the `remove()` method has been called will also be removed. You can do this
simply by doing `yourComponent.remove();`. ## Resizing
Every time the game needs to be resized, for example when the orientation is changed,
`FlameGame` will call all of the `Component`s `onGameResize` methods and it will also pass this
information to the camera and viewport.
The `FlameGame.camera` controls which point in the coordinate space should be the top-left of the
screen (it defaults to [0,0] like a regular `Canvas`).
## Lifecycle ## Lifecycle

View File

@ -9,16 +9,15 @@ class PriorityExample extends FlameGame with HasTappables {
the priority. the priority.
'''; ''';
@override PriorityExample()
Future<void> onLoad() async { : super(
final squares = [ children: [
Square(Vector2(100, 100)), Square(Vector2(100, 100)),
Square(Vector2(160, 100)), Square(Vector2(160, 100)),
Square(Vector2(170, 150)), Square(Vector2(170, 150)),
Square(Vector2(110, 150)), Square(Vector2(110, 150)),
]; ],
addAll(squares); );
}
} }
class Square extends RectangleComponent class Square extends RectangleComponent

View File

@ -16,13 +16,16 @@ import 'package:meta/meta.dart';
/// This is the recommended base class to use for most games made with Flame. /// This is the recommended base class to use for most games made with Flame.
/// It is based on the Flame Component System (also known as FCS). /// It is based on the Flame Component System (also known as FCS).
class FlameGame extends Component with Game { class FlameGame extends Component with Game {
FlameGame({Camera? camera}) { FlameGame({
Iterable<Component>? children,
Camera? camera,
}) : super(children: children) {
assert( assert(
Component.staticGameInstance == null, Component.staticGameInstance == null,
'$this instantiated, while another game ${Component.staticGameInstance} ' '$this instantiated, while another game ${Component.staticGameInstance} '
'declares itself to be a singleton', 'declares itself to be a singleton',
); );
_cameraWrapper = CameraWrapper(camera ?? Camera(), children); _cameraWrapper = CameraWrapper(camera ?? Camera(), this.children);
} }
late final CameraWrapper _cameraWrapper; late final CameraWrapper _cameraWrapper;

View File

@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flame/src/game/game_render_box.dart'; import 'package:flame/src/game/game_render_box.dart';
@ -610,10 +611,76 @@ void main() {
expect(game.projector.unscaleVector(Vector2(8, 16)), Vector2(1, 2)); expect(game.projector.unscaleVector(Vector2(8, 16)), Vector2(1, 2));
}, },
); );
testWithGame<FlameGame>(
'children in the constructor',
() {
return FlameGame(
children: [_IndexedComponent(1), _IndexedComponent(2)],
);
},
(game) async {
game.add(_IndexedComponent(3));
game.add(_IndexedComponent(4));
await game.ready();
expect(game.children.length, 4);
expect(
game.children
.whereType<_IndexedComponent>()
.map((c) => c.index)
.isSorted((a, b) => a.compareTo(b)),
isTrue,
);
},
);
testWithGame<FlameGame>(
'children in the constructor and onLoad',
() {
return _ConstructorChildrenGame(
constructorChildren: [_IndexedComponent(1), _IndexedComponent(2)],
onLoadChildren: [_IndexedComponent(3), _IndexedComponent(4)],
);
},
(game) async {
game.add(_IndexedComponent(5));
game.add(_IndexedComponent(6));
await game.ready();
expect(game.children.length, 6);
expect(
game.children
.whereType<_IndexedComponent>()
.map((c) => c.index)
.isSorted((a, b) => a.compareTo(b)),
isTrue,
);
},
);
}); });
}); });
} }
class _IndexedComponent extends Component {
final int index;
_IndexedComponent(this.index);
}
class _ConstructorChildrenGame extends FlameGame {
final Iterable<_IndexedComponent> onLoadChildren;
_ConstructorChildrenGame({
required Iterable<_IndexedComponent> constructorChildren,
required this.onLoadChildren,
}) : super(children: constructorChildren);
@override
Future<void> onLoad() async {
addAll(onLoadChildren);
}
}
class _GameWithTappables extends FlameGame with HasTappables {} class _GameWithTappables extends FlameGame with HasTappables {}
class _MyTappableComponent extends _MyComponent with Tappable { class _MyTappableComponent extends _MyComponent with Tappable {