From c424e1047513efc2a7b88611771314f1876cb3a3 Mon Sep 17 00:00:00 2001 From: Hans Kokx Date: Tue, 17 Mar 2026 20:45:29 +0100 Subject: [PATCH] Enable option to turn on mouselook Signed-off-by: Hans Kokx --- .../lib/screens/difficulty_screen.dart | 1 + apps/wolf_3d_gui/lib/screens/game_screen.dart | 113 +++++++------- .../lib/src/input/wolf_3d_input.dart | 13 ++ packages/wolf_3d_flutter/lib/wolf_3d.dart | 2 + .../lib/wolf_3d_input_flutter.dart | 140 ++++++++++++++++-- 5 files changed, 199 insertions(+), 70 deletions(-) diff --git a/apps/wolf_3d_gui/lib/screens/difficulty_screen.dart b/apps/wolf_3d_gui/lib/screens/difficulty_screen.dart index f079f15..b6bc80d 100644 --- a/apps/wolf_3d_gui/lib/screens/difficulty_screen.dart +++ b/apps/wolf_3d_gui/lib/screens/difficulty_screen.dart @@ -35,6 +35,7 @@ class _DifficultyScreenState extends State { difficulty: difficulty, startingEpisode: widget.wolf3d.activeEpisode, audio: widget.wolf3d.audio, + input: widget.wolf3d.input, ), ), ); diff --git a/apps/wolf_3d_gui/lib/screens/game_screen.dart b/apps/wolf_3d_gui/lib/screens/game_screen.dart index 2b5da6f..cf4ad63 100644 --- a/apps/wolf_3d_gui/lib/screens/game_screen.dart +++ b/apps/wolf_3d_gui/lib/screens/game_screen.dart @@ -3,7 +3,6 @@ import 'package:flutter/services.dart'; 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_input_flutter.dart'; -import 'package:wolf_3d_dart/wolf_3d_input.dart'; import 'package:wolf_3d_renderer/wolf_3d_ascii_renderer.dart'; import 'package:wolf_3d_renderer/wolf_3d_flutter_renderer.dart'; @@ -12,12 +11,14 @@ class GameScreen extends StatefulWidget { final Difficulty difficulty; final int startingEpisode; final EngineAudio audio; + final Wolf3dFlutterInput input; const GameScreen({ required this.data, required this.difficulty, required this.startingEpisode, required this.audio, + required this.input, super.key, }); @@ -27,19 +28,17 @@ class GameScreen extends StatefulWidget { class _GameScreenState extends State { late final WolfEngine _engine; - late final Wolf3dInput _input; bool _useAsciiMode = false; @override void initState() { super.initState(); - _input = Wolf3dFlutterInput(); _engine = WolfEngine( data: widget.data, difficulty: widget.difficulty, startingEpisode: widget.startingEpisode, audio: widget.audio, - input: _input, + input: widget.input, onGameWon: () => Navigator.of(context).pop(), ); _engine.init(); @@ -48,65 +47,71 @@ class _GameScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: Stack( - children: [ - _useAsciiMode - ? WolfAsciiRenderer(engine: _engine) - : WolfFlutterRenderer(engine: _engine), + body: Listener( + onPointerDown: widget.input.onPointerDown, + onPointerUp: widget.input.onPointerUp, + onPointerMove: widget.input.onPointerMove, + onPointerHover: widget.input.onPointerMove, + child: Stack( + children: [ + _useAsciiMode + ? WolfAsciiRenderer(engine: _engine) + : WolfFlutterRenderer(engine: _engine), - 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', + 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 listener + Focus( + autofocus: true, + onKeyEvent: (node, event) { + if (event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.tab) { + setState(() => _useAsciiMode = !_useAsciiMode); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: const SizedBox.shrink(), ), - // TAB listener - Focus( - autofocus: true, - onKeyEvent: (node, event) { - if (event is KeyDownEvent && - event.logicalKey == LogicalKeyboardKey.tab) { - setState(() => _useAsciiMode = !_useAsciiMode); - return KeyEventResult.handled; - } - return KeyEventResult.ignored; - }, - child: const SizedBox.shrink(), - ), + // Loading Overlay + if (!_engine.isInitialized) + Container( + color: Colors.black, + child: const Center( + child: CircularProgressIndicator(color: Colors.teal), + ), + ), - // Loading Overlay - if (!_engine.isInitialized) - Container( - color: Colors.black, - child: const Center( - child: CircularProgressIndicator(color: Colors.teal), + Positioned( + top: 16, + right: 16, + child: Text( + 'TAB: Swap Renderer', + style: TextStyle(color: Colors.white.withValues(alpha: 0.5)), ), ), - - Positioned( - top: 16, - right: 16, - child: Text( - 'TAB: Swap Renderer', - style: TextStyle(color: Colors.white.withValues(alpha: 0.5)), - ), - ), - ], + ], + ), ), ); } diff --git a/packages/wolf_3d_dart/lib/src/input/wolf_3d_input.dart b/packages/wolf_3d_dart/lib/src/input/wolf_3d_input.dart index 923e803..9dcc90e 100644 --- a/packages/wolf_3d_dart/lib/src/input/wolf_3d_input.dart +++ b/packages/wolf_3d_dart/lib/src/input/wolf_3d_input.dart @@ -25,3 +25,16 @@ abstract class Wolf3dInput { requestedWeapon: requestedWeapon, ); } + +enum WolfInputAction { + forward, + backward, + turnLeft, + turnRight, + fire, + interact, + weapon1, + weapon2, + weapon3, + weapon4, +} diff --git a/packages/wolf_3d_flutter/lib/wolf_3d.dart b/packages/wolf_3d_flutter/lib/wolf_3d.dart index b86b97e..b4ea9d5 100644 --- a/packages/wolf_3d_flutter/lib/wolf_3d.dart +++ b/packages/wolf_3d_flutter/lib/wolf_3d.dart @@ -4,6 +4,7 @@ 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(); @@ -14,6 +15,7 @@ class Wolf3d { // --- Core Systems --- final EngineAudio audio = WolfAudio(); + final Wolf3dFlutterInput input = Wolf3dFlutterInput(); // --- Getters --- WolfensteinData get activeGame { diff --git a/packages/wolf_3d_flutter/lib/wolf_3d_input_flutter.dart b/packages/wolf_3d_flutter/lib/wolf_3d_input_flutter.dart index 64f7734..c19d2eb 100644 --- a/packages/wolf_3d_flutter/lib/wolf_3d_input_flutter.dart +++ b/packages/wolf_3d_flutter/lib/wolf_3d_input_flutter.dart @@ -1,38 +1,146 @@ +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'; class Wolf3dFlutterInput extends Wolf3dInput { + // 1. Customizable Key Bindings (Multiple keys per action) + Map> bindings = { + WolfInputAction.forward: { + LogicalKeyboardKey.keyW, + LogicalKeyboardKey.arrowUp, + }, + WolfInputAction.backward: { + LogicalKeyboardKey.keyS, + LogicalKeyboardKey.arrowDown, + }, + WolfInputAction.turnLeft: { + LogicalKeyboardKey.keyA, + LogicalKeyboardKey.arrowLeft, + }, + WolfInputAction.turnRight: { + LogicalKeyboardKey.keyD, + LogicalKeyboardKey.arrowRight, + }, + WolfInputAction.fire: { + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.controlRight, + }, + WolfInputAction.interact: {LogicalKeyboardKey.space}, + WolfInputAction.weapon1: {LogicalKeyboardKey.digit1}, + WolfInputAction.weapon2: {LogicalKeyboardKey.digit2}, + WolfInputAction.weapon3: {LogicalKeyboardKey.digit3}, + WolfInputAction.weapon4: {LogicalKeyboardKey.digit4}, + }; + + // 2. Mouse State Variables + bool isMouseLeftDown = false; + bool isMouseRightDown = false; + double _mouseDeltaX = 0.0; + double _mouseDeltaY = 0.0; + bool _previousMouseRightDown = false; + + // 3. Mouselook Toggle + bool _isMouseLookEnabled = false; + + bool get mouseLookEnabled => _isMouseLookEnabled; + + set mouseLookEnabled(bool value) { + _isMouseLookEnabled = value; + // Clear any built-up delta when turning it off so it doesn't + // suddenly jump if they turn it back on. + if (!value) { + _mouseDeltaX = 0.0; + _mouseDeltaY = 0.0; + } + } + Set _previousKeys = {}; + // --- Customization Helpers --- + void bindKey(WolfInputAction action, LogicalKeyboardKey key) => + bindings[action]?.add(key); + void unbindKey(WolfInputAction action, LogicalKeyboardKey key) => + bindings[action]?.remove(key); + + // --- Mouse Event Handlers --- + void onPointerDown(PointerDownEvent event) { + if (event.buttons & kPrimaryMouseButton != 0) isMouseLeftDown = true; + if (event.buttons & kSecondaryMouseButton != 0) isMouseRightDown = true; + } + + void onPointerUp(PointerUpEvent event) { + if (event.buttons & kPrimaryMouseButton == 0) isMouseLeftDown = false; + if (event.buttons & kSecondaryMouseButton == 0) isMouseRightDown = false; + } + + void onPointerMove(PointerEvent event) { + // Only capture movement if mouselook is actually enabled + if (_isMouseLookEnabled) { + _mouseDeltaX += event.delta.dx; + _mouseDeltaY += event.delta.dy; + } + } + + // --- Input Helpers --- + bool _isActive(WolfInputAction action, Set pressedKeys) { + return bindings[action]!.any((key) => pressedKeys.contains(key)); + } + + bool _isNewlyPressed( + WolfInputAction action, + Set newlyPressed, + ) { + return bindings[action]!.any((key) => newlyPressed.contains(key)); + } + @override void update() { final pressedKeys = HardwareKeyboard.instance.logicalKeysPressed; final newlyPressedKeys = pressedKeys.difference(_previousKeys); - isMovingForward = pressedKeys.contains(LogicalKeyboardKey.keyW); - isMovingBackward = pressedKeys.contains(LogicalKeyboardKey.keyS); - isTurningLeft = pressedKeys.contains(LogicalKeyboardKey.keyA); - isTurningRight = pressedKeys.contains(LogicalKeyboardKey.keyD); + // Evaluate keyboard first + bool kbForward = _isActive(WolfInputAction.forward, pressedKeys); + bool kbBackward = _isActive(WolfInputAction.backward, pressedKeys); + bool kbLeft = _isActive(WolfInputAction.turnLeft, pressedKeys); + bool kbRight = _isActive(WolfInputAction.turnRight, pressedKeys); - isInteracting = newlyPressedKeys.contains(LogicalKeyboardKey.space); + // Add mouse delta if mouselook is enabled + 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 + _mouseDeltaX = 0.0; + _mouseDeltaY = 0.0; + + // Right click or Spacebar to interact + isInteracting = + _isNewlyPressed(WolfInputAction.interact, newlyPressedKeys) || + (mouseLookEnabled && isMouseRightDown && !_previousMouseRightDown); + + // Left click or Ctrl to fire isFiring = - pressedKeys.contains(LogicalKeyboardKey.controlLeft) && - !pressedKeys.contains(LogicalKeyboardKey.space); + _isActive(WolfInputAction.fire, pressedKeys) || + (mouseLookEnabled && isMouseLeftDown); requestedWeapon = null; - for (final LogicalKeyboardKey key in newlyPressedKeys) { - if (key == LogicalKeyboardKey.digit1) requestedWeapon = WeaponType.knife; - if (key == LogicalKeyboardKey.digit2) requestedWeapon = WeaponType.pistol; - if (key == LogicalKeyboardKey.digit3) { - requestedWeapon = WeaponType.machineGun; - } - if (key == LogicalKeyboardKey.digit4) { - requestedWeapon = WeaponType.chainGun; - } + if (_isNewlyPressed(WolfInputAction.weapon1, newlyPressedKeys)) { + requestedWeapon = WeaponType.knife; + } + if (_isNewlyPressed(WolfInputAction.weapon2, newlyPressedKeys)) { + requestedWeapon = WeaponType.pistol; + } + if (_isNewlyPressed(WolfInputAction.weapon3, newlyPressedKeys)) { + requestedWeapon = WeaponType.machineGun; + } + if (_isNewlyPressed(WolfInputAction.weapon4, newlyPressedKeys)) { + requestedWeapon = WeaponType.chainGun; } _previousKeys = Set.from(pressedKeys); + _previousMouseRightDown = isMouseRightDown; } }