diff --git a/.github/.cspell/gamedev_dictionary.txt b/.github/.cspell/gamedev_dictionary.txt index c72f6d39566..cf1179a5141 100644 --- a/.github/.cspell/gamedev_dictionary.txt +++ b/.github/.cspell/gamedev_dictionary.txt @@ -24,6 +24,8 @@ arial # name of a typeface arities # plural of arity arity # number of parameters a function takes autofocus # auto focus event +backgrounded # moving the app to the background +backgrounding # moving the app to the background backpressure # strategy to deal with excess flow of data backquote # another word for backtick backtick # the back tick character "`" @@ -39,6 +41,7 @@ coord # coordinate coords # plural of coord deduplication # removal of duplicates easings # Easing functions specify the rate of change of a parameter over time +foregrounded # moving the app to the foreground fullscreen # mode in which a program or app occupies the entire screen with no borders goldens # test files used as reference for Golden Tests hardcoding # putting a value as a literal instead of computing it diff --git a/doc/flame/game.md b/doc/flame/game.md index d4a5d2c093f..51db73c5687 100644 --- a/doc/flame/game.md +++ b/doc/flame/game.md @@ -226,3 +226,21 @@ While the game is paused, it is possible to advanced it frame by frame using the method. It might not be much useful in the final game, but can be very helpful in inspecting game state step by step during the development cycle. + + +### Backgrounding + +The game will be automatically paused when the app is sent to the background, +and resumed when it comes back to the foreground. This behavior can be disabled by setting +`pauseWhenBackgrounded` to `false`. + +```dart +class MyGame extends FlameGame { + MyGame() { + pauseWhenBackgrounded = false; + } +} +``` + +On the current Flutter stable (3.13), this flag is effectively ignored on +non-mobile platforms including the web. diff --git a/packages/flame/lib/src/game/flame_game.dart b/packages/flame/lib/src/game/flame_game.dart index 746ed909da8..59ca8bf5cdc 100644 --- a/packages/flame/lib/src/game/flame_game.dart +++ b/packages/flame/lib/src/game/flame_game.dart @@ -255,4 +255,44 @@ class FlameGame extends ComponentTreeRoot } } } + + /// Whether the game should pause when the app is backgrounded. + /// + /// On the latest Flutter stable at the time of writing (3.13), + /// this is only working on Android and iOS. + /// + /// Defaults to true. + bool pauseWhenBackgrounded = true; + bool _pausedBecauseBackgrounded = false; + + @override + @mustCallSuper + void lifecycleStateChange(AppLifecycleState state) { + switch (state) { + case AppLifecycleState.resumed: + case AppLifecycleState.inactive: + if (_pausedBecauseBackgrounded) { + resumeEngine(); + } + case AppLifecycleState.paused: + case AppLifecycleState.detached: + case AppLifecycleState.hidden: + if (pauseWhenBackgrounded && !paused) { + pauseEngine(); + _pausedBecauseBackgrounded = true; + } + } + } + + @override + void pauseEngine() { + _pausedBecauseBackgrounded = false; + super.pauseEngine(); + } + + @override + void resumeEngine() { + _pausedBecauseBackgrounded = false; + super.resumeEngine(); + } } diff --git a/packages/flame/lib/src/game/game_render_box.dart b/packages/flame/lib/src/game/game_render_box.dart index 608561ef748..549b91f48b8 100644 --- a/packages/flame/lib/src/game/game_render_box.dart +++ b/packages/flame/lib/src/game/game_render_box.dart @@ -132,10 +132,14 @@ class GameRenderBox extends RenderBox with WidgetsBindingObserver { void _bindLifecycleListener() { WidgetsBinding.instance.addObserver(this); + didChangeAppLifecycleState( + WidgetsBinding.instance.lifecycleState ?? AppLifecycleState.resumed, + ); } void _unbindLifecycleListener() { WidgetsBinding.instance.removeObserver(this); + didChangeAppLifecycleState(AppLifecycleState.paused); } @override diff --git a/packages/flame/test/game/flame_game_test.dart b/packages/flame/test/game/flame_game_test.dart index 7a203cc6f98..2404d7a01a6 100644 --- a/packages/flame/test/game/flame_game_test.dart +++ b/packages/flame/test/game/flame_game_test.dart @@ -698,6 +698,28 @@ void main() { }); }); }); + + group('pauseWhenBackgrounded:', () { + testWithFlameGame('true', (game) async { + game.pauseWhenBackgrounded = true; + + game.lifecycleStateChange(AppLifecycleState.paused); + expect(game.paused, true); + + game.lifecycleStateChange(AppLifecycleState.resumed); + expect(game.paused, false); + }); + + testWithFlameGame('false', (game) async { + game.pauseWhenBackgrounded = false; + + game.lifecycleStateChange(AppLifecycleState.paused); + expect(game.paused, false); + + game.lifecycleStateChange(AppLifecycleState.resumed); + expect(game.paused, false); + }); + }); }); }