Moved away from singleton pattern

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-17 20:24:39 +01:00
parent 4c28a66554
commit 55cf73f7f5
7 changed files with 70 additions and 47 deletions

View File

@@ -5,11 +5,11 @@ import 'package:wolf_3d_gui/screens/game_select_screen.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Wolf3d.I.init(); final Wolf3d wolf3d = await Wolf3d().init();
runApp( runApp(
const MaterialApp( MaterialApp(
home: GameSelectScreen(), home: GameSelectScreen(wolf3d: wolf3d),
), ),
); );
} }

View File

@@ -4,8 +4,11 @@ import 'package:wolf_3d_flutter/wolf_3d.dart';
import 'package:wolf_3d_gui/screens/game_screen.dart'; import 'package:wolf_3d_gui/screens/game_screen.dart';
class DifficultyScreen extends StatefulWidget { class DifficultyScreen extends StatefulWidget {
final Wolf3d wolf3d;
const DifficultyScreen({ const DifficultyScreen({
super.key, super.key,
required this.wolf3d,
}); });
@override @override
@@ -13,24 +16,25 @@ class DifficultyScreen extends StatefulWidget {
} }
class _DifficultyScreenState extends State<DifficultyScreen> { class _DifficultyScreenState extends State<DifficultyScreen> {
bool get isShareware => Wolf3d.I.activeGame.version == GameVersion.shareware; bool get isShareware =>
widget.wolf3d.activeGame.version == GameVersion.shareware;
@override @override
void dispose() { void dispose() {
Wolf3d.I.audio.stopMusic(); widget.wolf3d.audio.stopMusic();
super.dispose(); super.dispose();
} }
void _startGame(Difficulty difficulty, {bool showGallery = false}) { void _startGame(Difficulty difficulty, {bool showGallery = false}) {
Wolf3d.I.audio.stopMusic(); widget.wolf3d.audio.stopMusic();
Navigator.of(context).pushReplacement( Navigator.of(context).pushReplacement(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => GameScreen( builder: (context) => GameScreen(
data: Wolf3d.I.activeGame, data: widget.wolf3d.activeGame,
difficulty: difficulty, difficulty: difficulty,
startingEpisode: Wolf3d.I.activeEpisode, startingEpisode: widget.wolf3d.activeEpisode,
audio: Wolf3d.I.audio, audio: widget.wolf3d.audio,
), ),
), ),
); );
@@ -59,8 +63,6 @@ class _DifficultyScreenState extends State<DifficultyScreen> {
), ),
), ),
const SizedBox(height: 40), const SizedBox(height: 40),
// --- Difficulty Buttons ---
ListView.builder( ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: Difficulty.values.length, itemCount: Difficulty.values.length,

View File

@@ -6,7 +6,9 @@ import 'package:wolf_3d_gui/screens/sprite_gallery.dart';
import 'package:wolf_3d_gui/screens/vga_gallery.dart'; import 'package:wolf_3d_gui/screens/vga_gallery.dart';
class EpisodeScreen extends StatefulWidget { class EpisodeScreen extends StatefulWidget {
const EpisodeScreen({super.key}); final Wolf3d wolf3d;
const EpisodeScreen({super.key, required this.wolf3d});
@override @override
State<EpisodeScreen> createState() => _EpisodeScreenState(); State<EpisodeScreen> createState() => _EpisodeScreenState();
@@ -16,21 +18,21 @@ class _EpisodeScreenState extends State<EpisodeScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
Wolf3d.I.audio.playMenuMusic(); widget.wolf3d.audio.playMenuMusic();
} }
void _selectEpisode(int index) { void _selectEpisode(int index) {
Wolf3d.I.setActiveEpisode(index); widget.wolf3d.setActiveEpisode(index);
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => DifficultyScreen(), builder: (context) => DifficultyScreen(wolf3d: widget.wolf3d),
), ),
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<Episode> episodes = Wolf3d.I.activeGame.episodes; final List<Episode> episodes = widget.wolf3d.activeGame.episodes;
return Scaffold( return Scaffold(
backgroundColor: Colors.teal, backgroundColor: Colors.teal,
@@ -43,7 +45,7 @@ class _EpisodeScreenState extends State<EpisodeScreen> {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) { builder: (context) {
return VgaGallery(images: Wolf3d.I.vgaImages); return VgaGallery(images: widget.wolf3d.vgaImages);
}, },
), ),
); );
@@ -55,7 +57,9 @@ class _EpisodeScreenState extends State<EpisodeScreen> {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) { builder: (context) {
return SpriteGallery(sprites: Wolf3d.I.sprites); return SpriteGallery(
wolf3d: widget.wolf3d,
);
}, },
), ),
); );

View File

@@ -4,25 +4,27 @@ import 'package:wolf_3d_flutter/wolf_3d.dart';
import 'package:wolf_3d_gui/screens/episode_screen.dart'; import 'package:wolf_3d_gui/screens/episode_screen.dart';
class GameSelectScreen extends StatelessWidget { class GameSelectScreen extends StatelessWidget {
const GameSelectScreen({super.key}); final Wolf3d wolf3d;
const GameSelectScreen({super.key, required this.wolf3d});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: ListView.builder( body: ListView.builder(
itemCount: Wolf3d.I.availableGames.length, itemCount: wolf3d.availableGames.length,
itemBuilder: (context, i) { itemBuilder: (context, i) {
final WolfensteinData data = Wolf3d.I.availableGames[i]; final WolfensteinData data = wolf3d.availableGames[i];
final GameVersion version = data.version; final GameVersion version = data.version;
return Card( return Card(
child: ListTile( child: ListTile(
title: Text(version.name), title: Text(version.name),
onTap: () { onTap: () {
Wolf3d.I.setActiveGame(data); wolf3d.setActiveGame(data);
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const EpisodeScreen(), builder: (context) => EpisodeScreen(wolf3d: wolf3d),
), ),
); );
}, },

View File

@@ -5,11 +5,11 @@ import 'package:wolf_3d_flutter/wolf_3d.dart';
import 'package:wolf_3d_renderer/wolf_3d_asset_painter.dart'; import 'package:wolf_3d_renderer/wolf_3d_asset_painter.dart';
class SpriteGallery extends StatelessWidget { class SpriteGallery extends StatelessWidget {
final List<Sprite> sprites; final Wolf3d wolf3d;
const SpriteGallery({super.key, required this.sprites}); const SpriteGallery({super.key, required this.wolf3d});
bool get isShareware => Wolf3d.I.activeGame.version == GameVersion.shareware; bool get isShareware => wolf3d.activeGame.version == GameVersion.shareware;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -25,9 +25,8 @@ class SpriteGallery extends StatelessWidget {
crossAxisSpacing: 8, crossAxisSpacing: 8,
mainAxisSpacing: 8, mainAxisSpacing: 8,
), ),
itemCount: sprites.length, itemCount: wolf3d.sprites.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
// --- Check which enemy owns this sprite ---
String label = "Sprite Index: $index"; String label = "Sprite Index: $index";
for (final enemy in EnemyType.values) { for (final enemy in EnemyType.values) {
if (enemy.claimsSpriteIndex(index, isShareware: isShareware)) { if (enemy.claimsSpriteIndex(index, isShareware: isShareware)) {
@@ -36,14 +35,10 @@ class SpriteGallery extends StatelessWidget {
isShareware: isShareware, isShareware: isShareware,
); );
// Appends the enum name (e.g., "guard", "dog")
label += "\n${enemy.name}"; label += "\n${enemy.name}";
// Appends the animation name
if (animation != null) { if (animation != null) {
label += "\n${animation.name}"; label += "\n${animation.name}";
} }
break; break;
} }
} }
@@ -61,7 +56,7 @@ class SpriteGallery extends StatelessWidget {
Expanded( Expanded(
child: AspectRatio( child: AspectRatio(
aspectRatio: 4 / 3, aspectRatio: 4 / 3,
child: WolfAssetPainter.sprite(sprites[index]), child: WolfAssetPainter.sprite(wolf3d.sprites[index]),
), ),
), ),
], ],

View File

@@ -3,48 +3,52 @@ import 'package:wolf_3d_dart/wolf_3d_engine.dart';
import 'package:wolf_3d_flutter/wolf_3d.dart'; import 'package:wolf_3d_flutter/wolf_3d.dart';
class FlutterAudioAdapter implements EngineAudio { class FlutterAudioAdapter implements EngineAudio {
final Wolf3d wolf3d;
FlutterAudioAdapter(this.wolf3d);
@override @override
void playLevelMusic(WolfLevel level) { void playLevelMusic(WolfLevel level) {
Wolf3d.I.audio.playLevelMusic(level); wolf3d.audio.playLevelMusic(level);
} }
@override @override
void stopMusic() { void stopMusic() {
Wolf3d.I.audio.stopMusic(); wolf3d.audio.stopMusic();
} }
@override @override
void playSoundEffect(int sfxId) { void playSoundEffect(int sfxId) {
Wolf3d.I.audio.playSoundEffect(sfxId); wolf3d.audio.playSoundEffect(sfxId);
} }
@override @override
void playMenuMusic() { void playMenuMusic() {
Wolf3d.I.audio.playMenuMusic(); wolf3d.audio.playMenuMusic();
} }
@override @override
Future<void> init() async { Future<void> init() async {
await Wolf3d.I.audio.init(); await wolf3d.audio.init();
} }
@override @override
void dispose() { void dispose() {
Wolf3d.I.audio.dispose(); wolf3d.audio.dispose();
} }
@override @override
Future<void> debugSoundTest() async { Future<void> debugSoundTest() async {
Wolf3d.I.audio.debugSoundTest(); wolf3d.audio.debugSoundTest();
} }
@override @override
WolfensteinData? get activeGame => Wolf3d.I.activeGame; WolfensteinData? get activeGame => wolf3d.activeGame;
@override @override
set activeGame(WolfensteinData? value) { set activeGame(WolfensteinData? value) {
if (value != null) { if (value != null) {
Wolf3d.I.setActiveGame(value); wolf3d.setActiveGame(value);
} }
} }
} }

View File

@@ -6,9 +6,7 @@ import 'package:wolf_3d_dart/wolf_3d_engine.dart';
import 'package:wolf_3d_dart/wolf_3d_synth.dart'; import 'package:wolf_3d_dart/wolf_3d_synth.dart';
class Wolf3d { class Wolf3d {
Wolf3d._(); Wolf3d();
static final Wolf3d _instance = Wolf3d._();
static Wolf3d get I => _instance;
// --- State --- // --- State ---
final List<WolfensteinData> availableGames = []; final List<WolfensteinData> availableGames = [];
@@ -31,6 +29,13 @@ class Wolf3d {
int get activeEpisode => _activeEpisode; int get activeEpisode => _activeEpisode;
void setActiveEpisode(int episodeIndex) { void setActiveEpisode(int episodeIndex) {
if (_activeGame == null) {
throw StateError("No active game selected. Call setActiveGame() first.");
}
if (episodeIndex < 0 || episodeIndex >= _activeGame!.episodes.length) {
throw RangeError("Episode index out of range for the active game.");
}
_activeEpisode = episodeIndex; _activeEpisode = episodeIndex;
} }
@@ -45,12 +50,22 @@ class Wolf3d {
// --- Actions --- // --- Actions ---
void setActiveGame(WolfensteinData game) { void setActiveGame(WolfensteinData game) {
if (!availableGames.contains(game)) {
throw ArgumentError(
"The provided game data is not in the list of available games.",
);
}
if (_activeGame == game) {
return; // No change needed
}
_activeGame = game; _activeGame = game;
audio.activeGame = game; audio.activeGame = game;
} }
/// Initializes the engine by loading available game data. /// Initializes the engine by loading available game data.
Future<void> init({String? directory}) async { Future<Wolf3d> init({String? directory}) async {
await audio.init(); await audio.init();
availableGames.clear(); availableGames.clear();
@@ -63,7 +78,6 @@ class Wolf3d {
for (final version in versionsToTry) { for (final version in versionsToTry) {
try { try {
final ext = version.version.fileExtension; final ext = version.version.fileExtension;
// final folder = 'assets/${version.path}';
final folder = 'packages/wolf_3d_assets/assets/${version.path}'; final folder = 'packages/wolf_3d_assets/assets/${version.path}';
final data = WolfensteinLoader.loadFromBytes( final data = WolfensteinLoader.loadFromBytes(
@@ -100,6 +114,8 @@ class Wolf3d {
debugPrint("External discovery failed: $e"); debugPrint("External discovery failed: $e");
} }
} }
return this;
} }
Future<ByteData?> _tryLoad(String path) async { Future<ByteData?> _tryLoad(String path) async {