Refactor menu rendering and improve projection sampling

- Updated AsciiRasterizer to support game and episode selection menus with improved layout and cursor handling.
- Enhanced SixelRasterizer and SoftwareRasterizer to modularize menu drawing logic for game and episode selection.
- Introduced new methods for drawing menus and applying fade effects across rasterizers.
- Adjusted wall texture sampling in Rasterizer to anchor to projection height center for consistent rendering.
- Added tests for wall texture sampling behavior to ensure legacy compatibility and new functionality.
- Modified Flutter audio adapter to use nullable access for active game and adjusted game selection logic in the main class.
- Cleaned up input handling in Wolf3dFlutterInput by removing unused menu tap variables.

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-18 20:06:18 +01:00
parent d93f467163
commit 0e143892f0
15 changed files with 1090 additions and 204 deletions

View File

@@ -40,12 +40,15 @@ class Wolf3d {
return _activeGame!;
}
/// Nullable access to the selected game, useful during menu bootstrap.
WolfensteinData? get maybeActiveGame => _activeGame;
// Episode selection lives on the facade so menus can configure gameplay
// before constructing a new engine instance.
int _activeEpisode = 0;
int? _activeEpisode;
/// Index of the episode currently selected in the UI flow.
int get activeEpisode => _activeEpisode;
int? get activeEpisode => _activeEpisode;
Difficulty? _activeDifficulty;
@@ -80,16 +83,30 @@ class Wolf3d {
/// engine so it can be retrieved via [engine]. [onGameWon] is invoked when
/// the player completes the final level of the episode.
WolfEngine launchEngine({required void Function() onGameWon}) {
if (availableGames.isEmpty) {
throw StateError(
'No game data was discovered. Add game files before launching the engine.',
);
}
_engine = WolfEngine(
data: activeGame,
availableGames: availableGames,
difficulty: _activeDifficulty,
startingEpisode: _activeEpisode,
frameBuffer: FrameBuffer(320, 200),
menuBackgroundRgb: menuBackgroundRgb,
menuPanelRgb: menuPanelRgb,
audio: audio,
engineAudio: audio,
input: input,
onGameWon: onGameWon,
onMenuExit: onGameWon,
onGameSelected: (game) {
_activeGame = game;
audio.activeGame = game;
},
onEpisodeSelected: (episodeIndex) {
_activeEpisode = episodeIndex;
},
);
_engine!.init();
return _engine!;
@@ -107,8 +124,18 @@ class Wolf3d {
_activeEpisode = episodeIndex;
}
/// Clears any selected episode so menu flow starts fresh.
void clearActiveEpisode() {
_activeEpisode = null;
}
/// Convenience access to the active episode's level list.
List<WolfLevel> get levels => activeGame.episodes[activeEpisode].levels;
List<WolfLevel> get levels {
if (_activeEpisode == null) {
throw StateError('No active episode selected.');
}
return activeGame.episodes[_activeEpisode!].levels;
}
/// Convenience access to the active game's wall textures.
List<Sprite> get walls => activeGame.walls;
@@ -141,6 +168,7 @@ class Wolf3d {
}
_activeGame = game;
_activeEpisode = null;
audio.activeGame = game;
}