Smoother keyboard movement using ticks instead of key repeat polling

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-09-16 15:57:08 +02:00
parent 7d99526d33
commit 41ab5c9fcc
+73 -42
View File
@@ -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<Offset> 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<Player> createState() => _PlayerState();
}
class _PlayerState extends State<Player> {
class _PlayerState extends State<Player> 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<Player> {
@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<Player> {
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<LogicalKeyboardKey> 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<Ray> calculateRays(Offset offset, double playerAngle) {