Is it possible to make an overlay in Flame for Flutter made of Flutter widgets, and have some transparent widgets, where a game could be seen through the overlay?
I have found information that transparent widgets can be done (How to create a transparent container in flutter), but I am not sure if they can make a Flame overlay transparent.
Question update:
Here is an minimum example of what I am trying to achieve.
import 'dart:async';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Flame.device.fullScreen();
await Flame.device.setPortrait();
runApp(MainApp());
}
class MainApp extends StatelessWidget {
MainApp({Key? key}) : super(key: key);
final game = MainGame();
@override
Widget build(BuildContext context) {
return MaterialApp(
darkTheme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.deepPurple,
),
home: GameWidget(
game: game,
overlayBuilderMap: {
'MainMenu': (context, MainGame game) => MainMenu(game: game),
},
),
);
}
}
class MainGame extends FlameGame with MultiTouchTapDetector {
@override
void render(Canvas canvas) {
super.render(canvas);
Paint paint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
canvas.drawRect(Rect.fromPoints(const Offset(10.0, 10.0), const Offset(30.0, 30.0)), paint);
}
@override
void onTapDown(int pointerId, TapDownInfo info) {
if (!overlays.isActive('MainMenu')) {
overlays.add('MainMenu');
}
else {
overlays.remove('MainMenu');
}
super.onTapDown(pointerId, info);
}
}
class MainMenu extends StatefulWidget {
const MainMenu({Key? key, required this.game}) : super(key: key);
/// The reference to the game.
final MainGame game;
@override
_MainMenuState createState() => _MainMenuState();
}
class _MainMenuState extends State<MainMenu> with TickerProviderStateMixin {
static Duration duration = const Duration(milliseconds: 250);
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: duration)
..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.dismissed) {
widget.game.overlays.remove('MainMenu');
}
});
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void deactivate() {
//_controller.reverse(from: 1.0);
super.deactivate();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, _) {
double animationVal = _controller.value;
double translateVal = (animationVal - 1.0) * 320.0;
return Transform.translate(
offset: Offset(translateVal, 0.0),
child: Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
decoration: const BoxDecoration(
color: Colors.deepPurple,
),
child: Column(
children: const <Widget>[
Text('MyApp Menu'),
],
),
),
ListTile(
title: const Text('Item 1'),
onTap: () => _controller.reverse(),
),
ListTile(
title: const Text('Item 2'),
onTap: () => _controller.reverse(),
),
]
),
),
);
},
);
}
}
In the example drawer menu is sliding into the screen. Press on "Item 1" closes the menu as it should. Pressing somewhere else in the black area of the game closes the drawer menu by just hiding it. I see two options here:
- Animate the menu before hiding it, but I do not know how to reference the
MainMenu
class fromMainGame
class. In that case I would not calloverlays.remove
, but_controller.reverse()
which ultimately callsoverlays.remove
. - Make a transparent widget as a part of
MainMenu
that occupies the rest of the screen and catchonTap
event to call_controller.reverse()
.
Then I have two subquestions:
- There is a value 320 in the source for Drawer widget. Can I get the Drawer widget
width
in order to put the correct value there. I do not want to set thewidth
of Drawer. I want it to be the defaultwidth
. - Can I put an image or icon on the upper outer right edge of the drawer, that would look like a tab? When Drawer is out it would be "<<". When Drawer is hidden there would be an image in the game that would look like that part of the Drawer is visible to activate it ">>". I know how to put an image in the corner of the game. I do not know how to attach it to a Drawer.
Another update:
I changed the example according to @spydon's answer. Now the whole new set of problems appeared. Here is the changed source:
import 'dart:async';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
final GlobalKey<ScaffoldState> _key = GlobalKey();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Flame.device.fullScreen();
await Flame.device.setPortrait();
runApp(const MainApp());
}
class MainApp extends StatefulWidget {
const MainApp({Key? key}) : super(key: key);
@override
State<MainApp> createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
final game = MainGame();
@override
Widget build(BuildContext context) {
return MaterialApp(
darkTheme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.deepPurple,
),
home: Scaffold(
key: _key,
drawer: Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
decoration: const BoxDecoration(
color: Colors.deepPurple,
),
child: Column(
children: const <Widget>[
Text('MyApp Menu'),
],
),
),
ListTile(
title: const Text('Item 1'),
onTap: () { Navigator.of(context).pop(); },
),
ListTile(
title: const Text('Item 2'),
onTap: () { Navigator.of(context).pop(); },
),
]
),
),
body: GameWidget(game: game),
),
);
}
}
class MainGame extends FlameGame with MultiTouchTapDetector {
@override
void render(Canvas canvas) {
super.render(canvas);
Paint paint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
canvas.drawRect(Rect.fromPoints(const Offset(10.0, 10.0), const Offset(30.0, 30.0)), paint);
}
@override
void onTapDown(int pointerId, TapDownInfo info) {
_key.currentState!.openDrawer();
super.onTapDown(pointerId, info);
}
}