From a872b6fcfa558f4b850038b78a4044c25c0fc776 Mon Sep 17 00:00:00 2001 From: Hans Kokx Date: Sat, 14 Mar 2026 00:59:34 +0100 Subject: [PATCH] Cleanup movement Signed-off-by: Hans Kokx --- lib/classes/coordinate_2d.dart | 69 ++++++++++++++---------- lib/features/entities/enemies/enemy.dart | 16 +++--- lib/features/entities/entity.dart | 4 ++ lib/features/renderer/renderer.dart | 52 +++++++++--------- 4 files changed, 76 insertions(+), 65 deletions(-) diff --git a/lib/classes/coordinate_2d.dart b/lib/classes/coordinate_2d.dart index 5a45f4e..6332f9d 100644 --- a/lib/classes/coordinate_2d.dart +++ b/lib/classes/coordinate_2d.dart @@ -1,54 +1,69 @@ import 'dart:math' as math; -/// A robust, immutable 2D coordinate. +/// A lightweight, immutable 2D Vector/Coordinate system. class Coordinate2D implements Comparable { final double x; final double y; const Coordinate2D(this.x, this.y); - /// Factory for a (0,0) coordinate. - static const Coordinate2D zero = Coordinate2D(0.0, 0.0); - - // --- Core Logic --- - - /// Returns the Euclidean distance between this and [other]. - /// Uses the formula: $$d = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}$$ - double distanceTo(Coordinate2D other) { - final dx = x - other.x; - final dy = y - other.y; - return math.sqrt(dx * dx + dy * dy); + /// Returns the angle in radians between this coordinate and [other]. + /// Useful for "Look At" logic or determining steering direction. + /// Result is between -pi and pi. + double angleTo(Coordinate2D other) { + return math.atan2(other.y - y, other.x - x); } - /// True if both ordinates are finite (not NaN or Infinity). - bool get isValid => x.isFinite && y.isFinite; + /// Rotates the coordinate around (0,0) by [radians]. + Coordinate2D rotate(double radians) { + final cos = math.cos(radians); + final sin = math.sin(radians); + return Coordinate2D( + (x * cos) - (y * sin), + (x * sin) + (y * cos), + ); + } + + /// Linear Interpolation: Slides between this and [target] by [t] (0.0 to 1.0). + /// Perfect for smooth camera follows or "lerping" an object to a new spot. + Coordinate2D lerp(Coordinate2D target, double t) { + return Coordinate2D( + x + (target.x - x) * t, + y + (target.y - y) * t, + ); + } + + double get magnitude => math.sqrt(x * x + y * y); + + Coordinate2D get normalized { + final m = magnitude; + if (m == 0) return const Coordinate2D(0, 0); + return Coordinate2D(x / m, y / m); + } + + double dot(Coordinate2D other) => (x * other.x) + (y * other.y); + + double distanceTo(Coordinate2D other) => (this - other).magnitude; Coordinate2D operator +(Coordinate2D other) => Coordinate2D(x + other.x, y + other.y); Coordinate2D operator -(Coordinate2D other) => Coordinate2D(x - other.x, y - other.y); - Coordinate2D operator *(double factor) => - Coordinate2D(x * factor, y * factor); - Coordinate2D operator /(double divisor) => - Coordinate2D(x / divisor, y / divisor); + Coordinate2D operator *(double n) => Coordinate2D(x * n, y * n); + Coordinate2D operator /(double n) => Coordinate2D(x / n, y / n); @override bool operator ==(Object other) => identical(this, other) || - other is Coordinate2D && - runtimeType == other.runtimeType && - x == other.x && - y == other.y; + other is Coordinate2D && x == other.x && y == other.y; @override int get hashCode => Object.hash(x, y); @override - int compareTo(Coordinate2D other) { - if (x != other.x) return x.compareTo(other.x); - return y.compareTo(other.y); - } + int compareTo(Coordinate2D other) => + x != other.x ? x.compareTo(other.x) : y.compareTo(other.y); @override - String toString() => 'Coordinate($x, $y)'; + String toString() => 'Coordinate2D($x, $y)'; } diff --git a/lib/features/entities/enemies/enemy.dart b/lib/features/entities/enemies/enemy.dart index 9e239b4..e3ea510 100644 --- a/lib/features/entities/enemies/enemy.dart +++ b/lib/features/entities/enemies/enemy.dart @@ -61,12 +61,10 @@ abstract class Enemy extends Entity { Coordinate2D playerPosition, bool Function(int x, int y) isWalkable, ) { - double dx = playerPosition.x - x; - double dy = playerPosition.y - y; - double distance = math.sqrt(dx * dx + dy * dy); + double distance = position.distanceTo(playerPosition); // 1. FOV Check - double angleToPlayer = math.atan2(dy, dx); + double angleToPlayer = position.angleTo(playerPosition); double diff = angle - angleToPlayer; while (diff <= -math.pi) { @@ -79,14 +77,12 @@ abstract class Enemy extends Entity { if (diff.abs() > math.pi / 2) return false; // 2. Map Check - double dirX = dx / distance; - double dirY = dy / distance; + Coordinate2D dir = (playerPosition - position).normalized; double stepSize = 0.2; - for (double i = 0; i < distance; i += stepSize) { - int checkX = (x + dirX * i).toInt(); - int checkY = (y + dirY * i).toInt(); - if (!isWalkable(checkX, checkY)) return false; + for (double i = 0; i < distance; i += stepSize) { + Coordinate2D checkPos = position + (dir * i); + if (!isWalkable(checkPos.x.toInt(), checkPos.y.toInt())) return false; } return true; diff --git a/lib/features/entities/entity.dart b/lib/features/entities/entity.dart index c327571..c601c38 100644 --- a/lib/features/entities/entity.dart +++ b/lib/features/entities/entity.dart @@ -1,3 +1,5 @@ +import 'package:wolf_dart/classes/coordinate_2d.dart'; + enum EntityState { staticObj, idle, patrolling, shooting, pain, dead } abstract class Entity { @@ -18,4 +20,6 @@ abstract class Entity { this.mapId = 0, this.lastActionTime = 0, }); + + Coordinate2D get position => Coordinate2D(x, y); } diff --git a/lib/features/renderer/renderer.dart b/lib/features/renderer/renderer.dart index cf1f08e..9183866 100644 --- a/lib/features/renderer/renderer.dart +++ b/lib/features/renderer/renderer.dart @@ -151,15 +151,12 @@ class _WolfRendererState extends State for (int y = 0; y < currentLevel.length; y++) { for (int x = 0; x < currentLevel[y].length; x++) { if (currentLevel[y][x] == 0) { - double safeX = x + 0.5; - double safeY = y + 0.5; - double dist = math.sqrt( - math.pow(safeX - player.x, 2) + math.pow(safeY - player.y, 2), - ); + Coordinate2D safeSpot = Coordinate2D(x + 0.5, y + 0.5); + double dist = safeSpot.distanceTo(player.position); if (dist < shortestDist) { shortestDist = dist; - nearestSafeSpot = Coordinate2D(safeX, safeY); + nearestSafeSpot = safeSpot; } } } @@ -180,13 +177,13 @@ class _WolfRendererState extends State // --- ORCHESTRATOR --- void _tick(Duration elapsed) { // 1. Process intentions and receive movement vectors - final movement = _processInputs(elapsed); + final inputResult = _processInputs(elapsed); doorManager.update(elapsed); // 2. Explicit State Updates player.updateWeaponSwitch(); - _applyMovementAndCollision(movement.x, movement.y); + _applyMovementAndCollision(inputResult.movement.x, inputResult.movement.y); _updateEntities(elapsed); // Explicit reassignment from a pure(r) function @@ -208,14 +205,15 @@ class _WolfRendererState extends State damageFlashOpacity = 0.5; } - // Returns movement deltas instead of modifying class variables - Coordinate2D _processInputs(Duration elapsed) { + // Returns a Record containing both movement delta and rotation delta + ({Coordinate2D movement, double dAngle}) _processInputs(Duration elapsed) { inputManager.update(); const double moveSpeed = 0.16; const double turnSpeed = 0.12; - double dx = 0; - double dy = 0; + + Coordinate2D movement = const Coordinate2D(0, 0); + double dAngle = 0.0; if (inputManager.requestedWeapon != null) { player.requestWeaponSwitch(inputManager.requestedWeapon!); @@ -225,21 +223,23 @@ class _WolfRendererState extends State player.fire(elapsed.inMilliseconds); } + // Calculate intended rotation + if (inputManager.isTurningLeft) dAngle -= turnSpeed; + if (inputManager.isTurningRight) dAngle += turnSpeed; + + // Calculate intended movement based on CURRENT angle + Coordinate2D forwardVec = Coordinate2D( + math.cos(player.angle), + math.sin(player.angle), + ); + if (inputManager.isMovingForward) { - dx += math.cos(player.angle) * moveSpeed; - dy += math.sin(player.angle) * moveSpeed; + movement += forwardVec * moveSpeed; } if (inputManager.isMovingBackward) { - dx -= math.cos(player.angle) * moveSpeed; - dy -= math.sin(player.angle) * moveSpeed; + movement -= forwardVec * moveSpeed; } - if (inputManager.isTurningLeft) player.angle -= turnSpeed; - if (inputManager.isTurningRight) player.angle += turnSpeed; - - if (player.angle < 0) player.angle += 2 * math.pi; - if (player.angle > 2 * math.pi) player.angle -= 2 * math.pi; - if (inputManager.isInteracting) { doorManager.handleInteraction( player.x, @@ -248,7 +248,7 @@ class _WolfRendererState extends State ); } - return Coordinate2D(dx, dy); + return (movement: movement, dAngle: dAngle); } // Now receives dx and dy explicitly @@ -282,11 +282,7 @@ class _WolfRendererState extends State onDamagePlayer: _takeDamage, ); } else if (entity is Collectible) { - double dx = player.x - entity.x; - double dy = player.y - entity.y; - double dist = math.sqrt(dx * dx + dy * dy); - - if (dist < 0.5) { + if (player.position.distanceTo(entity.position) < 0.5) { if (player.tryPickup(entity)) { itemsToRemove.add(entity); }