Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Adding ComponentNotifier API (#1889)
This adds a proposal for a new API for flame, the ComponentNotifier. This API offers the user change notifiers classes that are tied to FlameGame and its components so the user can be notified when a component is added, removed or updated. This will enable users to: Take the benefit of reactive programming inside the game Have a simple way of watching certain states from the game, on Flutter Widgets One important note here is that this proposal does not mean to replace integrations like flame_bloc, but rather provider an simple and out of the box solution, without any need of additional packages, since change notifiers are provided by flutter itself. Opening this as draft for now to get feedback on the implementation, will write tests and docs once we have the final implementation.
- Loading branch information
1 parent
51a896b
commit bd7f51f
Showing
13 changed files
with
671 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
examples/lib/stories/components/components_notifier_example.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import 'package:flame/components.dart'; | ||
import 'package:flame/game.dart'; | ||
import 'package:flame/widgets.dart'; | ||
import 'package:flutter/material.dart'; | ||
|
||
class ComponentsNotifierExampleWidget extends StatefulWidget { | ||
const ComponentsNotifierExampleWidget({super.key}); | ||
|
||
static String description = ''' | ||
Showcases how the components notifier can be used between | ||
a flame game instance and widgets. | ||
Tap the red dots to defeat the enemies and see the hud being updated | ||
to reflect the current state of the game. | ||
'''; | ||
|
||
@override | ||
State<ComponentsNotifierExampleWidget> createState() => | ||
_ComponentsNotifierExampleWidgetState(); | ||
} | ||
|
||
class _ComponentsNotifierExampleWidgetState | ||
extends State<ComponentsNotifierExampleWidget> { | ||
@override | ||
void initState() { | ||
super.initState(); | ||
|
||
game = ComponentNotifierExample(); | ||
} | ||
|
||
late final ComponentNotifierExample game; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Scaffold( | ||
body: Stack( | ||
children: [ | ||
Positioned.fill( | ||
child: GameWidget(game: game), | ||
), | ||
Positioned( | ||
left: 16, | ||
top: 16, | ||
child: ComponentsNotifierBuilder<Enemy>( | ||
notifier: game.componentsNotifier<Enemy>(), | ||
builder: (context, notifier) { | ||
return GameHud( | ||
remainingEnemies: notifier.components.length, | ||
onReplay: game.replay, | ||
); | ||
}, | ||
), | ||
), | ||
], | ||
), | ||
); | ||
} | ||
} | ||
|
||
class GameHud extends StatelessWidget { | ||
const GameHud({ | ||
super.key, | ||
required this.remainingEnemies, | ||
required this.onReplay, | ||
}); | ||
|
||
final int remainingEnemies; | ||
final VoidCallback onReplay; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Card( | ||
child: Padding( | ||
padding: const EdgeInsets.all(16), | ||
child: remainingEnemies == 0 | ||
? ElevatedButton( | ||
onPressed: onReplay, | ||
child: const Text('Play again'), | ||
) | ||
: Text('Remaining enemies: $remainingEnemies'), | ||
), | ||
); | ||
} | ||
} | ||
|
||
class Enemy extends CircleComponent with Tappable, Notifier { | ||
Enemy({super.position}) | ||
: super( | ||
radius: 20, | ||
paint: Paint()..color = const Color(0xFFFF0000), | ||
); | ||
|
||
@override | ||
bool onTapUp(_) { | ||
removeFromParent(); | ||
return true; | ||
} | ||
} | ||
|
||
class ComponentNotifierExample extends FlameGame with HasTappables { | ||
@override | ||
Future<void> onLoad() async { | ||
replay(); | ||
} | ||
|
||
void replay() { | ||
add(Enemy(position: Vector2(100, 100))); | ||
add(Enemy(position: Vector2(200, 100))); | ||
add(Enemy(position: Vector2(300, 100))); | ||
} | ||
} |
105 changes: 105 additions & 0 deletions
105
examples/lib/stories/components/components_notifier_provider_example.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import 'package:flame/components.dart'; | ||
import 'package:flame/game.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:provider/provider.dart'; | ||
|
||
class ComponentsNotifierProviderExampleWidget extends StatefulWidget { | ||
const ComponentsNotifierProviderExampleWidget({super.key}); | ||
|
||
static String description = ''' | ||
Similar to the Components Notifier example, but uses provider | ||
instead of the built in ComponentsNotifierBuilder widget. | ||
'''; | ||
|
||
@override | ||
State<ComponentsNotifierProviderExampleWidget> createState() => | ||
_ComponentsNotifierProviderExampleWidgetState(); | ||
} | ||
|
||
class _ComponentsNotifierProviderExampleWidgetState | ||
extends State<ComponentsNotifierProviderExampleWidget> { | ||
@override | ||
void initState() { | ||
super.initState(); | ||
|
||
game = ComponentNotifierExample(); | ||
} | ||
|
||
late final ComponentNotifierExample game; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Scaffold( | ||
body: MultiProvider( | ||
providers: [ | ||
Provider<ComponentNotifierExample>.value(value: game), | ||
ChangeNotifierProvider<ComponentsNotifier<Enemy>>( | ||
create: (_) => game.componentsNotifier<Enemy>(), | ||
), | ||
], | ||
child: Stack( | ||
children: [ | ||
Positioned.fill( | ||
child: GameWidget(game: game), | ||
), | ||
const Positioned( | ||
left: 16, | ||
top: 16, | ||
child: GameHud(), | ||
), | ||
], | ||
), | ||
), | ||
); | ||
} | ||
} | ||
|
||
class GameHud extends StatelessWidget { | ||
const GameHud({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final enemies = context.watch<ComponentsNotifier<Enemy>>().components; | ||
|
||
return Card( | ||
child: Padding( | ||
padding: const EdgeInsets.all(16), | ||
child: enemies.isEmpty | ||
? ElevatedButton( | ||
child: const Text('Play again'), | ||
onPressed: () { | ||
context.read<ComponentNotifierExample>().replay(); | ||
}, | ||
) | ||
: Text('Remaining enemies: ${enemies.length}'), | ||
), | ||
); | ||
} | ||
} | ||
|
||
class Enemy extends CircleComponent with Tappable, Notifier { | ||
Enemy({super.position}) | ||
: super( | ||
radius: 20, | ||
paint: Paint()..color = const Color(0xFFFF0000), | ||
); | ||
|
||
@override | ||
bool onTapUp(_) { | ||
removeFromParent(); | ||
return true; | ||
} | ||
} | ||
|
||
class ComponentNotifierExample extends FlameGame with HasTappables { | ||
@override | ||
Future<void> onLoad() async { | ||
replay(); | ||
} | ||
|
||
void replay() { | ||
add(Enemy(position: Vector2(100, 100))); | ||
add(Enemy(position: Vector2(200, 100))); | ||
add(Enemy(position: Vector2(300, 100))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.