mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 19:12:31 +08:00
game widget (#533)
This commit is contained in:
@ -9,6 +9,8 @@
|
||||
- Translate README to Russian
|
||||
- Split up Component and PositionComponent to BaseComponent
|
||||
- Unify multiple render methods on Sprite
|
||||
- Refactored how games are inserted into a flutter tree
|
||||
- Refactored the widgets overlay API
|
||||
|
||||
## 1.0.0-rc2
|
||||
- Improve IsometricTileMap and Spritesheet classes
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/gestures.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flame/flame.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/extensions/vector2.dart';
|
||||
import 'package:flame/sprite_animation.dart';
|
||||
import 'package:flame/components/sprite_animation_component.dart';
|
||||
@ -13,7 +13,11 @@ void main() async {
|
||||
|
||||
final Vector2 size = await Flame.util.initialDimensions();
|
||||
final game = MyGame(size);
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: game,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends BaseGame with TapDetector {
|
||||
|
||||
@ -8,7 +8,11 @@ import 'package:flutter/material.dart';
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final Vector2 size = await Flame.util.initialDimensions();
|
||||
runApp(MyGame(size).widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(size),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends BaseGame {
|
||||
|
||||
@ -13,8 +13,11 @@ void main() async {
|
||||
Flame.initializeWidget();
|
||||
await Flame.util.initialDimensions();
|
||||
|
||||
final myGame = MyGame();
|
||||
runApp(myGame.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class AndroidComponent extends SpriteComponent with Resizable {
|
||||
|
||||
@ -2,11 +2,11 @@ import 'package:flame/effects/combined_effect.dart';
|
||||
import 'package:flame/effects/move_effect.dart';
|
||||
import 'package:flame/effects/scale_effect.dart';
|
||||
import 'package:flame/effects/rotate_effect.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/gestures.dart';
|
||||
import 'package:flame/extensions/offset.dart';
|
||||
import 'package:flame/extensions/vector2.dart';
|
||||
import 'package:flame/flame.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import './square.dart';
|
||||
@ -14,7 +14,11 @@ import './square.dart';
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Flame.util.fullScreen();
|
||||
runApp(MyGame().widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends BaseGame with TapDetector {
|
||||
|
||||
@ -14,7 +14,11 @@ import './square.dart';
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Flame.util.fullScreen();
|
||||
runApp(MyGame().widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends BaseGame with TapDetector {
|
||||
|
||||
@ -15,7 +15,11 @@ import './square.dart';
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Flame.util.fullScreen();
|
||||
runApp(MyGame().widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends BaseGame with TapDetector {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flame/extensions/vector2.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/gestures.dart';
|
||||
import 'package:flame/effects/effects.dart';
|
||||
import 'package:flame/extensions/offset.dart';
|
||||
@ -9,6 +9,7 @@ import './square.dart';
|
||||
|
||||
class MyGame extends BaseGame with TapDetector {
|
||||
Square square;
|
||||
|
||||
MyGame() {
|
||||
add(square = Square()
|
||||
..x = 100
|
||||
@ -36,5 +37,9 @@ class MyGame extends BaseGame with TapDetector {
|
||||
}
|
||||
|
||||
void main() {
|
||||
runApp(MyGame().widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flame/gestures.dart';
|
||||
import 'package:flame/anchor.dart';
|
||||
import 'package:flame/effects/effects.dart';
|
||||
@ -30,5 +30,9 @@ class MyGame extends BaseGame with TapDetector {
|
||||
}
|
||||
|
||||
void main() {
|
||||
runApp(MyGame().widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -32,5 +32,9 @@ class MyGame extends BaseGame with TapDetector {
|
||||
}
|
||||
|
||||
void main() {
|
||||
runApp(MyGame().widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flame/gestures.dart';
|
||||
import 'package:flame/palette.dart';
|
||||
|
||||
void main() {
|
||||
final game = MyGame();
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Includes an example including basic detectors
|
||||
@ -16,6 +19,9 @@ class MyGame extends Game
|
||||
final _greenPaint = Paint()..color = const Color(0xFF00FF00);
|
||||
final _redPaint = Paint()..color = const Color(0xFFFF0000);
|
||||
|
||||
@override
|
||||
Color backgroundColor() => const Color(0xFFF1F1F1);
|
||||
|
||||
Paint _paint;
|
||||
|
||||
Rect _rect = const Rect.fromLTWH(50, 50, 50, 50);
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flame/gestures.dart';
|
||||
import 'package:flame/palette.dart';
|
||||
import 'package:flame/extensions/vector2.dart';
|
||||
import 'package:flame/extensions/offset.dart';
|
||||
|
||||
void main() {
|
||||
final game = MyGame();
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends Game with MouseMovementDetector {
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flame/gestures.dart';
|
||||
import 'package:flame/palette.dart';
|
||||
|
||||
void main() {
|
||||
final game = MyGame();
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Includes an example including advanced detectors
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flame/gestures.dart';
|
||||
import 'package:flame/palette.dart';
|
||||
|
||||
void main() {
|
||||
final game = MyGame();
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Includes an example mixing two advanced detectors
|
||||
|
||||
@ -13,7 +13,9 @@ void main() {
|
||||
final widget = Container(
|
||||
padding: const EdgeInsets.all(50),
|
||||
color: const Color(0xFFA9A9A9),
|
||||
child: game.widget,
|
||||
child: GameWidget(
|
||||
game: game,
|
||||
),
|
||||
);
|
||||
|
||||
runApp(widget);
|
||||
|
||||
@ -7,7 +7,11 @@ import 'package:flame/extensions/offset.dart';
|
||||
|
||||
void main() {
|
||||
final game = MyGame();
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: game,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends Game with ScrollDetector {
|
||||
|
||||
@ -6,15 +6,15 @@ import 'package:flame/components/position_component.dart';
|
||||
import 'package:flame/components/mixins/tapable.dart';
|
||||
|
||||
void main() {
|
||||
final game = MyGame();
|
||||
|
||||
final widget = Container(
|
||||
runApp(
|
||||
Container(
|
||||
padding: const EdgeInsets.all(50),
|
||||
color: const Color(0xFFA9A9A9),
|
||||
child: game.widget,
|
||||
child: GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
runApp(widget);
|
||||
}
|
||||
|
||||
class TapableSquare extends PositionComponent with Tapable {
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import './game.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(MyGame().widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/foundation.dart'
|
||||
show debugDefaultTargetPlatformOverride;
|
||||
import 'package:flutter/material.dart';
|
||||
@ -7,5 +8,9 @@ import './game.dart';
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
|
||||
runApp(MyGame().widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -17,8 +17,11 @@ final topLeft = Vector2(x, y);
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final game = MyGame();
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class Selector extends SpriteComponent {
|
||||
|
||||
@ -8,8 +8,11 @@ import 'package:flutter/material.dart';
|
||||
import 'player.dart';
|
||||
|
||||
void main() {
|
||||
final game = MyGame();
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends BaseGame with MultiTouchDragDetector {
|
||||
|
||||
@ -5,7 +5,11 @@ import 'package:flutter/services.dart';
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
void main() => runApp(MyGame().widget);
|
||||
void main() => runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
|
||||
class MyGame extends Game with KeyboardEvents {
|
||||
static final Paint _white = Paint()..color = const Color(0xFFFFFFFF);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import 'package:flame/extensions/vector2.dart';
|
||||
import 'package:flutter/material.dart' hide Animation;
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart' hide Animation;
|
||||
import 'package:flame/sprite.dart';
|
||||
import 'package:flame/layer/layer.dart';
|
||||
import 'package:flame/flame.dart';
|
||||
@ -12,7 +12,11 @@ void main() async {
|
||||
|
||||
await Flame.util.fullScreen();
|
||||
|
||||
runApp(LayerGame().widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: LayerGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class GameLayer extends DynamicLayer {
|
||||
|
||||
@ -11,7 +11,11 @@ void main() async {
|
||||
final size = await Flame.util.initialDimensions();
|
||||
|
||||
final game = MyGame(size);
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: game,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends Game {
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
import 'package:flame/flame.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/components/parallax_component.dart';
|
||||
import 'package:flame/extensions/vector2.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Flame.util.fullScreen();
|
||||
runApp(MyGame().widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends BaseGame {
|
||||
|
||||
@ -25,9 +25,16 @@ import 'package:flame/extensions/vector2.dart';
|
||||
import 'package:flame/sprite.dart';
|
||||
import 'package:flame/spritesheet.dart';
|
||||
import 'package:flame/text_config.dart';
|
||||
import 'package:flutter/material.dart' hide Animation, Image;
|
||||
import 'package:flutter/material.dart' hide Image;
|
||||
|
||||
void main() async => runApp((await loadGame()).widget);
|
||||
void main() async {
|
||||
final game = await loadGame();
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: game,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends BaseGame {
|
||||
/// Defines dimensions of the sample
|
||||
|
||||
@ -9,7 +9,11 @@ void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final Vector2 size = await Flame.util.initialDimensions();
|
||||
final game = MyGame(size);
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: game,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends BaseGame {
|
||||
|
||||
@ -11,7 +11,11 @@ void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final Vector2 size = await Flame.util.initialDimensions();
|
||||
final game = MyGame(size);
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: game,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends BaseGame {
|
||||
|
||||
@ -10,7 +10,11 @@ void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final Vector2 size = await Flame.util.initialDimensions();
|
||||
final game = MyGame(size);
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: game,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends BaseGame {
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
import 'package:flame/extensions/vector2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flame/sprite.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flame/sprite.dart';
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
void main() {
|
||||
runApp(MyGame().widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends Game {
|
||||
|
||||
@ -8,7 +8,11 @@ import 'package:flutter/material.dart';
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
runApp(MyGame().widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyGame extends BaseGame {
|
||||
|
||||
@ -12,7 +12,11 @@ import 'package:flutter/material.dart';
|
||||
|
||||
void main() async {
|
||||
final Vector2 size = await Flame.util.initialDimensions();
|
||||
runApp(MyGame(size).widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(size),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TextConfig regular = TextConfig(color: BasicPalette.white.color);
|
||||
|
||||
@ -7,10 +7,10 @@ import 'package:flame/extensions/vector2.dart';
|
||||
import 'package:flame/components/timer_component.dart';
|
||||
|
||||
void main() {
|
||||
runApp(GameWidget());
|
||||
runApp(MyGameApp());
|
||||
}
|
||||
|
||||
class GameWidget extends StatelessWidget {
|
||||
class MyGameApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(routes: {
|
||||
@ -26,8 +26,8 @@ class GameWidget extends StatelessWidget {
|
||||
Navigator.of(context).pushNamed('/base_game');
|
||||
})
|
||||
]),
|
||||
'/game': (BuildContext context) => MyGame().widget,
|
||||
'/base_game': (BuildContext context) => MyBaseGame().widget
|
||||
'/game': (BuildContext context) => GameWidget(game: MyGame()),
|
||||
'/base_game': (BuildContext context) => GameWidget(game: MyBaseGame())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import 'package:flame/palette.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ExampleGame extends Game with HasWidgetsOverlay, TapDetector {
|
||||
class ExampleGame extends Game with TapDetector {
|
||||
bool isPaused = false;
|
||||
|
||||
@override
|
||||
@ -12,26 +12,20 @@ class ExampleGame extends Game with HasWidgetsOverlay, TapDetector {
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
canvas.drawRect(const Rect.fromLTWH(100, 100, 100, 100),
|
||||
Paint()..color = BasicPalette.white.color);
|
||||
canvas.drawRect(
|
||||
const Rect.fromLTWH(100, 100, 100, 100),
|
||||
Paint()..color = BasicPalette.white.color,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTap() {
|
||||
if (isPaused) {
|
||||
removeWidgetOverlay('PauseMenu');
|
||||
overlays.remove('PauseMenu');
|
||||
isPaused = false;
|
||||
} else {
|
||||
addWidgetOverlay(
|
||||
overlays.add(
|
||||
'PauseMenu',
|
||||
Center(
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
color: const Color(0xFFFF0000),
|
||||
child: const Center(child: const Text('Paused')),
|
||||
),
|
||||
),
|
||||
);
|
||||
isPaused = true;
|
||||
}
|
||||
|
||||
@ -1,7 +1,69 @@
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import './example_game.dart';
|
||||
|
||||
void main() {
|
||||
runApp(ExampleGame().widget);
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: MyHomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
MyHomePage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_MyHomePageState createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
ExampleGame _myGame;
|
||||
|
||||
Widget pauseMenuBuilder(BuildContext buildContext) {
|
||||
return Center(
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
color: const Color(0xFFFF0000),
|
||||
child: const Center(
|
||||
child: const Text('Paused'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Testing addingOverlay'),
|
||||
),
|
||||
body: _myGame == null
|
||||
? const Text('Wait')
|
||||
: GameWidget<ExampleGame>(
|
||||
game: _myGame,
|
||||
overlayBuilderMap: {
|
||||
"PauseMenu": pauseMenuBuilder,
|
||||
},
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => newGame(),
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void newGame() {
|
||||
setState(() {
|
||||
_myGame = ExampleGame();
|
||||
print('New game created');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import './example_game.dart';
|
||||
|
||||
void main() {
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: MyHomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
MyHomePage({Key key}) : super(key: key);
|
||||
@override
|
||||
_MyHomePageState createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
ExampleGame _myGame;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Testing addingOverlay'),
|
||||
),
|
||||
body: _myGame == null ? const Text('Wait') : _myGame.widget,
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => newGame(),
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void newGame() {
|
||||
print('New game created');
|
||||
_myGame = ExampleGame();
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
75
doc/game.md
75
doc/game.md
@ -5,24 +5,43 @@ The Game Loop module is a simple abstraction over the game loop concept. Basical
|
||||
* The render method takes the canvas ready for drawing the current state of the game.
|
||||
* The update method receives the delta time in seconds since last update and allows you to move to the next state.
|
||||
|
||||
The class `Game` can be subclassed and will provide both these methods for you to implement. In return it will provide you with a `widget` property that returns the game widget, that can be rendered in your app.
|
||||
The `Game` class can be extended and will provide these gameloop methods and then its instance Flutter widget tree via the `GameWidget`.
|
||||
|
||||
You can either render it directly in your `runApp`, or you can have a bigger structure, with routing, other screens and menus for your game.
|
||||
You can add it into the top of you tree (directly as an argument to `runApp`) or inside the usual app-like widget structure (with scaffold, routes, etc.).
|
||||
|
||||
To start, just add your game widget directly to your runApp, like this:
|
||||
Example of usage directly with `runApp`:
|
||||
|
||||
```dart
|
||||
|
||||
class MyGameSubClass extends Game {
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
// TODO: implement render
|
||||
}
|
||||
|
||||
@override
|
||||
void update(double t) {
|
||||
// TODO: implement update
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
main() {
|
||||
Game game = MyGameImpl();
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGameSubClass(),
|
||||
)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Instead of implementing the low level `Game` class, you should probably use the more full-featured `BaseGame` class, or the `Forge2DGame` class if you want to use a physics engine.
|
||||
It is important to notice that `Game` is an abstract class with just the very basic implementations of the gameloop.
|
||||
|
||||
As an option and more suitable for most cases, there is the full-featured `BaseGame` class. For example, Forge2D games uses `Forge2DGame` class;
|
||||
|
||||
The `BaseGame` implements a `Component` based `Game` for you; basically it has a list of `Component`s and passes the `update` and `render` calls appropriately. You can still extend those methods to add custom behavior, and you will get a few other features for free, like the passing of `resize` methods (every time the screen is resized the information will be passed to the resize methods of all your components) and also a basic camera feature. The `BaseGame.camera` controls which point in coordinate space should be the top-left of the screen (defaults to [0,0] like a regular `Canvas`).
|
||||
|
||||
A very simple `BaseGame` implementation example can be seen below:
|
||||
A `BaseGame` implementation example can be seen below:
|
||||
|
||||
```dart
|
||||
class MyCrate extends SpriteComponent {
|
||||
@ -49,27 +68,39 @@ To remove components from the list on a `BaseGame` the `markToRemove` method can
|
||||
|
||||
## Flutter Widgets and Game instances
|
||||
|
||||
Since a Flame game is a widget itself, it is quite easy to use Flutter widgets and Flame game together. But to make it even easier, Flame provides a `mixin` called `HasWidgetsOverlay` which will enable any Flutter widget to be show on top of your game instance, this makes it very easy to create things like a pause menu, or an inventory screen for example.
|
||||
Since a Flame game can be wrapped in a widget, it is quite easy to use it alongside other Flutter widgets. But still, there is a the Widgets overlay API that makes things even easier.
|
||||
|
||||
To use it, simple add the `HasWidgetsOverlay` `mixin` on your game class, by doing so, you will have two new methods available `addWidgetOverlay` and `removeWidgetOverlay`, like the name suggests, they can be used to add or remove widgets overlay above your game. They can be used as shown below:
|
||||
`Game.overlays` enables to any Flutter widget to be shown on top of a game instance, this makes it very easy to create things like a pause menu, or an inventory screen for example.
|
||||
This property that will be used to manage the active overlays.
|
||||
|
||||
This management happens via the `.overlays.add` and `.overlays.remove` methods that marks an overlay to be shown and hidden, respectively, via a `String` argument that identifies an overlay.
|
||||
After that it can be specified which widgets represent each overlay in the `GameWidget` declaration by setting a `overlayBuilderMap`.
|
||||
|
||||
```dart
|
||||
addWidgetOverlay(
|
||||
"PauseMenu", // Your overlay identifier
|
||||
Center(child:
|
||||
Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
color: const Color(0xFFFF0000),
|
||||
child: const Center(child: const Text("Paused")),
|
||||
),
|
||||
) // Your widget, this can be any Flutter widget
|
||||
);
|
||||
// inside game methods:
|
||||
final pauseOverlayIdentifier = "PauseMenu";
|
||||
|
||||
removeWidgetOverlay("PauseMenu"); // Use the overlay identifier to remove the overlay
|
||||
overlays.add(pauseOverlayIdentifier); // marks "PauseMenu" to be rendered.
|
||||
overlays.remove(pauseOverlayIdentifier); // marks "PauseMenu" to not be rendered.
|
||||
```
|
||||
|
||||
Under the hood, Flame uses a [Stack widget](https://api.flutter.dev/flutter/widgets/Stack-class.html) to display the overlay, so it is important to __note that the order which the overlays are added matters__, where the last added overlay, will be in the front of those added before.
|
||||
```dart
|
||||
// on the widget declaration
|
||||
final game = MyGame();
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return GameWidget(
|
||||
game: game,
|
||||
overlayBuilderMap: {
|
||||
"PauseMenu": (ctx) {
|
||||
return Text("A pause menu");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The order in which the overlays are declared on the `overlayBuilderMap` defines which overlay will be rendered first.
|
||||
|
||||
Here you can see a [working example](/doc/examples/with_widgets_overlay) of this feature.
|
||||
|
||||
|
||||
@ -11,9 +11,11 @@ import 'package:flame/palette.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
final game = MyGame();
|
||||
|
||||
runApp(game.widget);
|
||||
runApp(
|
||||
GameWidget(
|
||||
game: MyGame(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class Palette {
|
||||
|
||||
@ -14,6 +14,7 @@ import 'util.dart';
|
||||
class Flame {
|
||||
// Flame asset bundle, defaults to root
|
||||
static AssetBundle _bundle;
|
||||
|
||||
static AssetBundle get bundle => _bundle == null ? rootBundle : _bundle;
|
||||
|
||||
/// Access a shared instance of [AssetsCache] class.
|
||||
@ -25,10 +26,11 @@ class Flame {
|
||||
/// Access a shared instance of the [Util] class.
|
||||
static Util util = Util();
|
||||
|
||||
static Future<void> init(
|
||||
{AssetBundle bundle,
|
||||
static Future<void> init({
|
||||
AssetBundle bundle,
|
||||
bool fullScreen = true,
|
||||
DeviceOrientation orientation}) async {
|
||||
DeviceOrientation orientation,
|
||||
}) async {
|
||||
initializeWidget();
|
||||
|
||||
if (fullScreen) {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// Keeping compatible with earlier versions of Flame
|
||||
export './game/base_game.dart';
|
||||
export './game/game.dart';
|
||||
export './game/game_widget.dart';
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart' hide WidgetBuilder;
|
||||
|
||||
import 'game.dart';
|
||||
import 'game_render_box.dart';
|
||||
|
||||
/// This a widget to embed a game inside the Widget tree. You can use it in pair with [BaseGame] or any other more complex [Game], as desired.
|
||||
///
|
||||
/// It handles for you positioning, size constraints and other factors that arise when your game is embedded within the component tree.
|
||||
/// Provided it with a [Game] instance for your game and the optional size of the widget.
|
||||
/// Creating this without a fixed size might mess up how other components are rendered with relation to this one in the tree.
|
||||
/// You can bind Gesture Recognizers immediately around this to add controls to your widgets, with easy coordinate conversions.
|
||||
class EmbeddedGameWidget extends LeafRenderObjectWidget {
|
||||
final Game game;
|
||||
|
||||
EmbeddedGameWidget(this.game);
|
||||
|
||||
@override
|
||||
RenderBox createRenderObject(BuildContext context) {
|
||||
return RenderConstrainedBox(
|
||||
child: GameRenderBox(context, game),
|
||||
additionalConstraints: const BoxConstraints.expand(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
RenderConstrainedBox renderBox,
|
||||
) {
|
||||
renderBox
|
||||
..child = GameRenderBox(context, game)
|
||||
..additionalConstraints = const BoxConstraints.expand();
|
||||
}
|
||||
}
|
||||
@ -5,24 +5,23 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart' hide WidgetBuilder;
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../assets/assets_cache.dart';
|
||||
import '../assets/images.dart';
|
||||
import '../extensions/vector2.dart';
|
||||
import '../keyboard.dart';
|
||||
import 'widget_builder.dart';
|
||||
|
||||
/// Represents a generic game.
|
||||
///
|
||||
/// Subclass this to implement the [update] and [render] methods.
|
||||
/// Flame will deal with calling these methods properly when the game's widget is rendered.
|
||||
abstract class Game {
|
||||
// Widget Builder for this Game
|
||||
final builder = WidgetBuilder();
|
||||
|
||||
final images = Images();
|
||||
final assets = AssetsCache();
|
||||
BuildContext buildContext;
|
||||
|
||||
bool get isAttached => buildContext != null;
|
||||
|
||||
/// Returns the game background color.
|
||||
/// By default it will return a black color.
|
||||
@ -48,17 +47,27 @@ abstract class Game {
|
||||
/// Check [AppLifecycleState] for details about the events received.
|
||||
void lifecycleStateChange(AppLifecycleState state) {}
|
||||
|
||||
/// Use for caluclating the FPS.
|
||||
/// Use for calculating the FPS.
|
||||
void onTimingsCallback(List<FrameTiming> timings) {}
|
||||
|
||||
/// Returns the game widget. Put this in your structure to start rendering and updating the game.
|
||||
/// You can add it directly to the runApp method or inside your widget structure (if you use vanilla screens and widgets).
|
||||
Widget get widget => builder.build(this);
|
||||
|
||||
void _handleKeyEvent(RawKeyEvent e) {
|
||||
(this as KeyboardEvents).onKeyEvent(e);
|
||||
}
|
||||
|
||||
/// Marks game as not attached tto any widget tree.
|
||||
///
|
||||
/// Should be called manually.
|
||||
void attach(PipelineOwner owner, BuildContext context) {
|
||||
if (isAttached) {
|
||||
throw UnsupportedError("""
|
||||
Game attachment error:
|
||||
A game instance can only be attached to one widget at a time.
|
||||
""");
|
||||
}
|
||||
buildContext = context;
|
||||
onAttach();
|
||||
}
|
||||
|
||||
// Called when the Game widget is attached
|
||||
@mustCallSuper
|
||||
void onAttach() {
|
||||
@ -67,23 +76,23 @@ abstract class Game {
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks game as not attached tto any widget tree.
|
||||
///
|
||||
/// Should not be called manually.
|
||||
void detach() {
|
||||
buildContext = null;
|
||||
onDetach();
|
||||
}
|
||||
|
||||
// Called when the Game widget is detached
|
||||
@mustCallSuper
|
||||
void onDetach() {
|
||||
// Keeping this here, because if we leave this on HasWidgetsOverlay
|
||||
// and somebody overrides this and forgets to call the stream close
|
||||
// we can face some leaks.
|
||||
|
||||
// Also we only do this in release mode, otherwise when using hot reload
|
||||
// the controller would be closed and errors would happen
|
||||
if (this is HasWidgetsOverlay && kReleaseMode) {
|
||||
(this as HasWidgetsOverlay).widgetOverlayController.close();
|
||||
}
|
||||
|
||||
if (this is KeyboardEvents) {
|
||||
RawKeyboard.instance.removeListener(_handleKeyEvent);
|
||||
}
|
||||
|
||||
images.clearCache();
|
||||
}
|
||||
|
||||
@ -102,29 +111,57 @@ abstract class Game {
|
||||
/// Use this method to load the assets need for the game instance to run
|
||||
Future<void> onLoad() async {}
|
||||
|
||||
/// Returns the widget which will be show while the instance is loading
|
||||
Widget loadingWidget() => Container();
|
||||
/// A property that stores an [ActiveOverlaysNotifier]
|
||||
///
|
||||
/// This is useful to render widgets above a game, like a pause menu for example.
|
||||
/// Overlays visible or hidden via [overlays.add] or [overlays.remove], respectively.
|
||||
///
|
||||
/// Ex:
|
||||
/// ```
|
||||
/// final pauseOverlayIdentifier = "PauseMenu";
|
||||
/// overlays.add(pauseOverlayIdentifier); // marks "PauseMenu" to be rendered.
|
||||
/// overlays.remove(pauseOverlayIdentifier); // marks "PauseMenu" to not be rendered.
|
||||
/// ```
|
||||
///
|
||||
/// See also:
|
||||
/// - [new GameWidget]
|
||||
/// - [Game.overlays]
|
||||
final overlays = ActiveOverlaysNotifier();
|
||||
}
|
||||
|
||||
class OverlayWidget {
|
||||
final String name;
|
||||
final Widget widget;
|
||||
/// A [ChangeNotifier] used to control the visibility of overlays on a [Game] instance.
|
||||
///
|
||||
/// To learn more, see:
|
||||
/// - [Game.overlays]
|
||||
class ActiveOverlaysNotifier extends ChangeNotifier {
|
||||
final Set<String> _activeOverlays = {};
|
||||
|
||||
OverlayWidget(this.name, this.widget);
|
||||
/// Mark a, overlay to be rendered.
|
||||
///
|
||||
/// See also:
|
||||
/// - [new GameWidget]
|
||||
/// - [Game.overlays]
|
||||
bool add(String overlayName) {
|
||||
final setChanged = _activeOverlays.add(overlayName);
|
||||
if (setChanged) {
|
||||
notifyListeners();
|
||||
}
|
||||
return setChanged;
|
||||
}
|
||||
|
||||
mixin HasWidgetsOverlay on Game {
|
||||
@override
|
||||
final builder = OverlayWidgetBuilder();
|
||||
|
||||
final StreamController<OverlayWidget> widgetOverlayController =
|
||||
StreamController();
|
||||
|
||||
void addWidgetOverlay(String overlayName, Widget widget) {
|
||||
widgetOverlayController.sink.add(OverlayWidget(overlayName, widget));
|
||||
/// Mark a, overlay to not be rendered.
|
||||
///
|
||||
/// See also:
|
||||
/// - [new GameWidget]
|
||||
/// - [Game.overlays]
|
||||
bool remove(String overlayName) {
|
||||
final hasRemoved = _activeOverlays.remove(overlayName);
|
||||
if (hasRemoved) {
|
||||
notifyListeners();
|
||||
}
|
||||
return hasRemoved;
|
||||
}
|
||||
|
||||
void removeWidgetOverlay(String overlayName) {
|
||||
widgetOverlayController.sink.add(OverlayWidget(overlayName, null));
|
||||
}
|
||||
/// A [Set] of the active overlay names.
|
||||
Set<String> get value => _activeOverlays;
|
||||
}
|
||||
|
||||
@ -10,11 +10,11 @@ import 'game.dart';
|
||||
import 'game_loop.dart';
|
||||
|
||||
class GameRenderBox extends RenderBox with WidgetsBindingObserver {
|
||||
BuildContext context;
|
||||
BuildContext buildContext;
|
||||
Game game;
|
||||
GameLoop gameLoop;
|
||||
|
||||
GameRenderBox(this.context, this.game) {
|
||||
GameRenderBox(this.buildContext, this.game) {
|
||||
gameLoop = GameLoop(gameLoopCallback);
|
||||
WidgetsBinding.instance.addTimingsCallback(game.onTimingsCallback);
|
||||
}
|
||||
@ -31,7 +31,7 @@ class GameRenderBox extends RenderBox with WidgetsBindingObserver {
|
||||
@override
|
||||
void attach(PipelineOwner owner) {
|
||||
super.attach(owner);
|
||||
game.onAttach();
|
||||
game.attach(owner, buildContext);
|
||||
|
||||
game.pauseEngineFn = gameLoop.pause;
|
||||
game.resumeEngineFn = gameLoop.resume;
|
||||
@ -46,7 +46,7 @@ class GameRenderBox extends RenderBox with WidgetsBindingObserver {
|
||||
@override
|
||||
void detach() {
|
||||
super.detach();
|
||||
game.onDetach();
|
||||
game.detach();
|
||||
gameLoop.stop();
|
||||
_unbindLifecycleListener();
|
||||
}
|
||||
|
||||
@ -1,10 +1,229 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
|
||||
import '../components/mixins/tapable.dart';
|
||||
import '../gestures.dart';
|
||||
import 'embedded_game_widget.dart';
|
||||
import 'game.dart';
|
||||
import '../gestures.dart';
|
||||
import '../components/mixins/tapable.dart';
|
||||
import 'game_render_box.dart';
|
||||
|
||||
typedef GameLoadingWidgetBuilder = Widget Function(
|
||||
BuildContext,
|
||||
bool error,
|
||||
);
|
||||
|
||||
/// A [StatefulWidget] that is in charge of attaching a [Game] instance into the flutter tree
|
||||
///
|
||||
class GameWidget<T extends Game> extends StatefulWidget {
|
||||
/// The game instance in which this widget will render
|
||||
final T game;
|
||||
|
||||
/// The text direction to be used in text elements in a game.
|
||||
final TextDirection textDirection;
|
||||
|
||||
/// Builder to provide a widget tree to be built whilst the [Future] provided
|
||||
/// via [Game.onLoad] is not resolved.
|
||||
final GameLoadingWidgetBuilder loadingBuilder;
|
||||
|
||||
/// Builder to provide a widget tree to be built between the game elements and
|
||||
/// the background color provided via [Game.backgroundColor]
|
||||
final WidgetBuilder backgroundBuilder;
|
||||
|
||||
/// A map to show widgets overlay.
|
||||
///
|
||||
/// See also:
|
||||
/// - [new GameWidget]
|
||||
/// - [Game.overlays]
|
||||
final Map<String, WidgetBuilder> overlayBuilderMap;
|
||||
|
||||
/// Renders a [game] in a flutter widget tree.
|
||||
///
|
||||
/// Ex:
|
||||
/// ```
|
||||
/// ...
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return GameWidget(
|
||||
/// game: MyGameClass(),
|
||||
/// )
|
||||
/// }
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// It is also possible to render layers of widgets over the game surface with widget subtrees.
|
||||
///
|
||||
/// To do that a [overlayBuilderMap] should be provided. The visibility of
|
||||
/// these overlays are controlled by [Game.overlays] property
|
||||
///
|
||||
/// Ex:
|
||||
/// ```
|
||||
/// ...
|
||||
///
|
||||
/// final game = MyGame();
|
||||
///
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return GameWidget(
|
||||
/// game: game,
|
||||
/// overlayBuilderMap: {
|
||||
/// "PauseMenu": (ctx) {
|
||||
/// return Text("A pause menu");
|
||||
/// },
|
||||
/// },
|
||||
/// )
|
||||
/// }
|
||||
/// ...
|
||||
/// game.overlays.add("PauseMenu");
|
||||
/// ```
|
||||
const GameWidget({
|
||||
Key key,
|
||||
this.game,
|
||||
this.textDirection,
|
||||
this.loadingBuilder,
|
||||
this.backgroundBuilder,
|
||||
this.overlayBuilderMap,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Renders a [game] in a flutter widget tree alongside widgets overlays.
|
||||
///
|
||||
/// To use overlays, the game subclass has to be mixed with [HasWidgetsOverlay],
|
||||
|
||||
@override
|
||||
_GameWidgetState createState() => _GameWidgetState();
|
||||
}
|
||||
|
||||
class _GameWidgetState extends State<GameWidget> {
|
||||
Set<String> activeOverlays = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
addOverlaysListener(widget.game);
|
||||
loadingFuture = widget.game.onLoad();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant GameWidget<Game> oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.game != widget.game) {
|
||||
removeOverlaysListener(oldWidget.game);
|
||||
addOverlaysListener(widget.game);
|
||||
}
|
||||
loadingFuture = widget.game.onLoad();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
removeOverlaysListener(widget.game);
|
||||
}
|
||||
|
||||
// widget overlay stuff
|
||||
void addOverlaysListener(Game game) {
|
||||
widget.game.overlays.addListener(onChangeActiveOverlays);
|
||||
activeOverlays = widget.game.overlays.value;
|
||||
}
|
||||
|
||||
void removeOverlaysListener(Game game) {
|
||||
game.overlays.removeListener(onChangeActiveOverlays);
|
||||
}
|
||||
|
||||
void onChangeActiveOverlays() {
|
||||
widget.game.overlays.value.forEach((overlayKey) {
|
||||
assert(widget.overlayBuilderMap.containsKey(overlayKey),
|
||||
"A non mapped overlay has been added: $overlayKey");
|
||||
});
|
||||
setState(() {
|
||||
activeOverlays = widget.game.overlays.value;
|
||||
});
|
||||
}
|
||||
|
||||
// loading future
|
||||
Future<void> loadingFuture;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget internalGameWidget = _GameRenderObjectWidget(widget.game);
|
||||
|
||||
final hasBasicDetectors = _hasBasicGestureDetectors(widget.game);
|
||||
final hasAdvancedDetectors = _hasAdvancedGesturesDetectors(widget.game);
|
||||
|
||||
assert(
|
||||
!(hasBasicDetectors && hasAdvancedDetectors),
|
||||
"""
|
||||
WARNING: Both Advanced and Basic detectors detected.
|
||||
Advanced detectors will override basic detectors and the later will not receive events
|
||||
""",
|
||||
);
|
||||
|
||||
if (hasBasicDetectors) {
|
||||
internalGameWidget = _applyBasicGesturesDetectors(
|
||||
widget.game,
|
||||
internalGameWidget,
|
||||
);
|
||||
} else if (hasAdvancedDetectors) {
|
||||
internalGameWidget = _applyAdvancedGesturesDetectors(
|
||||
widget.game,
|
||||
internalGameWidget,
|
||||
);
|
||||
}
|
||||
|
||||
if (_hasMouseDetectors(widget.game)) {
|
||||
internalGameWidget = _applyMouseDetectors(
|
||||
widget.game,
|
||||
internalGameWidget,
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> stackedWidgets = [internalGameWidget];
|
||||
stackedWidgets = _addBackground(context, stackedWidgets);
|
||||
stackedWidgets = _addOverlays(context, stackedWidgets);
|
||||
return Directionality(
|
||||
textDirection: widget.textDirection ??
|
||||
Directionality.maybeOf(context) ??
|
||||
TextDirection.ltr,
|
||||
child: Container(
|
||||
color: widget.game.backgroundColor(),
|
||||
child: FutureBuilder(
|
||||
future: loadingFuture,
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
return Stack(children: stackedWidgets);
|
||||
}
|
||||
return widget.loadingBuilder != null
|
||||
? widget.loadingBuilder(context, snapshot.hasError)
|
||||
: Container();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _addBackground(BuildContext context, List<Widget> stackWidgets) {
|
||||
if (widget.backgroundBuilder == null) {
|
||||
return stackWidgets;
|
||||
}
|
||||
final backgroundContent = KeyedSubtree(
|
||||
key: ValueKey(widget.game),
|
||||
child: widget.backgroundBuilder(context),
|
||||
);
|
||||
stackWidgets.insert(0, backgroundContent);
|
||||
return stackWidgets;
|
||||
}
|
||||
|
||||
List<Widget> _addOverlays(BuildContext context, List<Widget> stackWidgets) {
|
||||
if (widget.overlayBuilderMap == null) {
|
||||
return stackWidgets;
|
||||
}
|
||||
final widgets = activeOverlays.map((String overlayKey) {
|
||||
final builder = widget.overlayBuilderMap[overlayKey];
|
||||
return KeyedSubtree(
|
||||
key: ValueKey(overlayKey),
|
||||
child: builder(context),
|
||||
);
|
||||
});
|
||||
stackWidgets.addAll(widgets);
|
||||
return stackWidgets;
|
||||
}
|
||||
}
|
||||
|
||||
bool _hasBasicGestureDetectors(Game game) =>
|
||||
game is TapDetector ||
|
||||
@ -25,76 +244,11 @@ bool _hasAdvancedGesturesDetectors(Game game) =>
|
||||
bool _hasMouseDetectors(Game game) =>
|
||||
game is MouseMovementDetector || game is ScrollDetector;
|
||||
|
||||
class _GenericTapEventHandler {
|
||||
void Function(int pointerId) onTap;
|
||||
void Function(int pointerId) onTapCancel;
|
||||
void Function(int pointerId, TapDownDetails details) onTapDown;
|
||||
void Function(int pointerId, TapUpDetails details) onTapUp;
|
||||
}
|
||||
|
||||
Widget _applyAdvancedGesturesDetectors(Game game, Widget child) {
|
||||
final Map<Type, GestureRecognizerFactory> gestures = {};
|
||||
|
||||
final List<_GenericTapEventHandler> _tapHandlers = [];
|
||||
|
||||
if (game is HasTapableComponents) {
|
||||
_tapHandlers.add(_GenericTapEventHandler()
|
||||
..onTapDown = game.onTapDown
|
||||
..onTapUp = game.onTapUp
|
||||
..onTapCancel = game.onTapCancel);
|
||||
}
|
||||
|
||||
if (game is MultiTouchTapDetector) {
|
||||
_tapHandlers.add(_GenericTapEventHandler()
|
||||
..onTapDown = game.onTapDown
|
||||
..onTapUp = game.onTapUp
|
||||
..onTapCancel = game.onTapCancel);
|
||||
}
|
||||
|
||||
if (_tapHandlers.isNotEmpty) {
|
||||
gestures[MultiTapGestureRecognizer] =
|
||||
GestureRecognizerFactoryWithHandlers<MultiTapGestureRecognizer>(
|
||||
() => MultiTapGestureRecognizer(),
|
||||
(MultiTapGestureRecognizer instance) {
|
||||
instance.onTapDown = (pointerId, d) =>
|
||||
_tapHandlers.forEach((h) => h.onTapDown?.call(pointerId, d));
|
||||
instance.onTapUp = (pointerId, d) =>
|
||||
_tapHandlers.forEach((h) => h.onTapUp?.call(pointerId, d));
|
||||
instance.onTapCancel = (pointerId) =>
|
||||
_tapHandlers.forEach((h) => h.onTapCancel?.call(pointerId));
|
||||
instance.onTap = (pointerId) =>
|
||||
_tapHandlers.forEach((h) => h.onTap?.call(pointerId));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (game is MultiTouchDragDetector) {
|
||||
gestures[ImmediateMultiDragGestureRecognizer] =
|
||||
GestureRecognizerFactoryWithHandlers<
|
||||
ImmediateMultiDragGestureRecognizer>(
|
||||
() => ImmediateMultiDragGestureRecognizer(),
|
||||
(ImmediateMultiDragGestureRecognizer instance) {
|
||||
instance
|
||||
..onStart = (Offset o) {
|
||||
final drag = DragEvent();
|
||||
drag.initialPosition = o;
|
||||
|
||||
game.onReceiveDrag(drag);
|
||||
|
||||
return drag;
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return RawGestureDetector(
|
||||
gestures: gestures,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _applyBasicGesturesDetectors(Game game, Widget child) {
|
||||
return GestureDetector(
|
||||
key: const ObjectKey("BasicGesturesDetector"),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
|
||||
// Taps
|
||||
onTap: game is TapDetector ? () => game.onTap() : null,
|
||||
onTapCancel: game is TapDetector ? () => game.onTapCancel() : null,
|
||||
@ -203,6 +357,67 @@ Widget _applyBasicGesturesDetectors(Game game, Widget child) {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _applyAdvancedGesturesDetectors(Game game, Widget child) {
|
||||
final Map<Type, GestureRecognizerFactory> gestures = {};
|
||||
|
||||
final List<_GenericTapEventHandler> _tapHandlers = [];
|
||||
|
||||
if (game is HasTapableComponents) {
|
||||
_tapHandlers.add(_GenericTapEventHandler()
|
||||
..onTapDown = game.onTapDown
|
||||
..onTapUp = game.onTapUp
|
||||
..onTapCancel = game.onTapCancel);
|
||||
}
|
||||
|
||||
if (game is MultiTouchTapDetector) {
|
||||
_tapHandlers.add(_GenericTapEventHandler()
|
||||
..onTapDown = game.onTapDown
|
||||
..onTapUp = game.onTapUp
|
||||
..onTapCancel = game.onTapCancel);
|
||||
}
|
||||
|
||||
if (_tapHandlers.isNotEmpty) {
|
||||
gestures[MultiTapGestureRecognizer] =
|
||||
GestureRecognizerFactoryWithHandlers<MultiTapGestureRecognizer>(
|
||||
() => MultiTapGestureRecognizer(),
|
||||
(MultiTapGestureRecognizer instance) {
|
||||
instance.onTapDown = (pointerId, d) =>
|
||||
_tapHandlers.forEach((h) => h.onTapDown?.call(pointerId, d));
|
||||
instance.onTapUp = (pointerId, d) =>
|
||||
_tapHandlers.forEach((h) => h.onTapUp?.call(pointerId, d));
|
||||
instance.onTapCancel = (pointerId) =>
|
||||
_tapHandlers.forEach((h) => h.onTapCancel?.call(pointerId));
|
||||
instance.onTap = (pointerId) =>
|
||||
_tapHandlers.forEach((h) => h.onTap?.call(pointerId));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (game is MultiTouchDragDetector) {
|
||||
gestures[ImmediateMultiDragGestureRecognizer] =
|
||||
GestureRecognizerFactoryWithHandlers<
|
||||
ImmediateMultiDragGestureRecognizer>(
|
||||
() => ImmediateMultiDragGestureRecognizer(),
|
||||
(ImmediateMultiDragGestureRecognizer instance) {
|
||||
instance
|
||||
..onStart = (Offset o) {
|
||||
final drag = DragEvent();
|
||||
drag.initialPosition = o;
|
||||
|
||||
game.onReceiveDrag(drag);
|
||||
|
||||
return drag;
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return RawGestureDetector(
|
||||
gestures: gestures,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _applyMouseDetectors(Game game, Widget child) {
|
||||
return MouseRegion(
|
||||
child: Listener(
|
||||
@ -216,93 +431,23 @@ Widget _applyMouseDetectors(Game game, Widget child) {
|
||||
);
|
||||
}
|
||||
|
||||
class WidgetBuilder {
|
||||
Widget build(Game game) {
|
||||
Widget widget = Container(
|
||||
color: game.backgroundColor(),
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: EmbeddedGameWidget(game),
|
||||
),
|
||||
);
|
||||
|
||||
final hasBasicDetectors = _hasBasicGestureDetectors(game);
|
||||
final hasAdvancedDetectors = _hasAdvancedGesturesDetectors(game);
|
||||
|
||||
assert(
|
||||
!(hasBasicDetectors && hasAdvancedDetectors),
|
||||
'WARNING: Both Advanced and Basic detectors detected. Advanced detectors will override basic detectors and the later will not receive events',
|
||||
);
|
||||
|
||||
if (hasBasicDetectors) {
|
||||
widget = _applyBasicGesturesDetectors(game, widget);
|
||||
} else if (hasAdvancedDetectors) {
|
||||
widget = _applyAdvancedGesturesDetectors(game, widget);
|
||||
class _GenericTapEventHandler {
|
||||
void Function(int pointerId) onTap;
|
||||
void Function(int pointerId) onTapCancel;
|
||||
void Function(int pointerId, TapDownDetails details) onTapDown;
|
||||
void Function(int pointerId, TapUpDetails details) onTapUp;
|
||||
}
|
||||
|
||||
if (_hasMouseDetectors(game)) {
|
||||
widget = _applyMouseDetectors(game, widget);
|
||||
}
|
||||
class _GameRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
final Game game;
|
||||
|
||||
return FutureBuilder(
|
||||
future: game.onLoad(),
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
return widget;
|
||||
}
|
||||
return game.loadingWidget();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OverlayGameWidget extends StatefulWidget {
|
||||
final Widget gameChild;
|
||||
final HasWidgetsOverlay game;
|
||||
|
||||
OverlayGameWidget({Key key, this.gameChild, this.game}) : super(key: key);
|
||||
_GameRenderObjectWidget(this.game);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _OverlayGameWidgetState();
|
||||
}
|
||||
|
||||
class _OverlayGameWidgetState extends State<OverlayGameWidget> {
|
||||
final Map<String, Widget> _overlays = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.game.widgetOverlayController.stream.listen((overlay) {
|
||||
setState(() {
|
||||
if (overlay.widget == null) {
|
||||
_overlays.remove(overlay.name);
|
||||
} else {
|
||||
_overlays[overlay.name] = overlay.widget;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Stack(children: [widget.gameChild, ..._overlays.values.toList()]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OverlayWidgetBuilder extends WidgetBuilder {
|
||||
OverlayWidgetBuilder();
|
||||
|
||||
@override
|
||||
Widget build(Game game) {
|
||||
final container = super.build(game);
|
||||
|
||||
return OverlayGameWidget(
|
||||
gameChild: container,
|
||||
game: game as HasWidgetsOverlay,
|
||||
key: UniqueKey(),
|
||||
RenderBox createRenderObject(BuildContext context) {
|
||||
return RenderConstrainedBox(
|
||||
child: GameRenderBox(context, game),
|
||||
additionalConstraints: const BoxConstraints.expand(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import 'package:flame/components/position_component.dart';
|
||||
import 'package:flame/components/mixins/has_game_ref.dart';
|
||||
import 'package:flame/components/mixins/resizable.dart';
|
||||
import 'package:flame/components/mixins/tapable.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/game/base_game.dart';
|
||||
import 'package:flame/extensions/vector2.dart';
|
||||
import 'package:flame/game/game_render_box.dart';
|
||||
@ -101,7 +102,7 @@ void main() {
|
||||
Builder(
|
||||
builder: (BuildContext context) {
|
||||
renderBox = GameRenderBox(context, game);
|
||||
return game.widget;
|
||||
return GameWidget(game: game);
|
||||
},
|
||||
),
|
||||
);
|
||||
@ -109,7 +110,9 @@ void main() {
|
||||
renderBox.gameLoopCallback(1.0);
|
||||
expect(component.isUpdateCalled, true);
|
||||
renderBox.paint(
|
||||
PaintingContext(ContainerLayer(), Rect.zero), Offset.zero);
|
||||
PaintingContext(ContainerLayer(), Rect.zero),
|
||||
Offset.zero,
|
||||
);
|
||||
expect(component.isRenderCalled, true);
|
||||
renderBox.detach();
|
||||
});
|
||||
|
||||
@ -4,13 +4,14 @@ import 'package:flame/anchor.dart';
|
||||
import 'package:flame/components/position_component.dart';
|
||||
import 'package:flame/effects/effects.dart';
|
||||
import 'package:flame/extensions/vector2.dart';
|
||||
import 'package:flame/game/base_game.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
final Random random = Random();
|
||||
|
||||
class Callback {
|
||||
bool isCalled = false;
|
||||
|
||||
void call() => isCalled = true;
|
||||
}
|
||||
|
||||
@ -32,7 +33,9 @@ void effectTest(
|
||||
game.add(component);
|
||||
component.addEffect(effect);
|
||||
final double duration = effect.iterationTime;
|
||||
await tester.pumpWidget(game.widget);
|
||||
await tester.pumpWidget(GameWidget(
|
||||
game: game,
|
||||
));
|
||||
double timeLeft = iterations * duration;
|
||||
while (timeLeft > 0) {
|
||||
double stepDelta = 50.0 + random.nextInt(50);
|
||||
|
||||
Reference in New Issue
Block a user