@@ -90,25 +90,25 @@ class WolfEngine {
|
||||
///
|
||||
/// Updates all world subsystems based on the [elapsed] time.
|
||||
/// This should be called once per frame by the host application.
|
||||
void tick(Duration elapsed) {
|
||||
void tick(Duration delta) {
|
||||
if (!isInitialized) return;
|
||||
|
||||
_timeAliveMs += elapsed.inMilliseconds;
|
||||
// Trust the incoming delta time natively
|
||||
_timeAliveMs += delta.inMilliseconds;
|
||||
|
||||
// 1. Process User Input
|
||||
input.update();
|
||||
final currentInput = input.currentInput;
|
||||
final inputResult = _processInputs(elapsed, currentInput);
|
||||
final inputResult = _processInputs(delta, currentInput);
|
||||
|
||||
// 2. Update Environment (Doors & Pushwalls)
|
||||
doorManager.update(elapsed);
|
||||
pushwallManager.update(elapsed, currentLevel);
|
||||
// 2. Update Environment
|
||||
doorManager.update(delta);
|
||||
pushwallManager.update(delta, currentLevel);
|
||||
|
||||
// 3. Update Physics & Movement
|
||||
player.tick(elapsed);
|
||||
player.tick(delta);
|
||||
player.angle += inputResult.dAngle;
|
||||
|
||||
// Normalize angle to [0, 2π]
|
||||
if (player.angle < 0) player.angle += 2 * math.pi;
|
||||
if (player.angle >= 2 * math.pi) player.angle -= 2 * math.pi;
|
||||
|
||||
@@ -120,8 +120,8 @@ class WolfEngine {
|
||||
player.x = validatedPos.x;
|
||||
player.y = validatedPos.y;
|
||||
|
||||
// 4. Update Dynamic World (Enemies & Combat)
|
||||
_updateEntities(elapsed);
|
||||
// 4. Update Dynamic World
|
||||
_updateEntities(delta);
|
||||
|
||||
player.updateWeapon(
|
||||
currentTime: _timeAliveMs,
|
||||
@@ -213,11 +213,15 @@ class WolfEngine {
|
||||
|
||||
/// Translates [EngineInput] into movement vectors and rotation.
|
||||
({Coordinate2D movement, double dAngle}) _processInputs(
|
||||
Duration elapsed,
|
||||
Duration delta,
|
||||
EngineInput input,
|
||||
) {
|
||||
const double moveSpeed = 0.14;
|
||||
const double turnSpeed = 0.10;
|
||||
// Standardize movement to 60 FPS (16.66ms per frame)
|
||||
final double timeScale = delta.inMilliseconds / 16.666;
|
||||
|
||||
// Apply the timeScale multiplier to ensure consistent speed at any framerate
|
||||
double moveSpeed = 0.14 * timeScale;
|
||||
double turnSpeed = 0.10 * timeScale;
|
||||
|
||||
Coordinate2D movement = const Coordinate2D(0, 0);
|
||||
double dAngle = 0.0;
|
||||
@@ -229,7 +233,6 @@ class WolfEngine {
|
||||
if (input.isFiring) {
|
||||
player.fire(_timeAliveMs);
|
||||
|
||||
// Throttle the acoustic flood-fill to emit a "wave" every 400ms while firing
|
||||
if (_timeAliveMs - _lastAcousticAlertTime > 400) {
|
||||
_propagateGunfire();
|
||||
_lastAcousticAlertTime = _timeAliveMs;
|
||||
@@ -248,14 +251,12 @@ class WolfEngine {
|
||||
if (input.isMovingForward) movement += forwardVec * moveSpeed;
|
||||
if (input.isMovingBackward) movement -= forwardVec * moveSpeed;
|
||||
|
||||
// Handle Wall Interactions (Switches, Doors, Secret Walls)
|
||||
if (input.isInteracting) {
|
||||
int targetX = (player.x + math.cos(player.angle)).toInt();
|
||||
int targetY = (player.y + math.sin(player.angle)).toInt();
|
||||
|
||||
if (targetX >= 0 && targetX < 64 && targetY >= 0 && targetY < 64) {
|
||||
int wallId = currentLevel[targetY][targetX];
|
||||
// Handle Elevator Switches
|
||||
if (wallId == MapObject.normalElevatorSwitch) {
|
||||
_onLevelCompleted(isSecretExit: false);
|
||||
return (movement: const Coordinate2D(0, 0), dAngle: 0.0);
|
||||
@@ -334,15 +335,29 @@ class WolfEngine {
|
||||
// Standard AI Update cycle
|
||||
final intent = entity.update(
|
||||
elapsedMs: _timeAliveMs,
|
||||
elapsedDeltaMs: elapsed.inMilliseconds,
|
||||
playerPosition: player.position,
|
||||
isWalkable: isWalkable,
|
||||
tryOpenDoor: doorManager.tryOpenDoor,
|
||||
onDamagePlayer: (int damage) => player.takeDamage(damage),
|
||||
);
|
||||
|
||||
// Scale the enemy's movement intent to prevent super-speed on 90Hz/120Hz displays
|
||||
double timeScale = elapsed.inMilliseconds / 16.666;
|
||||
|
||||
Coordinate2D scaledMovement = intent.movement * timeScale;
|
||||
Coordinate2D safeMovement = _clampMovement(scaledMovement);
|
||||
|
||||
entity.angle = intent.newAngle;
|
||||
entity.x += intent.movement.x;
|
||||
entity.y += intent.movement.y;
|
||||
|
||||
// Final sanity check: only move if the destination is on-map
|
||||
double nextX = entity.x + safeMovement.x;
|
||||
double nextY = entity.y + safeMovement.y;
|
||||
|
||||
if (nextX >= 0 && nextX < 64 && nextY >= 0 && nextY < 64) {
|
||||
entity.x = nextX;
|
||||
entity.y = nextY;
|
||||
}
|
||||
|
||||
// Handle Item Drops
|
||||
if (entity.state == EntityState.dead &&
|
||||
@@ -439,11 +454,29 @@ class WolfEngine {
|
||||
|
||||
/// Returns true if a tile is empty or contains a door that is sufficiently open.
|
||||
bool isWalkable(int x, int y) {
|
||||
// 1. Boundary Guard: Prevent range errors by checking if coordinates are on the map
|
||||
if (x < 0 || x >= 64 || y < 0 || y >= 64) {
|
||||
return false; // Out of bounds is never walkable
|
||||
}
|
||||
|
||||
if (currentLevel[y][x] == 0) return true;
|
||||
if (currentLevel[y][x] >= 90) return doorManager.isDoorOpenEnough(x, y);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Clamps movement to a maximum of 0.2 tiles per step to prevent wall-clipping
|
||||
/// and map-boundary jumps during low framerate/high delta spikes.
|
||||
Coordinate2D _clampMovement(Coordinate2D intent) {
|
||||
const double maxStep = 0.2;
|
||||
double length = math.sqrt(intent.x * intent.x + intent.y * intent.y);
|
||||
|
||||
if (length > maxStep) {
|
||||
double scale = maxStep / length;
|
||||
return Coordinate2D(intent.x * scale, intent.y * scale);
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
|
||||
/// Teleports the player to the nearest empty tile if they spawn inside a wall.
|
||||
void _bumpPlayerIfStuck() {
|
||||
int pX = player.x.toInt();
|
||||
|
||||
Reference in New Issue
Block a user