refactor: Game is now a class, not a mixin (#1751)

This simple refactor allows us to write class MyGame extends Game, instead of a more awkward class MyGame with Game. However, using ... with Game still continues to work, so no changes necessary for the users.
This commit is contained in:
Pasha Stetsenko
2022-06-25 14:32:32 -07:00
committed by GitHub
parent 20f169cadb
commit 5225a4ebd5
20 changed files with 54 additions and 57 deletions

View File

@ -138,7 +138,7 @@ class MyGame extends FlameGame {
anchor: Anchor.center, anchor: Anchor.center,
); );
add(player); add(player);
camera.followComponent(player); camera.followComponent(player);
} }
} }
@ -148,17 +148,20 @@ class MyGame extends FlameGame {
### Using the camera with the Game class ### Using the camera with the Game class
If you are not using `FlameGame`, but instead are using the `Game` mixin, then you need to manage If you are not using `FlameGame`, but instead are using the `Game` mixin, then you need to manage
calling certain camera methods yourself. Let's say we have the following game structure, and we calling certain camera methods yourself. Let's say we have the following game structure, and we
want to add the camera functionality: want to add the camera functionality:
```dart ```dart
class YourGame with Game { class YourGame extends Game {
Camera? camera; Camera? camera;
@override
Future<void> onLoad() async {} Future<void> onLoad() async {}
@override
void render(Canvas canvas) {} void render(Canvas canvas) {}
@override
void update(double dt) {} void update(double dt) {}
} }
``` ```
@ -166,8 +169,7 @@ class YourGame with Game {
We first create a new camera instance on load and assign our game as the reference: We first create a new camera instance on load and assign our game as the reference:
```dart ```dart
// ... @override
Future<void> onLoad() async { Future<void> onLoad() async {
camera = Camera(); camera = Camera();
@ -179,51 +181,42 @@ We first create a new camera instance on load and assign our game as the referen
// Rest of your on load code. // Rest of your on load code.
} }
// ...
``` ```
The camera can also be made aware of which position to follow, this is an optional feature as you The camera can also be made aware of which position to follow, this is an optional feature as you
can also use the camare for just moving,snapping or shaking. can also use the camera for just moving,snapping or shaking.
To do this the `Camera` class provides multiple methods for it but let's showcase the simplest one To do this the `Camera` class provides multiple methods for it but let's showcase the simplest one
and that is the `followVector2`: and that is the `followVector2`:
```dart ```dart
// Somewhere in your code. // Somewhere in your code:
camera?.followVector2( camera?.followVector2(
yourPositionToFollow, yourPositionToFollow,
worldBounds: yourWorldBounds, // Optional to pass, it will overwrite the previous bounds. worldBounds: yourWorldBounds, // Optional to pass, it will overwrite the previous bounds.
); );
``` ```
Now that the camera is created and it is aware of both the world bounds and the position it should Now that the camera is created and it is aware of both the world bounds and the position it should
follow, it can be used to translate the canvas in the render method: follow, it can be used to translate the canvas in the render method:
```dart ```dart
// ... @override
void render(Canvas canvas) { void render(Canvas canvas) {
camera?.apply(canvas); // This will apply the camera transformation. camera?.apply(canvas); // This will apply the camera transformation.
// Rest of your rendering code. // Rest of your rendering code.
} }
// ...
``` ```
The only thing left to do is to call the `update` method on the `Camera` so it can smoothly follow The only thing left to do is to call the `update` method on the `Camera` so it can smoothly follow
your given position: your given position:
```dart ```dart
// ... @override
void update(double dt) { void update(double dt) {
camera?.update(dt); camera?.update(dt);
// Rest of your update code. // Rest of your update code.
} }
// ...
``` ```

View File

@ -41,10 +41,12 @@ main() {
} }
``` ```
**Note:** If you instantiate your game in a build method your game will be rebuilt every time the ```{note}
Flutter tree gets rebuilt, which usually is more often than you'd like. To avoid this, you can If you instantiate your game in a build method your game will be rebuilt every time the
instead create an instance of your game first and reference it within your widget structure, like Flutter tree gets rebuilt, which usually is more often than you'd like. To avoid this, you can
it is done in the example above. instead create an instance of your game first and reference it within your widget structure, like
it is done in the example above.
```
To remove components from the list on a `FlameGame` the `remove` or `removeAll` methods can be used. To remove components from the list on a `FlameGame` the `remove` or `removeAll` methods can be used.
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
@ -103,7 +105,7 @@ just draw a background that covers the whole canvas if you would want it to chan
## SingleGameInstance mixin ## SingleGameInstance mixin
An optional mixin `SingleGameInstance` can be applied to your game if you are making a single-game An optional mixin `SingleGameInstance` can be applied to your game if you are making a single-game
application. This is a common scenario when building games: there is a single full-screen application. This is a common scenario when building games: there is a single full-screen
`GameWidget` which hosts a single `Game` instance. `GameWidget` which hosts a single `Game` instance.
Adding this mixin provides performance advantages in certain scenarios. In particular, a component's Adding this mixin provides performance advantages in certain scenarios. In particular, a component's
@ -123,7 +125,7 @@ class MyGame extends FlameGame with SingleGameInstance {
![Game low-level API](../images/game_mixin.png) ![Game low-level API](../images/game_mixin.png)
The `Game` mixin is a low-level API that can be used when you want to implement the functionality of The `Game` class is a low-level API that can be used when you want to implement the functionality of
how the game engine should be structured. `Game` does not implement any `update` or how the game engine should be structured. `Game` does not implement any `update` or
`render` function for example. `render` function for example.
@ -133,13 +135,15 @@ called from the `GameWidget` (or another parent) when the game is loaded + mount
called after `onLoad`) is called every time it is added to a new parent. `onRemove` is called when called after `onLoad`) is called every time it is added to a new parent. `onRemove` is called when
the class is removed from a parent. the class is removed from a parent.
**Note**: The `Game` mixin allows for more freedom of how to implement things, but you are also ```{note}
The `Game` class allows for more freedom of how to implement things, but you are also
missing out on all of the built-in features in Flame if you use it. missing out on all of the built-in features in Flame if you use it.
```
An example of how a `Game` implementation could look like: An example of how a `Game` implementation could look like:
```dart ```dart
class MyGameSubClass with Game { class MyGameSubClass extends Game {
@override @override
void render(Canvas canvas) { void render(Canvas canvas) {
// ... // ...
@ -151,7 +155,7 @@ class MyGameSubClass with Game {
} }
} }
main() { void main() {
final myGame = MyGameSubClass(); final myGame = MyGameSubClass();
runApp( runApp(
GameWidget( GameWidget(
@ -202,7 +206,7 @@ by setting a `overlayBuilderMap`.
void main() { void main() {
// Inside the game methods: // Inside the game methods:
final pauseOverlayIdentifier = 'PauseMenu'; final pauseOverlayIdentifier = 'PauseMenu';
// Marks 'PauseMenu' to be rendered. // Marks 'PauseMenu' to be rendered.
overlays.add(pauseOverlayIdentifier); overlays.add(pauseOverlayIdentifier);
// Marks 'PauseMenu' to not be rendered. // Marks 'PauseMenu' to not be rendered.
@ -226,8 +230,8 @@ Widget build(BuildContext context) {
} }
``` ```
The order of rendering for an overlay is determined by the order of the keys in the The order of rendering for an overlay is determined by the order of the keys in the
`overlayBuilderMap`. `overlayBuilderMap`.
An example of feature can be found An example of feature can be found
[here](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/system/overlays_example.dart). [here](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/system/overlays_example.dart).

View File

@ -2,7 +2,7 @@ import 'package:flame/extensions.dart';
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flame/parallax.dart'; import 'package:flame/parallax.dart';
class NoFCSParallaxExample with Game { class NoFCSParallaxExample extends Game {
static const String description = ''' static const String description = '''
This examples serves to test the Parallax feature outside of the Flame This examples serves to test the Parallax feature outside of the Flame
Component System (FCS), use the other files in this folder for examples on Component System (FCS), use the other files in this folder for examples on

View File

@ -4,7 +4,7 @@ import 'package:flame/palette.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
class NoFlameGameExample with Game, KeyboardEvents { class NoFlameGameExample extends Game with KeyboardEvents {
static const String description = ''' static const String description = '''
This example showcases how to create a game without the FlameGame. This example showcases how to create a game without the FlameGame.
It also briefly showcases how to act on keyboard events. It also briefly showcases how to act on keyboard events.

View File

@ -5,9 +5,9 @@ export 'src/extensions/vector2.dart';
export 'src/game/camera/camera.dart'; export 'src/game/camera/camera.dart';
export 'src/game/camera/viewport.dart'; export 'src/game/camera/viewport.dart';
export 'src/game/flame_game.dart'; export 'src/game/flame_game.dart';
export 'src/game/game.dart';
export 'src/game/game_widget/game_widget.dart'; export 'src/game/game_widget/game_widget.dart';
export 'src/game/mixins/fps_counter.dart'; export 'src/game/mixins/fps_counter.dart';
export 'src/game/mixins/game.dart';
export 'src/game/mixins/has_draggables.dart'; export 'src/game/mixins/has_draggables.dart';
export 'src/game/mixins/has_hoverables.dart'; export 'src/game/mixins/has_hoverables.dart';
export 'src/game/mixins/has_tappables.dart'; export 'src/game/mixins/has_tappables.dart';

View File

@ -7,7 +7,7 @@ import 'package:flame/src/components/component_set.dart';
import 'package:flame/src/components/mixins/coordinate_transform.dart'; import 'package:flame/src/components/mixins/coordinate_transform.dart';
import 'package:flame/src/components/position_type.dart'; import 'package:flame/src/components/position_type.dart';
import 'package:flame/src/game/flame_game.dart'; import 'package:flame/src/game/flame_game.dart';
import 'package:flame/src/game/mixins/game.dart'; import 'package:flame/src/game/game.dart';
import 'package:flame/src/gestures/events.dart'; import 'package:flame/src/gestures/events.dart';
import 'package:flame/src/text/text_paint.dart'; import 'package:flame/src/text/text_paint.dart';
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';

View File

@ -1,5 +1,5 @@
import 'package:flame/src/events/interfaces/multi_drag_listener.dart'; import 'package:flame/src/events/interfaces/multi_drag_listener.dart';
import 'package:flame/src/game/mixins/game.dart'; import 'package:flame/src/game/game.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';

View File

@ -1,6 +1,6 @@
import 'package:flame/src/events/flame_game_mixins/has_draggable_components.dart'; import 'package:flame/src/events/flame_game_mixins/has_draggable_components.dart';
import 'package:flame/src/events/interfaces/multi_drag_listener.dart'; import 'package:flame/src/events/interfaces/multi_drag_listener.dart';
import 'package:flame/src/game/mixins/game.dart'; import 'package:flame/src/game/game.dart';
import 'package:flame/src/gestures/events.dart'; import 'package:flame/src/gestures/events.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';

View File

@ -1,6 +1,6 @@
import 'package:flame/src/events/flame_game_mixins/has_tappable_components.dart'; import 'package:flame/src/events/flame_game_mixins/has_tappable_components.dart';
import 'package:flame/src/events/interfaces/multi_tap_listener.dart'; import 'package:flame/src/events/interfaces/multi_tap_listener.dart';
import 'package:flame/src/game/mixins/game.dart'; import 'package:flame/src/game/game.dart';
import 'package:flame/src/gestures/events.dart'; import 'package:flame/src/gestures/events.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';

View File

@ -4,7 +4,7 @@ import 'package:flame/src/events/flame_game_mixins/has_tappable_components.dart'
import 'package:flame/src/events/messages/position_event.dart'; import 'package:flame/src/events/messages/position_event.dart';
import 'package:flame/src/events/messages/tap_cancel_event.dart'; import 'package:flame/src/events/messages/tap_cancel_event.dart';
import 'package:flame/src/events/messages/tap_up_event.dart'; import 'package:flame/src/events/messages/tap_up_event.dart';
import 'package:flame/src/game/mixins/game.dart'; import 'package:flame/src/game/game.dart';
import 'package:flame/src/gestures/events.dart'; import 'package:flame/src/gestures/events.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';

View File

@ -1,7 +1,7 @@
import 'package:flame/extensions.dart'; import 'package:flame/extensions.dart';
import 'package:flame/src/events/messages/position_event.dart'; import 'package:flame/src/events/messages/position_event.dart';
import 'package:flame/src/events/messages/tap_down_event.dart'; import 'package:flame/src/events/messages/tap_down_event.dart';
import 'package:flame/src/game/mixins/game.dart'; import 'package:flame/src/game/game.dart';
import 'package:flame/src/gestures/events.dart'; import 'package:flame/src/gestures/events.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';

View File

@ -4,7 +4,7 @@ import 'package:flame/src/components/component.dart';
import 'package:flame/src/extensions/vector2.dart'; import 'package:flame/src/extensions/vector2.dart';
import 'package:flame/src/game/camera/camera.dart'; import 'package:flame/src/game/camera/camera.dart';
import 'package:flame/src/game/camera/camera_wrapper.dart'; import 'package:flame/src/game/camera/camera_wrapper.dart';
import 'package:flame/src/game/mixins/game.dart'; import 'package:flame/src/game/game.dart';
import 'package:flame/src/game/projector.dart'; import 'package:flame/src/game/projector.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';

View File

@ -10,11 +10,11 @@ import 'package:meta/meta.dart';
/// This gives access to a low-level game API, to not build everything from a /// This gives access to a low-level game API, to not build everything from a
/// low level `FlameGame` should be used. /// low level `FlameGame` should be used.
/// ///
/// Add this mixin to your game class and implement the [update] and [render] /// You can either extend this class, or add it as a mixin.
/// methods to use it in a `GameWidget`. ///
/// Flame will deal with calling these methods properly when the game's widget /// Methods [update] and [render] need to be implemented in order to connect
/// is rendered. /// your class with the internal game loop.
mixin Game { abstract class Game {
final images = Images(); final images = Images();
final assets = AssetsCache(); final assets = AssetsCache();

View File

@ -1,5 +1,5 @@
import 'package:flame/src/game/game.dart';
import 'package:flame/src/game/game_loop.dart'; import 'package:flame/src/game/game_loop.dart';
import 'package:flame/src/game/mixins/game.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart' hide WidgetBuilder; import 'package:flutter/widgets.dart' hide WidgetBuilder;
//ignore_for_file: unnecessary_non_null_assertion //ignore_for_file: unnecessary_non_null_assertion

View File

@ -2,9 +2,9 @@ import 'dart:async';
import 'package:flame/extensions.dart'; import 'package:flame/extensions.dart';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:flame/src/game/game.dart';
import 'package:flame/src/game/game_render_box.dart'; import 'package:flame/src/game/game_render_box.dart';
import 'package:flame/src/game/game_widget/gestures.dart'; import 'package:flame/src/game/game_widget/gestures.dart';
import 'package:flame/src/game/mixins/game.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';

View File

@ -1,6 +1,6 @@
import 'package:flame/events.dart'; import 'package:flame/events.dart';
import 'package:flame/src/events/flame_drag_adapter.dart'; import 'package:flame/src/events/flame_drag_adapter.dart';
import 'package:flame/src/game/mixins/game.dart'; import 'package:flame/src/game/game.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';

View File

@ -1,5 +1,5 @@
import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/component.dart';
import 'package:flame/src/game/mixins/game.dart'; import 'package:flame/src/game/game.dart';
/// Mixin that declares a [Game] class as a singleton. /// Mixin that declares a [Game] class as a singleton.
/// ///

View File

@ -1,4 +1,4 @@
import 'package:flame/src/game/mixins/game.dart'; import 'package:flame/src/game/game.dart';
import 'package:flame/src/gestures/events.dart'; import 'package:flame/src/gestures/events.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';

View File

@ -1,5 +1,5 @@
import 'package:flame/extensions.dart'; import 'package:flame/extensions.dart';
import 'package:flame/src/game/mixins/game.dart'; import 'package:flame/src/game/game.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
/// [EventPosition] converts position based events to three different coordinate /// [EventPosition] converts position based events to three different coordinate

View File

@ -10,7 +10,7 @@ import 'package:oxygen/oxygen.dart';
/// [OxygenGame] should be extended to add your own game logic. /// [OxygenGame] should be extended to add your own game logic.
/// ///
/// It is based on the Oxygen package. /// It is based on the Oxygen package.
abstract class OxygenGame with Game { abstract class OxygenGame extends Game {
late final FlameWorld world; late final FlameWorld world;
OxygenGame() { OxygenGame() {