From f3234e9766dd6c1fde2e7e7e4b2aa93765c285b1 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 1 Jun 2022 22:58:59 +0200 Subject: [PATCH] feat: Children as argument to FlameGame (#1680) Since we have already added children as an argument to Component, this adds it to the FlameGame. --- doc/flame/components.md | 27 ++++++-- doc/flame/game.md | 43 ++++++------ .../stories/components/priority_example.dart | 19 +++--- packages/flame/lib/src/game/flame_game.dart | 7 +- packages/flame/test/game/flame_game_test.dart | 67 +++++++++++++++++++ 5 files changed, 122 insertions(+), 41 deletions(-) diff --git a/doc/flame/components.md b/doc/flame/components.md index 59a0fdd8411..c3ce5b91597 100644 --- a/doc/flame/components.md +++ b/doc/flame/components.md @@ -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) @@ -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 diff --git a/doc/flame/game.md b/doc/flame/game.md index 2835e7c7308..5968f06e36d 100644 --- a/doc/flame/game.md +++ b/doc/flame/game.md @@ -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 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 { @@ -44,7 +32,7 @@ class MyGame extends FlameGame { } main() { - final myGame = MyGame(); + final myGame = MyGame(children: [MyCrate]); runApp( GameWidget( 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 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 diff --git a/examples/lib/stories/components/priority_example.dart b/examples/lib/stories/components/priority_example.dart index 1ff865cf16b..2a6fe363771 100644 --- a/examples/lib/stories/components/priority_example.dart +++ b/examples/lib/stories/components/priority_example.dart @@ -9,16 +9,15 @@ class PriorityExample extends FlameGame with HasTappables { the priority. '''; - @override - Future 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 diff --git a/packages/flame/lib/src/game/flame_game.dart b/packages/flame/lib/src/game/flame_game.dart index b19c91b7888..885e7c796e5 100644 --- a/packages/flame/lib/src/game/flame_game.dart +++ b/packages/flame/lib/src/game/flame_game.dart @@ -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? 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; diff --git a/packages/flame/test/game/flame_game_test.dart b/packages/flame/test/game/flame_game_test.dart index da38c2a6495..debe14f76c3 100644 --- a/packages/flame/test/game/flame_game_test.dart +++ b/packages/flame/test/game/flame_game_test.dart @@ -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'; @@ -610,10 +611,76 @@ void main() { expect(game.projector.unscaleVector(Vector2(8, 16)), Vector2(1, 2)); }, ); + + testWithGame( + '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( + '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 onLoad() async { + addAll(onLoadChildren); + } +} + class _GameWithTappables extends FlameGame with HasTappables {} class _MyTappableComponent extends _MyComponent with Tappable {