Delegate all audio management to the new audio package, then manage that through a new wolf3d class
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -17,21 +17,19 @@ import 'package:wolf_dart/features/player/player.dart';
|
||||
import 'package:wolf_dart/features/renderer/raycast_painter.dart';
|
||||
import 'package:wolf_dart/features/renderer/weapon_painter.dart';
|
||||
import 'package:wolf_dart/features/ui/hud.dart';
|
||||
import 'package:wolf_dart/sprite_gallery.dart';
|
||||
import 'package:wolf_dart/wolf_3d.dart';
|
||||
|
||||
class WolfRenderer extends StatefulWidget {
|
||||
const WolfRenderer(
|
||||
this.data, {
|
||||
required this.difficulty,
|
||||
required this.startingEpisode,
|
||||
super.key,
|
||||
this.difficulty = Difficulty.bringEmOn,
|
||||
this.showSpriteGallery = false,
|
||||
this.isShareware = true,
|
||||
});
|
||||
|
||||
final WolfensteinData data;
|
||||
final Difficulty difficulty;
|
||||
final bool showSpriteGallery;
|
||||
final bool isShareware;
|
||||
final int startingEpisode;
|
||||
|
||||
@override
|
||||
State<WolfRenderer> createState() => _WolfRendererState();
|
||||
@@ -56,6 +54,9 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
|
||||
double damageFlashOpacity = 0.0;
|
||||
|
||||
late int _currentMapIndex;
|
||||
late WolfLevel _currentLevel;
|
||||
|
||||
List<Entity> entities = [];
|
||||
|
||||
@override
|
||||
@@ -64,7 +65,38 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
_initGame();
|
||||
}
|
||||
|
||||
void _loadLevel(int mapIndex) {
|
||||
// Grab the specific level from the singleton
|
||||
_currentLevel = Wolf3d.I.levels[mapIndex];
|
||||
|
||||
// TODO: Initialize player position, spawn enemies based on difficulty, etc.
|
||||
debugPrint("Loaded Level: ${_currentLevel.name}");
|
||||
}
|
||||
|
||||
void _onLevelCompleted() {
|
||||
// When the player hits the elevator switch, advance the map
|
||||
setState(() {
|
||||
_currentMapIndex++;
|
||||
|
||||
// Check if they beat the episode (each episode is 10 levels)
|
||||
int maxLevelForEpisode = (widget.startingEpisode * 10) + 9;
|
||||
|
||||
if (_currentMapIndex > maxLevelForEpisode) {
|
||||
// TODO: Handle episode completion (show victory screen, return to menu)
|
||||
debugPrint("Episode Completed!");
|
||||
} else {
|
||||
_loadLevel(_currentMapIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _initGame() async {
|
||||
// 1. Calculate the starting index
|
||||
_currentMapIndex = widget.startingEpisode * 10;
|
||||
|
||||
// 2. Load the initial level data
|
||||
_loadLevel(_currentMapIndex);
|
||||
|
||||
// Get the first level out of the data class
|
||||
activeLevel = widget.data.levels.first;
|
||||
|
||||
@@ -109,7 +141,7 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
y + 0.5,
|
||||
widget.difficulty,
|
||||
widget.data.sprites.length,
|
||||
isSharewareMode: widget.isShareware,
|
||||
isSharewareMode: widget.data.version == GameVersion.shareware,
|
||||
);
|
||||
|
||||
if (newEntity != null) {
|
||||
@@ -402,10 +434,6 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
return const Center(child: CircularProgressIndicator(color: Colors.teal));
|
||||
}
|
||||
|
||||
if (widget.showSpriteGallery) {
|
||||
return SpriteGallery(sprites: widget.data.sprites);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: KeyboardListener(
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_soloud/flutter_soloud.dart';
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_synth/wolf_3d_synth.dart';
|
||||
import 'package:wolf_dart/features/difficulty/difficulty.dart';
|
||||
import 'package:wolf_dart/features/renderer/renderer.dart';
|
||||
import 'package:wolf_dart/wolf_3d.dart';
|
||||
@@ -16,77 +14,23 @@ class DifficultyScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DifficultyScreenState extends State<DifficultyScreen> {
|
||||
AudioSource? _menuMusicSource;
|
||||
SoundHandle? _menuMusicHandle;
|
||||
|
||||
bool get isShareware => Wolf3d.I.activeGame.version == GameVersion.shareware;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_playMenuMusic();
|
||||
}
|
||||
|
||||
Future<void> _playMenuMusic() async {
|
||||
final soloud = SoLoud.instance;
|
||||
|
||||
if (!soloud.isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. We only want to play music if the IMF data actually exists
|
||||
if (Wolf3d.I.music.isNotEmpty) {
|
||||
// Get the first track (usually the menu theme "Wondering About My Loved Ones")
|
||||
final music = Wolf3d.I.music.first;
|
||||
|
||||
// Render the hardware instructions into PCM and wrap in a WAV header
|
||||
final pcmSamples = ImfRenderer.render(music);
|
||||
|
||||
final wavBytes = ImfRenderer.createWavFile(pcmSamples);
|
||||
|
||||
// 3. Load the bytes into SoLoud's memory
|
||||
// The 'menu_theme.wav' string is just a dummy name to tell SoLoud it's dealing with a WAV format
|
||||
_menuMusicSource = await soloud.loadMem('menu_theme.wav', wavBytes);
|
||||
|
||||
// 4. Play the source and tell it to loop continuously!
|
||||
_menuMusicHandle = await soloud.play(
|
||||
_menuMusicSource!,
|
||||
looping: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_cleanupAudio();
|
||||
Wolf3d.I.audio.stopMusic();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _cleanupAudio() {
|
||||
final soloud = SoLoud.instance;
|
||||
|
||||
// Stop the playback
|
||||
if (_menuMusicHandle != null) {
|
||||
soloud.stop(_menuMusicHandle!);
|
||||
}
|
||||
|
||||
// Free the raw WAV data from C++ memory
|
||||
if (_menuMusicSource != null) {
|
||||
soloud.disposeSource(_menuMusicSource!);
|
||||
}
|
||||
}
|
||||
|
||||
void _startGame(Difficulty difficulty, {bool showGallery = false}) {
|
||||
// Stop the music and clear memory right before we push the new route
|
||||
_cleanupAudio();
|
||||
Wolf3d.I.audio.stopMusic();
|
||||
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => WolfRenderer(
|
||||
Wolf3d.I.activeGame,
|
||||
difficulty: difficulty,
|
||||
isShareware: isShareware,
|
||||
showSpriteGallery: showGallery,
|
||||
startingEpisode: Wolf3d.I.activeEpisode,
|
||||
),
|
||||
),
|
||||
);
|
||||
97
lib/features/screens/episode_screen.dart
Normal file
97
lib/features/screens/episode_screen.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wolf_dart/features/screens/difficulty_screen.dart';
|
||||
import 'package:wolf_dart/wolf_3d.dart';
|
||||
|
||||
class EpisodeScreen extends StatefulWidget {
|
||||
const EpisodeScreen({super.key});
|
||||
|
||||
@override
|
||||
State<EpisodeScreen> createState() => _EpisodeScreenState();
|
||||
}
|
||||
|
||||
class _EpisodeScreenState extends State<EpisodeScreen> {
|
||||
final List<String> _episodeNames = [
|
||||
"Episode 1\nEscape from Wolfenstein",
|
||||
"Episode 2\nOperation: Eisenfaust",
|
||||
"Episode 3\nDie, Fuhrer, Die!",
|
||||
"Episode 4\nA Dark Secret",
|
||||
"Episode 5\nTrail of the Madman",
|
||||
"Episode 6\nConfrontation",
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (Wolf3d.I.music.isNotEmpty) {
|
||||
Wolf3d.I.audio.playMusic(Wolf3d.I.music.first);
|
||||
}
|
||||
}
|
||||
|
||||
void _selectEpisode(int index) {
|
||||
Wolf3d.I.setActiveEpisode(index);
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
// We pass the audio handles so the next screen can stop them when the game starts
|
||||
builder: (context) => DifficultyScreen(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Determine how many episodes are available (10 levels per episode)
|
||||
final int numberOfEpisodes = (Wolf3d.I.levels.length / 10).floor().clamp(
|
||||
1,
|
||||
6,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'WHICH EPISODE TO PLAY?',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Courier',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: numberOfEpisodes,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 32.0,
|
||||
),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blueGrey[900],
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(300, 60),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
onPressed: () => _selectEpisode(index),
|
||||
child: Text(
|
||||
_episodeNames[index],
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_dart/features/difficulty/difficulty_screen.dart';
|
||||
import 'package:wolf_dart/features/screens/episode_screen.dart';
|
||||
import 'package:wolf_dart/wolf_3d.dart';
|
||||
|
||||
class GameSelectScreen extends StatelessWidget {
|
||||
@@ -22,7 +22,7 @@ class GameSelectScreen extends StatelessWidget {
|
||||
Wolf3d.I.setActiveGame(data);
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DifficultyScreen(),
|
||||
builder: (context) => const EpisodeScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_soloud/flutter_soloud.dart';
|
||||
import 'package:wolf_dart/game_select_screen.dart';
|
||||
import 'package:wolf_dart/wolf_3d.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await SoLoud.instance.init(
|
||||
sampleRate: 44100, // Audio quality
|
||||
bufferSize: 2048, // Buffer size affects latency
|
||||
channels: Channels.stereo,
|
||||
);
|
||||
|
||||
await Wolf3d.I.init();
|
||||
|
||||
runApp(
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:wolf_3d_data/wolf_3d_data.dart';
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_synth/wolf_3d_synth.dart';
|
||||
|
||||
class Wolf3d {
|
||||
Wolf3d._();
|
||||
@@ -12,6 +13,9 @@ class Wolf3d {
|
||||
final List<WolfensteinData> availableGames = [];
|
||||
WolfensteinData? _activeGame;
|
||||
|
||||
// --- Core Systems ---
|
||||
final WolfAudio audio = WolfAudio();
|
||||
|
||||
// --- Getters ---
|
||||
WolfensteinData get activeGame {
|
||||
if (_activeGame == null) {
|
||||
@@ -20,6 +24,15 @@ class Wolf3d {
|
||||
return _activeGame!;
|
||||
}
|
||||
|
||||
// --- Episode ---
|
||||
int _activeEpisode = 0;
|
||||
|
||||
int get activeEpisode => _activeEpisode;
|
||||
|
||||
void setActiveEpisode(int episodeIndex) {
|
||||
_activeEpisode = episodeIndex;
|
||||
}
|
||||
|
||||
// Convenience getters for the active game's assets
|
||||
List<WolfLevel> get levels => activeGame.levels;
|
||||
List<Sprite> get walls => activeGame.walls;
|
||||
@@ -36,6 +49,7 @@ class Wolf3d {
|
||||
|
||||
/// Initializes the engine by loading available game data.
|
||||
Future<void> init({String? directory}) async {
|
||||
await audio.init();
|
||||
availableGames.clear();
|
||||
|
||||
// 1. Bundle asset loading (migrated from GameSelectScreen)
|
||||
|
||||
92
packages/wolf_3d_synth/lib/src/wolf_3d_audio.dart
Normal file
92
packages/wolf_3d_synth/lib/src/wolf_3d_audio.dart
Normal file
@@ -0,0 +1,92 @@
|
||||
import 'package:flutter_soloud/flutter_soloud.dart';
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_synth/src/imf_renderer.dart';
|
||||
|
||||
class WolfAudio {
|
||||
bool _isInitialized = false;
|
||||
|
||||
// --- Music State ---
|
||||
AudioSource? _currentMusicSource;
|
||||
SoundHandle? _currentMusicHandle;
|
||||
|
||||
/// Initializes the SoLoud audio engine.
|
||||
Future<void> init() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
try {
|
||||
await SoLoud.instance.init(
|
||||
sampleRate: 44100,
|
||||
bufferSize: 2048,
|
||||
channels: Channels.stereo,
|
||||
);
|
||||
_isInitialized = true;
|
||||
print("WolfAudio: SoLoud initialized successfully.");
|
||||
} catch (e) {
|
||||
print("WolfAudio: Failed to initialize SoLoud - $e");
|
||||
}
|
||||
}
|
||||
|
||||
/// Disposes of the audio engine and frees resources.
|
||||
void dispose() {
|
||||
stopMusic();
|
||||
SoLoud.instance.deinit();
|
||||
_isInitialized = false;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// MUSIC MANAGEMENT
|
||||
// ==========================================
|
||||
|
||||
/// Renders and plays a specific IMF music track.
|
||||
Future<void> playMusic(ImfMusic track, {bool looping = true}) async {
|
||||
if (!_isInitialized) return;
|
||||
|
||||
// Stop currently playing music to prevent overlap
|
||||
stopMusic();
|
||||
|
||||
try {
|
||||
// Render hardware instructions into PCM and wrap in WAV
|
||||
final pcmSamples = ImfRenderer.render(track);
|
||||
final wavBytes = ImfRenderer.createWavFile(pcmSamples);
|
||||
|
||||
_currentMusicSource = await SoLoud.instance.loadMem(
|
||||
'track.wav',
|
||||
wavBytes,
|
||||
);
|
||||
_currentMusicHandle = await SoLoud.instance.play(
|
||||
_currentMusicSource!,
|
||||
looping: looping,
|
||||
);
|
||||
} catch (e) {
|
||||
print("WolfAudio: Error playing music track - $e");
|
||||
}
|
||||
}
|
||||
|
||||
/// Halts playback and frees memory for the current track.
|
||||
void stopMusic() {
|
||||
if (!_isInitialized) return;
|
||||
|
||||
if (_currentMusicHandle != null) {
|
||||
SoLoud.instance.stop(_currentMusicHandle!);
|
||||
_currentMusicHandle = null;
|
||||
}
|
||||
if (_currentMusicSource != null) {
|
||||
SoLoud.instance.disposeSource(_currentMusicSource!);
|
||||
_currentMusicSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Pauses the current track.
|
||||
void pauseMusic() {
|
||||
if (_isInitialized && _currentMusicHandle != null) {
|
||||
SoLoud.instance.setPause(_currentMusicHandle!, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Resumes a paused track.
|
||||
void resumeMusic() {
|
||||
if (_isInitialized && _currentMusicHandle != null) {
|
||||
SoLoud.instance.setPause(_currentMusicHandle!, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,4 @@
|
||||
/// More dartdocs go here.
|
||||
library;
|
||||
|
||||
export 'src/imf_renderer.dart' show ImfRenderer;
|
||||
export 'src/wolf_3d_audio.dart' show WolfAudio;
|
||||
|
||||
@@ -9,6 +9,7 @@ environment:
|
||||
resolution: workspace
|
||||
|
||||
dependencies:
|
||||
flutter_soloud: ^3.5.1
|
||||
wolf_3d_data_types:
|
||||
|
||||
dev_dependencies:
|
||||
|
||||
@@ -12,7 +12,6 @@ dependencies:
|
||||
wolf_3d_synth: any
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_soloud: ^3.5.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user