From 41ab5c9fccac48bafeb9d8d11861f565f88edf66 Mon Sep 17 00:00:00 2001 From: Hans Kokx Date: Wed, 16 Sep 2026 15:57:08 +0200 Subject: [PATCH] Smoother keyboard movement using ticks instead of key repeat polling Signed-off-by: Hans Kokx --- lib/main.dart | 115 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 42 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 31c2369..eb977c3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import "dart:math" as math; +import "package:flutter/scheduler.dart"; import "package:flutter/services.dart"; import "package:flutter/widgets.dart"; @@ -151,19 +152,29 @@ class Player extends StatefulWidget { final ValueChanged onPositionUpdated; final RayData rayData; - /// How "fast" the player moves + /// Length of the player's direction line on the minimap. static const int speed = 5; + /// Turning speed in radians per second, applied every frame. + static const double turnRate = 3.5; + + /// Movement speed in pixels per second, applied every frame. + static const double moveSpeed = 100; + @override State createState() => _PlayerState(); } -class _PlayerState extends State { +class _PlayerState extends State with SingleTickerProviderStateMixin { + late final Ticker _ticker; + Duration _lastElapsed = Duration.zero; + @override void initState() { super.initState(); - HardwareKeyboard.instance.addHandler(keyPressHandler); + HardwareKeyboard.instance.addHandler(_consumeKey); + _ticker = createTicker(_tick)..start(); WidgetsBinding.instance.addPostFrameCallback((_) { widget.onRaysUpdated( @@ -185,8 +196,20 @@ class _PlayerState extends State { @override void dispose() { + HardwareKeyboard.instance.removeHandler(_consumeKey); + _ticker.dispose(); super.dispose(); - HardwareKeyboard.instance.removeHandler(keyPressHandler); + } + + bool _consumeKey(KeyEvent event) { + switch (event.logicalKey) { + case LogicalKeyboardKey.keyA: + case LogicalKeyboardKey.keyD: + case LogicalKeyboardKey.keyW: + case LogicalKeyboardKey.keyS: + return true; + } + return false; } /// Get the map tile at a given pixel coordinate @@ -225,60 +248,68 @@ class _PlayerState extends State { return choices.first; } - bool keyPressHandler(KeyEvent event) { + /// Advances the player once per frame based on the keys currently held. + void _tick(Duration elapsed) { + final double dt = math.min( + (elapsed - _lastElapsed).inMicroseconds / Duration.microsecondsPerSecond, + 0.05, + ); + _lastElapsed = elapsed; + final Set keys = HardwareKeyboard.instance.logicalKeysPressed; - bool handled = false; - // Where is the player now? Offset playerPosition = widget.rayData.playerPosition; + double playerAngle = widget.rayData.playerAngle; + + bool hasMoved = false; + + // Turn the player. + final bool turningLeft = keys.contains(LogicalKeyboardKey.keyA); + final bool turningRight = keys.contains(LogicalKeyboardKey.keyD); + if (turningLeft != turningRight) { + playerAngle += (turningRight ? 1 : -1) * Player.turnRate * dt; + playerAngle = playerAngle.normalizeAngle; + hasMoved = true; + } + + // Capture the direction *after* any turn this frame. + final Offset direction = playerAngle.direction; final Offset playerCenter = playerPosition + Offset(GameMap.dimensions.width / 2, GameMap.dimensions.height / 2); - double playerAngle = widget.rayData.playerAngle; // How many pixels away from a wall to stay - const double buffer = 2; + const double wallDistanceBuffer = 2.5; - if (keys.contains(LogicalKeyboardKey.keyA)) { - // Turn left - playerAngle -= 0.1; - handled = true; - } else if (keys.contains(LogicalKeyboardKey.keyD)) { - // Turn right - playerAngle += 0.1; - handled = true; - } + final bool movingForward = keys.contains(LogicalKeyboardKey.keyW); + final bool movingBackward = keys.contains(LogicalKeyboardKey.keyS); + if (movingForward != movingBackward) { + final double travel = Player.moveSpeed * dt; - // Capture the direction *after* it's potentially mutated - final Offset direction = playerAngle.direction; - - if (keys.contains(LogicalKeyboardKey.keyW)) { - if (safeDistanceToWall(playerCenter, direction) > Player.speed + buffer) { - // Move forward - playerPosition += playerAngle.playerDelta; + if (movingForward) { + if (safeDistanceToWall(playerCenter, direction) > + travel + wallDistanceBuffer) { + // Move forward + playerPosition += direction * travel; + hasMoved = true; + } + } else if (safeDistanceToWall(playerCenter, -direction) > + travel + wallDistanceBuffer) { + // Move backwards + playerPosition -= direction * travel; + hasMoved = true; } - handled = true; - } else if (keys.contains(LogicalKeyboardKey.keyS)) { - if (safeDistanceToWall(playerCenter, -direction) > - Player.speed + buffer) { - // Move backwwards - playerPosition -= playerAngle.playerDelta; - } - handled = true; } - if (handled) { - widget.onRaysUpdated( - calculateRays(playerPosition, playerAngle), - ); - widget.onAngleUpdated(playerAngle); - widget.onPositionUpdated(playerPosition); - return true; - } + if (!hasMoved) return; - return false; + widget.onRaysUpdated( + calculateRays(playerPosition, playerAngle), + ); + widget.onAngleUpdated(playerAngle); + widget.onPositionUpdated(playerPosition); } List calculateRays(Offset offset, double playerAngle) {