Refactor and enhance documentation across the Wolf3D project
- Updated library imports to use the correct package paths for consistency. - Added detailed documentation comments to various classes and methods, improving code readability and maintainability. - Refined the GameSelectScreen, SpriteGallery, and VgaGallery classes with clearer descriptions of their functionality. - Enhanced the CliInput class to better explain the input handling process and its interaction with the engine. - Improved the SixelRasterizer and Opl2Emulator classes with comprehensive comments on their operations and state management. - Removed the deprecated wolf_3d.dart file and consolidated its functionality into wolf_3d_flutter.dart for a cleaner architecture. - Updated the Wolf3dFlutterInput class to clarify its role in merging keyboard and pointer events. - Enhanced the rendering classes to provide better context on their purpose and usage within the Flutter framework. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_flutter/wolf_3d.dart';
|
||||
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
|
||||
|
||||
class FlutterAudioAdapter implements EngineAudio {
|
||||
final Wolf3d wolf3d;
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_synth.dart';
|
||||
import 'package:wolf_3d_flutter/wolf_3d_input_flutter.dart';
|
||||
|
||||
class Wolf3d {
|
||||
Wolf3d();
|
||||
|
||||
// --- State ---
|
||||
final List<WolfensteinData> availableGames = [];
|
||||
WolfensteinData? _activeGame;
|
||||
|
||||
// --- Core Systems ---
|
||||
final EngineAudio audio = WolfAudio();
|
||||
final Wolf3dFlutterInput input = Wolf3dFlutterInput();
|
||||
|
||||
// --- Getters ---
|
||||
WolfensteinData get activeGame {
|
||||
if (_activeGame == null) {
|
||||
throw StateError("No active game selected. Call setActiveGame() first.");
|
||||
}
|
||||
return _activeGame!;
|
||||
}
|
||||
|
||||
// --- Episode ---
|
||||
int _activeEpisode = 0;
|
||||
|
||||
int get activeEpisode => _activeEpisode;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Convenience getters for the active game's assets
|
||||
List<WolfLevel> get levels => activeGame.episodes[activeEpisode].levels;
|
||||
List<Sprite> get walls => activeGame.walls;
|
||||
List<Sprite> get sprites => activeGame.sprites;
|
||||
List<PcmSound> get sounds => activeGame.sounds;
|
||||
List<PcmSound> get adLibSounds => activeGame.adLibSounds;
|
||||
List<ImfMusic> get music => activeGame.music;
|
||||
List<VgaImage> get vgaImages => activeGame.vgaImages;
|
||||
|
||||
// --- Actions ---
|
||||
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;
|
||||
audio.activeGame = game;
|
||||
}
|
||||
|
||||
/// Initializes the engine by loading available game data.
|
||||
Future<Wolf3d> init({String? directory}) async {
|
||||
await audio.init();
|
||||
availableGames.clear();
|
||||
|
||||
// 1. Bundle asset loading (migrated from GameSelectScreen)
|
||||
final versionsToTry = [
|
||||
(version: GameVersion.retail, path: 'retail'),
|
||||
(version: GameVersion.shareware, path: 'shareware'),
|
||||
];
|
||||
|
||||
for (final version in versionsToTry) {
|
||||
try {
|
||||
final ext = version.version.fileExtension;
|
||||
final folder = 'packages/wolf_3d_assets/assets/${version.path}';
|
||||
|
||||
final data = WolfensteinLoader.loadFromBytes(
|
||||
version: version.version,
|
||||
vswap: await _tryLoad('$folder/VSWAP.$ext'),
|
||||
mapHead: await _tryLoad('$folder/MAPHEAD.$ext'),
|
||||
gameMaps: await _tryLoad('$folder/GAMEMAPS.$ext'),
|
||||
vgaDict: await _tryLoad('$folder/VGADICT.$ext'),
|
||||
vgaHead: await _tryLoad('$folder/VGAHEAD.$ext'),
|
||||
vgaGraph: await _tryLoad('$folder/VGAGRAPH.$ext'),
|
||||
audioHed: await _tryLoad('$folder/AUDIOHED.$ext'),
|
||||
audioT: await _tryLoad('$folder/AUDIOT.$ext'),
|
||||
);
|
||||
|
||||
availableGames.add(data);
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. External side-loading (non-web)
|
||||
if (!kIsWeb) {
|
||||
try {
|
||||
final externalGames = await WolfensteinLoader.discover(
|
||||
directoryPath: directory,
|
||||
recursive: true,
|
||||
);
|
||||
for (var entry in externalGames.entries) {
|
||||
if (!availableGames.any((g) => g.version == entry.key)) {
|
||||
availableGames.add(entry.value);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("External discovery failed: $e");
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
Future<ByteData?> _tryLoad(String path) async {
|
||||
try {
|
||||
return await rootBundle.load(path);
|
||||
} catch (e) {
|
||||
debugPrint("Asset not found: $path");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,157 @@
|
||||
/// A Calculator.
|
||||
class Calculator {
|
||||
/// Returns [value] plus 1.
|
||||
int addOne(int value) => value + 1;
|
||||
/// High-level Flutter facade for discovering game data and sharing runtime services.
|
||||
library;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_synth.dart';
|
||||
import 'package:wolf_3d_flutter/wolf_3d_input_flutter.dart';
|
||||
|
||||
/// Coordinates asset discovery, audio initialization, and input reuse for apps.
|
||||
class Wolf3d {
|
||||
/// Creates an empty facade that must be initialized with [init].
|
||||
Wolf3d();
|
||||
|
||||
/// All successfully discovered or bundled game data sets.
|
||||
final List<WolfensteinData> availableGames = [];
|
||||
WolfensteinData? _activeGame;
|
||||
|
||||
/// Shared engine audio backend used by menus and gameplay sessions.
|
||||
final EngineAudio audio = WolfAudio();
|
||||
|
||||
/// Shared Flutter input adapter reused by gameplay screens.
|
||||
final Wolf3dFlutterInput input = Wolf3dFlutterInput();
|
||||
|
||||
/// The currently selected game data set.
|
||||
///
|
||||
/// Throws a [StateError] until [setActiveGame] has been called.
|
||||
WolfensteinData get activeGame {
|
||||
if (_activeGame == null) {
|
||||
throw StateError("No active game selected. Call setActiveGame() first.");
|
||||
}
|
||||
return _activeGame!;
|
||||
}
|
||||
|
||||
// Episode selection lives on the facade so menus can configure gameplay
|
||||
// before constructing a new engine instance.
|
||||
int _activeEpisode = 0;
|
||||
|
||||
/// Index of the episode currently selected in the UI flow.
|
||||
int get activeEpisode => _activeEpisode;
|
||||
|
||||
/// Sets the active episode for the current [activeGame].
|
||||
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;
|
||||
}
|
||||
|
||||
/// Convenience access to the active episode's level list.
|
||||
List<WolfLevel> get levels => activeGame.episodes[activeEpisode].levels;
|
||||
|
||||
/// Convenience access to the active game's wall textures.
|
||||
List<Sprite> get walls => activeGame.walls;
|
||||
|
||||
/// Convenience access to the active game's sprite set.
|
||||
List<Sprite> get sprites => activeGame.sprites;
|
||||
|
||||
/// Convenience access to digitized PCM effects.
|
||||
List<PcmSound> get sounds => activeGame.sounds;
|
||||
|
||||
/// Convenience access to AdLib/OPL effect assets.
|
||||
List<PcmSound> get adLibSounds => activeGame.adLibSounds;
|
||||
|
||||
/// Convenience access to level music tracks.
|
||||
List<ImfMusic> get music => activeGame.music;
|
||||
|
||||
/// Convenience access to VGA UI and splash images.
|
||||
List<VgaImage> get vgaImages => activeGame.vgaImages;
|
||||
|
||||
/// Makes [game] the active data set and points shared services at it.
|
||||
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;
|
||||
audio.activeGame = game;
|
||||
}
|
||||
|
||||
/// Initializes the engine by loading available game data.
|
||||
Future<Wolf3d> init({String? directory}) async {
|
||||
await audio.init();
|
||||
availableGames.clear();
|
||||
|
||||
// Bundled assets let the GUI work out of the box on supported platforms.
|
||||
final versionsToTry = [
|
||||
(version: GameVersion.retail, path: 'retail'),
|
||||
(version: GameVersion.shareware, path: 'shareware'),
|
||||
];
|
||||
|
||||
for (final version in versionsToTry) {
|
||||
try {
|
||||
final ext = version.version.fileExtension;
|
||||
final folder = 'packages/wolf_3d_assets/assets/${version.path}';
|
||||
|
||||
final data = WolfensteinLoader.loadFromBytes(
|
||||
version: version.version,
|
||||
vswap: await _tryLoad('$folder/VSWAP.$ext'),
|
||||
mapHead: await _tryLoad('$folder/MAPHEAD.$ext'),
|
||||
gameMaps: await _tryLoad('$folder/GAMEMAPS.$ext'),
|
||||
vgaDict: await _tryLoad('$folder/VGADICT.$ext'),
|
||||
vgaHead: await _tryLoad('$folder/VGAHEAD.$ext'),
|
||||
vgaGraph: await _tryLoad('$folder/VGAGRAPH.$ext'),
|
||||
audioHed: await _tryLoad('$folder/AUDIOHED.$ext'),
|
||||
audioT: await _tryLoad('$folder/AUDIOT.$ext'),
|
||||
);
|
||||
|
||||
availableGames.add(data);
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// On non-web platforms, also scan the local filesystem for user-supplied
|
||||
// data folders so the host can pick up extra versions automatically.
|
||||
if (!kIsWeb) {
|
||||
try {
|
||||
final externalGames = await WolfensteinLoader.discover(
|
||||
directoryPath: directory,
|
||||
recursive: true,
|
||||
);
|
||||
for (var entry in externalGames.entries) {
|
||||
if (!availableGames.any((g) => g.version == entry.key)) {
|
||||
availableGames.add(entry.value);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("External discovery failed: $e");
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Loads an asset from the Flutter bundle, returning `null` when absent.
|
||||
Future<ByteData?> _tryLoad(String path) async {
|
||||
try {
|
||||
return await rootBundle.load(path);
|
||||
} catch (e) {
|
||||
debugPrint("Asset not found: $path");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
/// Flutter-specific input adapter for the Wolf3D engine.
|
||||
///
|
||||
/// This class merges keyboard and pointer events into the frame-based fields
|
||||
/// exposed by [Wolf3dInput]. It is designed to be owned by higher-level app
|
||||
/// code and reused across menu and gameplay screens.
|
||||
library;
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_entities.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_input.dart';
|
||||
|
||||
/// Translates Flutter keyboard and mouse state into engine-friendly actions.
|
||||
class Wolf3dFlutterInput extends Wolf3dInput {
|
||||
// 1. Customizable Key Bindings (Multiple keys per action)
|
||||
/// Mapping from logical game actions to one or more keyboard bindings.
|
||||
///
|
||||
/// Each action can be rebound by replacing the matching set. The defaults
|
||||
/// support both WASD and arrow-key movement for desktop hosts.
|
||||
Map<WolfInputAction, Set<LogicalKeyboardKey>> bindings = {
|
||||
WolfInputAction.forward: {
|
||||
LogicalKeyboardKey.keyW,
|
||||
@@ -33,18 +44,23 @@ class Wolf3dFlutterInput extends Wolf3dInput {
|
||||
WolfInputAction.weapon4: {LogicalKeyboardKey.digit4},
|
||||
};
|
||||
|
||||
// 2. Mouse State Variables
|
||||
/// Whether the primary mouse button is currently held.
|
||||
bool isMouseLeftDown = false;
|
||||
|
||||
/// Whether the secondary mouse button is currently held.
|
||||
bool isMouseRightDown = false;
|
||||
double _mouseDeltaX = 0.0;
|
||||
double _mouseDeltaY = 0.0;
|
||||
bool _previousMouseRightDown = false;
|
||||
|
||||
// 3. Mouselook Toggle
|
||||
// Mouse-look is optional so touch or keyboard-only hosts can keep the same
|
||||
// adapter without incurring accidental pointer-driven movement.
|
||||
bool _isMouseLookEnabled = false;
|
||||
|
||||
/// Whether pointer deltas should be interpreted as movement/turn input.
|
||||
bool get mouseLookEnabled => _isMouseLookEnabled;
|
||||
|
||||
/// Enables or disables mouse-look style control.
|
||||
set mouseLookEnabled(bool value) {
|
||||
_isMouseLookEnabled = value;
|
||||
// Clear any built-up delta when turning it off so it doesn't
|
||||
@@ -57,27 +73,30 @@ class Wolf3dFlutterInput extends Wolf3dInput {
|
||||
|
||||
Set<LogicalKeyboardKey> _previousKeys = {};
|
||||
|
||||
// --- Customization Helpers ---
|
||||
/// Rebinds [action] to a single [key], replacing any previous bindings.
|
||||
void bindKey(WolfInputAction action, LogicalKeyboardKey key) {
|
||||
bindings[action] = {};
|
||||
bindings[action]?.add(key);
|
||||
}
|
||||
|
||||
/// Removes [key] from the current binding set for [action].
|
||||
void unbindKey(WolfInputAction action, LogicalKeyboardKey key) {
|
||||
bindings[action]?.remove(key);
|
||||
}
|
||||
|
||||
// --- Mouse Event Handlers ---
|
||||
/// Updates button state for a newly pressed pointer.
|
||||
void onPointerDown(PointerDownEvent event) {
|
||||
if (event.buttons & kPrimaryMouseButton != 0) isMouseLeftDown = true;
|
||||
if (event.buttons & kSecondaryMouseButton != 0) isMouseRightDown = true;
|
||||
}
|
||||
|
||||
/// Updates button state when a pointer is released.
|
||||
void onPointerUp(PointerUpEvent event) {
|
||||
if (event.buttons & kPrimaryMouseButton == 0) isMouseLeftDown = false;
|
||||
if (event.buttons & kSecondaryMouseButton == 0) isMouseRightDown = false;
|
||||
}
|
||||
|
||||
/// Accumulates pointer delta so it can be consumed on the next engine frame.
|
||||
void onPointerMove(PointerEvent event) {
|
||||
// Only capture movement if mouselook is actually enabled
|
||||
if (_isMouseLookEnabled) {
|
||||
@@ -86,11 +105,12 @@ class Wolf3dFlutterInput extends Wolf3dInput {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Input Helpers ---
|
||||
/// Returns whether any bound key for [action] is currently pressed.
|
||||
bool _isActive(WolfInputAction action, Set<LogicalKeyboardKey> pressedKeys) {
|
||||
return bindings[action]!.any((key) => pressedKeys.contains(key));
|
||||
}
|
||||
|
||||
/// Returns whether [action] was pressed during the current frame only.
|
||||
bool _isNewlyPressed(
|
||||
WolfInputAction action,
|
||||
Set<LogicalKeyboardKey> newlyPressed,
|
||||
@@ -103,20 +123,21 @@ class Wolf3dFlutterInput extends Wolf3dInput {
|
||||
final pressedKeys = HardwareKeyboard.instance.logicalKeysPressed;
|
||||
final newlyPressedKeys = pressedKeys.difference(_previousKeys);
|
||||
|
||||
// Evaluate keyboard first
|
||||
// Resolve digital keyboard state first so mouse-look only augments input.
|
||||
bool kbForward = _isActive(WolfInputAction.forward, pressedKeys);
|
||||
bool kbBackward = _isActive(WolfInputAction.backward, pressedKeys);
|
||||
bool kbLeft = _isActive(WolfInputAction.turnLeft, pressedKeys);
|
||||
bool kbRight = _isActive(WolfInputAction.turnRight, pressedKeys);
|
||||
|
||||
// Add mouse delta if mouselook is enabled
|
||||
// Mouse-look intentionally maps pointer deltas back onto the engine's
|
||||
// simple boolean input contract instead of introducing analog turns.
|
||||
isMovingForward = kbForward || (_isMouseLookEnabled && _mouseDeltaY < -1.5);
|
||||
isMovingBackward =
|
||||
kbBackward || (_isMouseLookEnabled && _mouseDeltaY > 1.5);
|
||||
isTurningLeft = kbLeft || (_isMouseLookEnabled && _mouseDeltaX < -1.5);
|
||||
isTurningRight = kbRight || (_isMouseLookEnabled && _mouseDeltaX > 1.5);
|
||||
|
||||
// Reset mouse deltas after consumption for digital engine movement
|
||||
// Deltas are one-frame impulses, so consume them immediately after use.
|
||||
_mouseDeltaX = 0.0;
|
||||
_mouseDeltaY = 0.0;
|
||||
|
||||
@@ -144,6 +165,8 @@ class Wolf3dFlutterInput extends Wolf3dInput {
|
||||
requestedWeapon = WeaponType.chainGun;
|
||||
}
|
||||
|
||||
// Preserve prior frame state so edge-triggered actions like interact and
|
||||
// weapon switching only fire once per physical key press.
|
||||
_previousKeys = Set.from(pressedKeys);
|
||||
_previousMouseRightDown = isMouseRightDown;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user