Files
wolf_dart/apps/wolf_3d_gui/lib/screens/game_screen.dart

186 lines
5.6 KiB
Dart

/// Active gameplay screen for the Flutter host.
library;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
import 'package:wolf_3d_renderer/wolf_3d_ascii_renderer.dart';
import 'package:wolf_3d_renderer/wolf_3d_flutter_renderer.dart';
import 'package:wolf_3d_renderer/wolf_3d_glsl_renderer.dart';
enum _RendererMode {
software,
ascii,
glsl,
}
/// Launches a [WolfEngine] via [Wolf3d] and exposes renderer/input integrations.
class GameScreen extends StatefulWidget {
/// Shared application facade owning the engine, audio, and input.
final Wolf3d wolf3d;
/// Creates a gameplay screen driven by [wolf3d].
const GameScreen({
required this.wolf3d,
super.key,
});
@override
State<GameScreen> createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> {
late final WolfEngine _engine;
_RendererMode _rendererMode = _RendererMode.software;
@override
void initState() {
super.initState();
_engine = widget.wolf3d.launchEngine(
onGameWon: () {
_engine.difficulty = null;
widget.wolf3d.clearActiveDifficulty();
Navigator.of(context).pop();
},
);
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: _engine.difficulty != null,
onPopInvokedWithResult: (didPop, _) {
if (!didPop && _engine.difficulty == null) {
widget.wolf3d.input.queueBackAction();
}
},
child: Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
return Listener(
onPointerDown: (event) {
widget.wolf3d.input.onPointerDown(event);
},
onPointerUp: widget.wolf3d.input.onPointerUp,
onPointerMove: widget.wolf3d.input.onPointerMove,
onPointerHover: widget.wolf3d.input.onPointerMove,
child: Stack(
children: [
_buildRenderer(),
if (!_engine.isInitialized)
Container(
color: Colors.black,
child: const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(color: Colors.teal),
SizedBox(height: 20),
Text(
"GET PSYCHED!",
style: TextStyle(
color: Colors.teal,
fontFamily: 'monospace',
),
),
],
),
),
),
// Tab toggles the renderer implementation for quick visual debugging.
Focus(
autofocus: true,
onKeyEvent: (node, event) {
if (event is KeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.tab) {
setState(_cycleRendererMode);
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
child: const SizedBox.shrink(),
),
// A second full-screen overlay keeps the presentation simple while
// the engine is still warming up or decoding the first frame.
if (!_engine.isInitialized)
Container(
color: Colors.black,
child: const Center(
child: CircularProgressIndicator(color: Colors.teal),
),
),
Positioned(
top: 16,
right: 16,
child: Text(
'TAB: ${_modeLabel(_rendererMode)}',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.5),
),
),
),
],
),
);
},
),
),
);
}
Widget _buildRenderer() {
// Keep all renderers behind the same engine so mode switching does not
// reset level state or audio playback.
switch (_rendererMode) {
case _RendererMode.software:
return WolfFlutterRenderer(engine: _engine);
case _RendererMode.ascii:
return WolfAsciiRenderer(engine: _engine);
case _RendererMode.glsl:
return WolfGlslRenderer(
engine: _engine,
onUnavailable: _onGlslUnavailable,
);
}
}
void _cycleRendererMode() {
switch (_rendererMode) {
case _RendererMode.software:
_rendererMode = _RendererMode.ascii;
break;
case _RendererMode.ascii:
_rendererMode = _RendererMode.glsl;
break;
case _RendererMode.glsl:
_rendererMode = _RendererMode.software;
break;
}
}
void _onGlslUnavailable() {
if (!mounted || _rendererMode != _RendererMode.glsl) {
return;
}
setState(() {
_rendererMode = _RendererMode.software;
});
}
String _modeLabel(_RendererMode mode) {
switch (mode) {
case _RendererMode.software:
return 'Software';
case _RendererMode.ascii:
return 'ASCII';
case _RendererMode.glsl:
return 'GLSL';
}
}
}