@@ -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<Coordinate2D> {
|
||||
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)';
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
||||
|
||||
enum EntityState { staticObj, idle, patrolling, shooting, pain, dead }
|
||||
|
||||
abstract class Entity<T> {
|
||||
@@ -18,4 +20,6 @@ abstract class Entity<T> {
|
||||
this.mapId = 0,
|
||||
this.lastActionTime = 0,
|
||||
});
|
||||
|
||||
Coordinate2D get position => Coordinate2D(x, y);
|
||||
}
|
||||
|
||||
@@ -151,15 +151,12 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
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<WolfRenderer>
|
||||
// --- 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<WolfRenderer>
|
||||
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<WolfRenderer>
|
||||
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<WolfRenderer>
|
||||
);
|
||||
}
|
||||
|
||||
return Coordinate2D(dx, dy);
|
||||
return (movement: movement, dAngle: dAngle);
|
||||
}
|
||||
|
||||
// Now receives dx and dy explicitly
|
||||
@@ -282,11 +282,7 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user