Skip to content

Commit

Permalink
feat: Children as argument to FlameGame (flame-engine#1680)
Browse files Browse the repository at this point in the history
Since we have already added children as an argument to Component, this adds it to the FlameGame.
  • Loading branch information
spydon authored and st-pasha committed Jun 3, 2022
1 parent 48cc8b7 commit f3234e9
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 41 deletions.
27 changes: 22 additions & 5 deletions doc/flame/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,30 @@ This diagram might look intimidating, but don't worry, it is not as complex as i

## 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
[](#positioncomponent).
Children can be added either with the `add(Component c)` method or directly in the constructor.

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
`FlameGame` class. If you are not using `FlameGame`, you can use these methods on your own game loop
if you wish.
`FlameGame` class.


### Component lifecycle

![Component Lifecycle Diagram](../images/component_lifecycle.png)

Expand All @@ -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).

A component lifecycle state can be checked by a series of getters:

- `isLoaded`: Returns a bool with the current loaded state
- `loaded`: Returns a future that will complete once the component has finished loading
- `isMounted`: Returns a bool with the current mounted state
Expand Down
43 changes: 19 additions & 24 deletions doc/flame/game.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,27 @@
# 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
and passes the `update` and `render` calls to all `Component`s that have been added to the game.
The `FlameGame` class implements a `Component` based `Game`. Basically it has a tree of `Component`s
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.

Every time the game needs to be resized, for example when the orientation is changed,
`FlameGame` will call all of the `Component`s `resize` 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`).
Components can be added to the `FlameGame` directly in the constructor with the named `children`
argument, or from anywhere else with the `add`/`addAll` methods.

A `FlameGame` implementation example can be seen below:
A simple `FlameGame` implementation which adds two components, one in `onLoad` and one directly in
the constructor can look like this:

```dart
/// A component that renders the crate sprite, with a 16 x 16 size.
class MyCrate extends SpriteComponent {
// creates a component that renders the crate.png sprite, with size 16 x 16
MyCrate() : super(size: Vector2.all(16), anchor: Anchor.center);
MyCrate() : super(size: Vector2.all(16));
@override
Future<void> onLoad() async {
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 {
Expand All @@ -44,7 +32,7 @@ class MyGame extends FlameGame {
}
main() {
final myGame = MyGame();
final myGame = MyGame(children: [MyCrate]);
runApp(
GameWidget(
game: myGame,
Expand All @@ -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
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
Expand Down
19 changes: 9 additions & 10 deletions examples/lib/stories/components/priority_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@ class PriorityExample extends FlameGame with HasTappables {
the priority.
''';

@override
Future<void> onLoad() async {
final squares = [
Square(Vector2(100, 100)),
Square(Vector2(160, 100)),
Square(Vector2(170, 150)),
Square(Vector2(110, 150)),
];
addAll(squares);
}
PriorityExample()
: super(
children: [
Square(Vector2(100, 100)),
Square(Vector2(160, 100)),
Square(Vector2(170, 150)),
Square(Vector2(110, 150)),
],
);
}

class Square extends RectangleComponent
Expand Down
7 changes: 5 additions & 2 deletions packages/flame/lib/src/game/flame_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ import 'package:meta/meta.dart';
/// 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).
class FlameGame extends Component with Game {
FlameGame({Camera? camera}) {
FlameGame({
Iterable<Component>? children,
Camera? camera,
}) : super(children: children) {
assert(
Component.staticGameInstance == null,
'$this instantiated, while another game ${Component.staticGameInstance} '
'declares itself to be a singleton',
);
_cameraWrapper = CameraWrapper(camera ?? Camera(), children);
_cameraWrapper = CameraWrapper(camera ?? Camera(), this.children);
}

late final CameraWrapper _cameraWrapper;
Expand Down
67 changes: 67 additions & 0 deletions packages/flame/test/game/flame_game_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/src/game/game_render_box.dart';
Expand Down Expand Up @@ -610,10 +611,76 @@ void main() {
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 _MyTappableComponent extends _MyComponent with Tappable {
Expand Down

0 comments on commit f3234e9

Please sign in to comment.