Änderung: Erweiterung der Fahrzeug-Logik und Verbesserung der Abbiege- sowie Spawn-Mechanik im Taxi-Spiel

Änderungen:
- Anpassung der Fahrzeug-Spawn-Wahrscheinlichkeit auf 100% für Debugging-Zwecke.
- Implementierung neuer Methoden zur Überprüfung, ob Fahrzeuge an Kreuzungen abbiegen sollten, und zur Ermittlung zufälliger Zielrichtungen basierend auf dem aktuellen Tile-Typ.
- Verbesserung der Fahrzeugbewegung und Kollisionserkennung durch präzisere Logik zur Überprüfung der Befahrbarkeit von Positionen.
- Erweiterung der Debugging-Ausgaben zur besseren Nachverfolgbarkeit von Fahrzeugbewegungen und -zuständen.

Diese Anpassungen optimieren die Fahrzeug-Interaktion und verbessern die Spielmechanik durch präzisere Logik und erweiterte Debugging-Funktionen.
This commit is contained in:
Torsten Schulz (local)
2025-10-20 15:15:18 +02:00
parent 9296e5e25d
commit bc3c765948
2 changed files with 422 additions and 337 deletions

View File

@@ -359,7 +359,7 @@ export default {
,carImage: null // Geladenes Auto-Bild ,carImage: null // Geladenes Auto-Bild
,lastCarGeneration: 0 // Zeitstempel der letzten Autos-Generierung ,lastCarGeneration: 0 // Zeitstempel der letzten Autos-Generierung
,carGenerationInterval: 1000 // Autos-Generierung alle 1000ms (1 Sekunde) prüfen ,carGenerationInterval: 1000 // Autos-Generierung alle 1000ms (1 Sekunde) prüfen
,carSpawnProbability: 0.2 // 20% Wahrscheinlichkeit pro Sekunde ,carSpawnProbability: 1.0 // 100% Wahrscheinlichkeit pro Sekunde (temporär für Debugging)
} }
}, },
computed: { computed: {
@@ -455,6 +455,91 @@ export default {
return item ? item.num : null; return item ? item.num : null;
}, },
// Prüfe, ob das Auto am Abbiegepunkt (Tile-Zentrumslinien) ist
// Gibt bevorzugte Zielrichtung zurück (string) oder null, wenn nicht abbiegen
shouldCarTurnAtIntersection(car) {
// Fahrzeugmitte verwenden
const cx = car.x + car.width / 2;
const cy = car.y + car.height / 2;
const rx = cx / this.tiles.size;
const ry = cy / this.tiles.size;
const tileType = this.mapTileTypeToStreetCoordinates(this.getCurrentTileType());
// Toleranzfenster um die Mittellinien, damit der Trigger nicht frame-genau sein muss
const tol = 0.01; // ~5px bei 500px Tilegröße
const nearYCenter = ry > 0.5 - tol && ry < 0.5 + tol;
const nearXCenter = rx > 0.5 - tol && rx < 0.5 + tol;
switch (tileType) {
case 'cornerBottomRight':
// unten -> rechts
if (car.direction === 'up' && nearYCenter) return 'right';
if (car.direction === 'left' && nearXCenter) return 'down';
return null;
case 'cornerBottomLeft':
// unten -> links
if (car.direction === 'up' && nearYCenter) return 'left';
if (car.direction === 'right' && nearXCenter) return 'down';
return null;
case 'cornerTopRight':
// oben -> rechts
if (car.direction === 'down' && nearYCenter) return 'right';
if (car.direction === 'left' && nearXCenter) return 'up';
return null;
case 'cornerTopLeft':
// oben -> links
if (car.direction === 'down' && nearYCenter) return 'left';
if (car.direction === 'right' && nearXCenter) return 'up';
return null;
default:
// Kreuzung: bei Zentrumslinien abbiegen erlaubt Richtung später wählen
if (nearXCenter || nearYCenter) return this.getRandomTargetDirection(car.direction);
return null;
}
},
// Liefert eine sinnvolle Zielrichtung je Tile-Typ und aktueller Richtung (keine Kehrtwende)
getRandomTargetDirection(currentDirection) {
const tileType = this.mapTileTypeToStreetCoordinates(this.getCurrentTileType());
const options = [];
switch (tileType) {
case 'cornerBottomRight':
// unten -> rechts: von 'up' nach 'right', von 'left' nach 'down'
if (currentDirection === 'up') options.push('right');
if (currentDirection === 'left') options.push('down');
break;
case 'cornerBottomLeft':
// unten -> links: von 'up' nach 'left', von 'right' nach 'down'
if (currentDirection === 'up') options.push('left');
if (currentDirection === 'right') options.push('down');
break;
case 'cornerTopRight':
// oben -> rechts: von 'down' nach 'right', von 'left' nach 'up'
if (currentDirection === 'down') options.push('right');
if (currentDirection === 'left') options.push('up');
break;
case 'cornerTopLeft':
// oben -> links: von 'down' nach 'left', von 'right' nach 'up'
if (currentDirection === 'down') options.push('left');
if (currentDirection === 'right') options.push('up');
break;
case 'cross':
// Kreuzung: links/rechts bei vertikaler Bewegung, hoch/runter bei horizontaler Bewegung
if (currentDirection === 'up' || currentDirection === 'down') options.push('left', 'right');
if (currentDirection === 'left' || currentDirection === 'right') options.push('up', 'down');
break;
default:
// gerade Strecken: weiterfahren
options.push(currentDirection);
}
// Fallback
if (options.length === 0) options.push(currentDirection);
return options[Math.floor(Math.random() * options.length)];
},
// Zeichnet die Straßen-Nr. auf die Minimap, je nach Tile-Typ und Position (pro Name nur einmal) // Zeichnet die Straßen-Nr. auf die Minimap, je nach Tile-Typ und Position (pro Name nur einmal)
drawStreetNumbersOnMinimap(ctx, x, y, size, tileType, col, row, drawnNames) { drawStreetNumbersOnMinimap(ctx, x, y, size, tileType, col, row, drawnNames) {
if (!this.currentMap || !Array.isArray(this.currentMap.tileStreets)) return; if (!this.currentMap || !Array.isArray(this.currentMap.tileStreets)) return;
@@ -749,7 +834,7 @@ export default {
// Game Loop stoppen // Game Loop stoppen
if (this.gameLoop) { if (this.gameLoop) {
cancelAnimationFrame(this.gameLoop); clearTimeout(this.gameLoop);
this.gameLoop = null; this.gameLoop = null;
} }
@@ -1354,20 +1439,23 @@ export default {
// 20% Wahrscheinlichkeit pro Sekunde // 20% Wahrscheinlichkeit pro Sekunde
if (!this.isPaused && Math.random() < this.carSpawnProbability) { if (!this.isPaused && Math.random() < this.carSpawnProbability) {
console.log('[🚗 GENERATION] Attempting to spawn car, current cars:', this.cars.length);
this.spawnCar(now); this.spawnCar(now);
} }
}, },
// Spawne ein neues Auto // Spawne ein neues Auto
spawnCar(now = Date.now()) { spawnCar(now = Date.now()) {
// Begrenze die Anzahl der Autos // Begrenze die Anzahl der Autos (TEST: nur 1 Auto)
if (this.cars.length >= 8) { if (this.cars.length >= 1) {
console.log('[🚗 SPAWN] Max cars reached:', this.cars.length);
return; return;
} }
// Spawne Auto auf einer befahrbaren Position // Spawne Auto auf einer befahrbaren Position
const spawnPosition = this.getRandomCarSpawnPosition(); const spawnPosition = this.getRandomCarSpawnPosition();
if (!spawnPosition) { if (!spawnPosition) {
console.log('[🚗 SPAWN] No valid spawn position found');
return; // Keine gültige Spawn-Position gefunden return; // Keine gültige Spawn-Position gefunden
} }
@@ -1390,39 +1478,44 @@ export default {
targetLane: null // Zielspur basierend auf Zielrichtung targetLane: null // Zielspur basierend auf Zielrichtung
}; };
console.log(`[🚗 CREATE] New car created - ID: ${car.id}, Position: x=${car.x.toFixed(2)}, y=${car.y.toFixed(2)}, direction=${car.direction}, angle=${car.angle.toFixed(2)}, speed=${car.speed.toFixed(2)}`);
this.cars.push(car); this.cars.push(car);
console.log(`[🚗 CREATE] Added car with ID: ${car.id}, Total cars now: ${this.cars.length}`);
console.log(`[🚗 CREATE] Car details: x=${car.x.toFixed(2)}, y=${car.y.toFixed(2)}, direction=${car.direction}, speed=${car.speed.toFixed(2)}`);
}, },
// Ermittelt erlaubte Spawn-Richtungen basierend auf dem aktuellen Tile-Typ // Ermittelt erlaubte Spawn-Richtungen basierend auf dem aktuellen Tile-Typ
// WICHTIG: Diese Methode gibt die BEWEGUNGSRICHTUNGEN zurück, nicht die Spawn-Seiten!
getAllowedSpawnDirections() { getAllowedSpawnDirections() {
const tileType = this.getCurrentTileType(); const tileType = this.getCurrentTileType();
// Basierend auf der allowedDirections Logik aus AdminTaxiToolsView.vue // Basierend auf der tile-Struktur und welche Seiten offen sind
switch (tileType) { switch (tileType) {
case 'cornertopleft': case 'cornertopleft':
return ['left', 'up']; // Kann von links und oben spawnen return ['left', 'up']; // Bewegt sich nach links (von rechts spawnen) und nach oben (von unten spawnen)
case 'cornertopright': case 'cornertopright':
return ['right', 'up']; // Kann von rechts und oben spawnen return ['right', 'up']; // Bewegt sich nach rechts (von links spawnen) und nach oben (von unten spawnen)
case 'cornerbottomleft': case 'cornerbottomleft':
return ['left', 'down']; // Kann von links und unten spawnen return ['left', 'down']; // Bewegt sich nach links (von rechts spawnen) und nach unten (von oben spawnen)
case 'cornerbottomright': case 'cornerbottomright':
return ['right', 'down']; // Kann von rechts und unten spawnen return ['left', 'up']; // Bewegt sich nach links (von rechts spawnen) und nach oben (von unten spawnen)
case 'horizontal': case 'horizontal':
case 'fuelhorizontal': case 'fuelhorizontal':
return ['left', 'right']; // Kann von links und rechts spawnen return ['left', 'right']; // Bewegt sich nach links (von rechts spawnen) und nach rechts (von links spawnen)
case 'vertical': case 'vertical':
case 'fuelvertical': case 'fuelvertical':
return ['up', 'down']; // Kann von oben und unten spawnen return ['up', 'down']; // Bewegt sich nach oben (von unten spawnen) und nach unten (von oben spawnen)
case 'cross': case 'cross':
return ['left', 'right', 'up', 'down']; // Kann von allen Seiten spawnen return ['left', 'right', 'up', 'down']; // Kann von allen Seiten spawnen
case 'tup': case 'tup':
return ['up', 'left', 'right']; // Kann von oben, links und rechts spawnen return ['up', 'left', 'right']; // Bewegt sich nach oben, links und rechts
case 'tdown': case 'tdown':
return ['down', 'left', 'right']; // Kann von unten, links und rechts spawnen return ['down', 'left', 'right']; // Bewegt sich nach unten, links und rechts
case 'tleft': case 'tleft':
return ['left', 'up', 'down']; // Kann von links, oben und unten spawnen return ['left', 'up', 'down']; // Bewegt sich nach links, oben und unten
case 'tright': case 'tright':
return ['right', 'up', 'down']; // Kann von rechts, oben und unten spawnen return ['right', 'up', 'down']; // Bewegt sich nach rechts, oben und unten
default: default:
return []; // Keine erlaubten Spawn-Richtungen return []; // Keine erlaubten Spawn-Richtungen
} }
@@ -1431,87 +1524,97 @@ export default {
// Finde eine zufällige befahrbare Spawn-Position für ein Auto // Finde eine zufällige befahrbare Spawn-Position für ein Auto
getRandomCarSpawnPosition() { getRandomCarSpawnPosition() {
if (!this.currentMap || !this.currentMap.tiles) { if (!this.currentMap || !this.currentMap.tiles) {
console.log('[🚗 SPAWN] No current map or tiles');
return null; return null;
} }
const tileSize = this.tiles.size; const tileSize = this.tiles.size;
const tileType = this.getCurrentTileType();
// Definiere die erlaubten Spawn-Positionen (relativ 0-1) // Erlaubte Spawn-Richtungen für den aktuellen Tile-Typ ermitteln
// Strengere Spur-Bänder: links/oben bis 0.45, rechts/unten ab 0.55 const allowedDirections = this.getAllowedSpawnDirections();
const spawnPositions = [
// Links spawnen, nach rechts fahren, auf der rechten Straßenseite (y=0.55-0.625)
{ relativeX: 0, relativeY: 0.55 + Math.random() * 0.075, angle: 0, direction: 'right' },
// Rechts spawnen, nach links fahren, auf der linken Straßenseite (y=0.375-0.45)
{ relativeX: 1, relativeY: 0.375 + Math.random() * 0.075, angle: Math.PI, direction: 'left' },
// Oben spawnen, nach unten fahren, auf der linken Straßenseite (x=0.375-0.45)
{ relativeX: 0.375 + Math.random() * 0.075, relativeY: 0, angle: Math.PI / 2, direction: 'down' },
// Unten spawnen, nach oben fahren, auf der rechten Straßenseite (x=0.55-0.625)
// Direkt im befahrbaren Bereich starten (relY ≈ 0.78)
{ relativeX: 0.55 + Math.random() * 0.075, relativeY: 1, angle: -Math.PI / 2, direction: 'up' }
];
// Wähle eine zufällige Spawn-Position console.log('[🚗 SPAWN] Tile type:', tileType, 'Allowed directions:', allowedDirections);
const spawnPos = spawnPositions[Math.floor(Math.random() * spawnPositions.length)]; console.log('[🚗 SPAWN] Tile size:', tileSize);
// Debug: Aktueller Tile-Typ und Wahl if (allowedDirections.length === 0) {
try { console.log('[🚗 SPAWN] No allowed spawn directions');
const tileRaw = this.getCurrentTileType(); return null; // Keine erlaubten Spawn-Richtungen
const streetType = this.mapTileTypeToStreetCoordinates(tileRaw); }
if (spawnPos.direction === 'up') {
console.log('[Cars][debug] fromBottom on tile', { tileRaw, streetType }); // Definiere die Spawn-Positionen (relativ 0-1) basierend auf der Richtung
} // Autos müssen von außerhalb des Tiles spawnen, um dann hineinzufahren
} catch (_) {} // Korrekte Fahrspuren: links (y=0.55-0.625), rechts (y=0.375-0.45), oben (x=0.375-0.45), unten (x=0.55-0.625)
// WICHTIG: direction ist die Bewegungsrichtung, nicht die Spawn-Seite!
// Auto-Dimensionen
const carWidth = 60;
const carHeight = 50;
// Spawn-Positionen basierend auf den tatsächlichen Straßenlinien in streetCoordinates.json
// cornerBottomRight hat vertikale Linien bei x=0.375 (187.5px) und x=0.625 (312.5px)
// cornerBottomRight hat horizontale Linien bei y=0.375 (187.5px) und y=0.625 (312.5px)
// Spur-Bänder: innerer Rand (≈ 15px bei 500px → 0.03 rel)
const laneMargin = 0.03;
const spawnPositionsMap = {
'right': { relativeX: -0.02, pickRelY: () => 0.5 + laneMargin + Math.random() * (0.625 - 0.5 - 2 * laneMargin), angle: 0, direction: 'right', laneAxis: 'H' },
'left': { relativeX: 1.02, pickRelY: () => 0.375 + laneMargin + Math.random() * (0.5 - 0.375 - 2 * laneMargin), angle: Math.PI, direction: 'left', laneAxis: 'H' },
'down': { pickRelX: () => 0.375 + laneMargin + Math.random() * (0.5 - 0.375 - 2 * laneMargin), relativeY: -0.02, angle: Math.PI / 2, direction: 'down', laneAxis: 'V' },
'up': { pickRelX: () => 0.5 + laneMargin + Math.random() * (0.625 - 0.5 - 2 * laneMargin), relativeY: 1.02, angle: -Math.PI / 2, direction: 'up', laneAxis: 'V' }
};
// Wähle eine zufällige erlaubte Richtung
const randomDirection = allowedDirections[Math.floor(Math.random() * allowedDirections.length)];
const spawnPos = spawnPositionsMap[randomDirection];
if (!spawnPos) {
return null;
}
console.log('[🚗 SPAWN] Selected direction:', randomDirection);
console.log('[🚗 SPAWN] Spawn position data:', spawnPos);
// Konvertiere relative Koordinaten zu absoluten Pixeln // Konvertiere relative Koordinaten zu absoluten Pixeln
const x = spawnPos.relativeX * tileSize; let x = (spawnPos.relativeX != null ? spawnPos.relativeX : (spawnPos.pickRelX ? spawnPos.pickRelX() : 0.5)) * tileSize;
const y = spawnPos.relativeY * tileSize; let y = (spawnPos.relativeY != null ? spawnPos.relativeY : (spawnPos.pickRelY ? spawnPos.pickRelY() : 0.5)) * tileSize;
// Intensive Debug-Ausgaben für dir==='up' console.log('[🚗 SPAWN] Before adjustment - x:', x.toFixed(2), 'y:', y.toFixed(2));
if (spawnPos.direction === 'up') {
console.log('[Cars][debug] fromBottom candidate', { // Passe Koordinaten an: Die Spawn-Position ist die Straßenlinie, aber wir brauchen die linke obere Ecke des Autos
relX: Number(spawnPos.relativeX.toFixed(3)), // Das Auto muss auf der Linie zentriert sein
relY: Number(spawnPos.relativeY.toFixed(3)), switch (randomDirection) {
x: Math.round(x), y: Math.round(y) case 'left':
}); case 'right':
// Horizontale Bewegung: Auto muss auf Y-Linie zentriert sein
y = y - carHeight / 2; // Zentriere Auto auf horizontaler Linie
if (x < 0) x = -carWidth; // Links außerhalb
if (x > tileSize) x = tileSize; // Rechts außerhalb
break;
case 'up':
case 'down':
// Vertikale Bewegung: Auto muss auf X-Linie zentriert sein
x = x - carWidth / 2; // Zentriere Auto auf vertikaler Linie
if (y < 0) y = -carHeight; // Oben außerhalb
if (y > tileSize) y = tileSize; // Unten außerhalb
break;
} }
// Prüfe ob Position befahrbar ist (für Autos: Rand entspannt) console.log('[🚗 SPAWN] After adjustment - x:', x.toFixed(2), 'y:', y.toFixed(2));
if (this.isPositionDriveableForCars(x, y, spawnPos.direction)) { console.log('[🚗 SPAWN] Direction:', randomDirection, 'Absolute:', { x: x.toFixed(2), y: y.toFixed(2) });
return {
x: x,
y: y,
angle: spawnPos.angle,
direction: spawnPos.direction
};
}
// Fallback: Versuche andere Positionen // laneCoord speichert die Zielspur (Mitte) auf der Querachse
for (let i = 0; i < spawnPositions.length; i++) { // Verwende die bereits berechneten absoluten x/y, um exakt dieselbe Spur zu halten
const pos = spawnPositions[i]; const laneCoord = spawnPos.laneAxis === 'H'
const testX = pos.relativeX * tileSize; ? (y + carHeight / 2) // Y-Mitte
const testY = pos.relativeY * tileSize; : (x + carWidth / 2); // X-Mitte
if (pos.direction === 'up') {
console.log('[Cars][debug] fromBottom fallback-candidate', {
relX: Number(pos.relativeX.toFixed(3)),
relY: Number(pos.relativeY.toFixed(3)),
x: Math.round(testX), y: Math.round(testY), ok: this.isPositionDriveable(testX, testY)
});
}
if (this.isPositionDriveableForCars(testX, testY, pos.direction)) { return {
return { x: x,
x: testX, y: y,
y: testY, angle: spawnPos.angle,
angle: pos.angle, direction: spawnPos.direction,
direction: pos.direction laneAxis: spawnPos.laneAxis,
}; laneCoord: laneCoord
} };
}
if (spawnPos.direction === 'up') {
console.warn('[Cars][debug] fromBottom: no driveable spawn found');
}
return null; // Keine gültige Position gefunden
}, },
// Prüfe ob eine Position befahrbar ist // Prüfe ob eine Position befahrbar ist
@@ -1526,21 +1629,83 @@ export default {
return streetCoordinates.isPointDriveable(relativeX, relativeY, streetTileType, 1); return streetCoordinates.isPointDriveable(relativeX, relativeY, streetTileType, 1);
}, },
// Fahrzeuge (KI) dürfen in den Screen hineingleiten: Ränder als befahrbar behandeln // Prüfe ob Auto auf einer befahrbaren Straße ist (wie Taxi-Logik)
isPositionDriveableForCars(x, y, direction) { isCarOnRoad(car) {
// Toleranz: 1px über/unter dem Canvasrand als befahrbar werten const centerX = car.x + car.width / 2;
if (x < 0 || y < 0 || x > this.tiles.size || y > this.tiles.size) { const centerY = car.y + car.height / 2;
// Wenn wir am Rand nach innen fahren, erlauben
// up: y nimmt ab; down: y nimmt zu; right: x nimmt zu; left: x nimmt ab // Außerhalb des Tiles? Dann ist es okay (Autos dürfen rein/raus fahren)
if (direction === 'up' && y >= -1) return true; if (centerX < 0 || centerY < 0 || centerX >= this.tiles.size || centerY >= this.tiles.size) {
if (direction === 'down' && y <= this.tiles.size + 1) return true; return true;
if (direction === 'right' && x <= this.tiles.size + 1) return true; }
if (direction === 'left' && x >= -1) return true;
// weit außerhalb: nicht befahrbar // Innerhalb des Tiles: Prüfe mehrere Punkte um das Auto herum (nicht nur Zentrum)
// Das Auto ist 60x50, also prüfe auch Ecken
const points = [
{ x: centerX, y: centerY, name: 'center' }, // Zentrum
{ x: car.x + 10, y: car.y + 10, name: 'top-left' }, // Linke obere Ecke (mit Padding)
{ x: car.x + car.width - 10, y: car.y + 10, name: 'top-right' }, // Rechte obere Ecke
{ x: car.x + 10, y: car.y + car.height - 10, name: 'bottom-left' }, // Linke untere Ecke
{ x: car.x + car.width - 10, y: car.y + car.height - 10, name: 'bottom-right' } // Rechte untere Ecke
];
// Auto ist auf Straße, wenn mindestens einer der Punkte auf der Straße ist
let onRoadPoints = [];
for (const point of points) {
if (this.isPositionDriveable(point.x, point.y)) {
onRoadPoints.push(point.name);
}
}
if (onRoadPoints.length > 0) {
console.log(`[🚗 ON_ROAD] Car at x=${car.x.toFixed(2)}, y=${car.y.toFixed(2)} - ON ROAD (points: ${onRoadPoints.join(', ')})`);
return true;
} else {
console.log(`[🚗 OFF_ROAD] Car at x=${car.x.toFixed(2)}, y=${car.y.toFixed(2)}, centerX=${centerX.toFixed(2)}, centerY=${centerY.toFixed(2)} - OFF ROAD (no points on road)`);
return false; return false;
} }
// Innerhalb des Tiles: normale Prüfung },
return this.isPositionDriveable(x, y);
// Prüfe ob Auto nahe an einer Kreuzung/Abzweigung ist und Richtung ändern sollte
shouldCarChangeDirection(car) {
const centerX = car.x + car.width / 2;
const centerY = car.y + car.height / 2;
// Zufälliger Kollisionsbereich: 5-20 Pixel vor der tatsächlichen Grenze
const randomOffset = 5 + Math.random() * 15;
// Hole erlaubte Richtungen für aktuelles Tile
const allowedDirections = this.getAllowedSpawnDirections();
// Prüfe je nach aktueller Richtung, ob eine Änderung möglich ist
switch (car.direction) {
case 'up':
// Wenn nahe am oberen Rand und andere Richtungen möglich sind
if (centerY <= randomOffset && allowedDirections.length > 1) {
return allowedDirections.filter(d => d !== 'down' && d !== 'up'); // Nicht zurück, nicht weitergerade wenn am Rand
}
break;
case 'down':
// Wenn nahe am unteren Rand
if (centerY >= this.tiles.size - randomOffset && allowedDirections.length > 1) {
return allowedDirections.filter(d => d !== 'up' && d !== 'down');
}
break;
case 'left':
// Wenn nahe am linken Rand
if (centerX <= randomOffset && allowedDirections.length > 1) {
return allowedDirections.filter(d => d !== 'right' && d !== 'left');
}
break;
case 'right':
// Wenn nahe am rechten Rand
if (centerX >= this.tiles.size - randomOffset && allowedDirections.length > 1) {
return allowedDirections.filter(d => d !== 'left' && d !== 'right');
}
break;
}
return null; // Keine Richtungsänderung nötig
}, },
@@ -1563,248 +1728,118 @@ export default {
updateCars() { updateCars() {
const now = Date.now(); const now = Date.now();
const maxAge = 45000; // 45 Sekunden const maxAge = 45000; // 45 Sekunden
const initialCarCount = this.cars.length;
console.log(`[🚗 UPDATE] Starting with ${initialCarCount} cars`);
this.cars = this.cars.filter((car, index) => {
console.log(`[🚗 UPDATE] Processing car ${index}: x=${car.x.toFixed(2)}, y=${car.y.toFixed(2)}, age=${(now - car.createdAt)}ms`);
this.cars = this.cars.filter(car => {
// Entferne alte Autos // Entferne alte Autos
if (now - car.createdAt > maxAge) { // Bewege das Auto
return false;
}
// Bewege das Auto nur wenn es auf einer befahrbaren Position ist
this.updateCarMovement(car); this.updateCarMovement(car);
// Entferne Autos, die außerhalb des Bildschirms sind // Entferne Autos mit speed=0 (off-road)
if (car.x < -50 || car.x > 550 || car.y < -50 || car.y > 550) { if (car.speed === 0) {
console.log(`[🚗 UPDATE] Removing car ${index} - speed=0 (off-road)`);
return false; return false;
} }
// Entferne Autos, die komplett außerhalb des sichtbaren Bereichs sind
// Berücksichtige die Auto-Größe: Auto wird erst entfernt, wenn es komplett außerhalb ist
const tileSize = this.tiles.size; // 500px
const carWidth = car.width || 60;
const carHeight = car.height || 50;
// Auto ist komplett außerhalb wenn:
// - Rechte Kante links vom Canvas: x + carWidth < 0
// - Linke Kante rechts vom Canvas: x > tileSize
// - Untere Kante über dem Canvas: y + carHeight < 0
// - Obere Kante unter dem Canvas: y > tileSize
if (car.x + carWidth < 0 || car.x > tileSize ||
car.y + carHeight < 0 || car.y > tileSize) {
console.log(`[🚗 UPDATE] Removing car ${index} - completely out of bounds (x=${car.x.toFixed(2)}, y=${car.y.toFixed(2)})`);
return false;
}
console.log(`[🚗 UPDATE] Keeping car ${index}`);
return true; return true;
}); });
console.log(`[🚗 UPDATE] Finished with ${this.cars.length} cars (removed ${initialCarCount - this.cars.length})`);
}, },
// Aktualisiere die Bewegung eines einzelnen Autos // Aktualisiere die Bewegung eines einzelnen Autos (vereinfachte Taxi-Logik)
updateCarMovement(car) { updateCarMovement(car) {
// Speichere aktuelle Position console.log(`[🚗 MOVE] Car ID: ${car.id} - Current: x=${car.x.toFixed(2)}, y=${car.y.toFixed(2)}, direction=${car.direction}`);
car.lastPosition = { x: car.x, y: car.y };
// Setze Zielrichtung beim ersten Betreten des Tiles // 1) An Abbiegepunkt? Dann (ggf.) vorgegebene Zielrichtung übernehmen
if (!car.targetDirection) { const forcedDir = this.shouldCarTurnAtIntersection(car);
car.targetDirection = this.getRandomTargetDirection(car.direction); if (forcedDir) {
car.targetLane = this.getTargetLane(car.targetDirection); const tileType = this.getCurrentTileType();
} // forcedDir hat Vorrang NICHT durch allowedDirections einschränken
let preferred = forcedDir;
// Prüfe ob Auto an einer Kreuzung ist und zur Zielrichtung abbiegen soll if (preferred && preferred !== car.direction) {
if (this.shouldCarTurnAtIntersection(car) && !car.hasTurnedAtIntersection) { console.log(`[🚗 MOVE] Car ID: ${car.id} - Turning on ${tileType} from ${car.direction} to ${preferred}`);
this.turnCarToTarget(car); // Beim Abbiegen: neue Spur bestimmen
car.hasTurnedAtIntersection = true; car.direction = preferred;
} if (preferred === 'left' || preferred === 'right') {
car.laneAxis = 'H';
// Berechne neue Position const laneMargin = 0.03;
const newX = car.x + Math.cos(car.angle) * car.speed; const rel = preferred === 'left'
const newY = car.y + Math.sin(car.angle) * car.speed; ? (0.375 + laneMargin + Math.random() * (0.5 - 0.375 - 2 * laneMargin))
: (0.5 + laneMargin + Math.random() * (0.625 - 0.5 - 2 * laneMargin));
// Prüfe ob neue Position befahrbar ist (Autos dürfen an Rändern hinein gleiten) car.laneCoord = rel * this.tiles.size; // Y-Spur
if (this.isPositionDriveableForCars(newX, newY, car.direction)) {
// Position ist befahrbar - bewege Auto
car.x = newX;
car.y = newY;
} else {
// Vorzugsweise zur geplanten Zielrichtung abbiegen (z. B. Kurve unten->rechts)
if (car.targetDirection) {
const prevAngle = car.angle;
const prevDir = car.direction;
this.turnCarToTarget(car);
const tryX = car.x + Math.cos(car.angle) * car.speed;
const tryY = car.y + Math.sin(car.angle) * car.speed;
if (this.isPositionDriveableForCars(tryX, tryY, car.direction)) {
car.x = tryX;
car.y = tryY;
} else { } else {
// Rückgängig machen, falls Zielrichtung auch nicht geht car.laneAxis = 'V';
car.angle = prevAngle; const laneMargin = 0.03;
car.direction = prevDir; const rel = preferred === 'up'
// Fallback: alternative Richtung suchen (ohne zurückzufahren) ? (0.5 + laneMargin + Math.random() * (0.625 - 0.5 - 2 * laneMargin))
this.adjustCarDirection(car); : (0.375 + laneMargin + Math.random() * (0.5 - 0.375 - 2 * laneMargin));
car.laneCoord = rel * this.tiles.size; // X-Spur
} }
} else { switch (preferred) {
// Position ist nicht befahrbar - suche alternative Richtung (ohne zurückzufahren) case 'up': car.angle = -Math.PI / 2; break;
this.adjustCarDirection(car); case 'down': car.angle = Math.PI / 2; break;
} case 'left': car.angle = Math.PI; break;
} case 'right':car.angle = 0; break;
},
// Bestimme zufällige Zielrichtung basierend auf Tile-Art und aktueller Richtung
getRandomTargetDirection(currentDirection) {
const tileType = this.getCurrentTileType();
const possibleDirections = [];
// Bestimme mögliche Richtungen basierend auf Tile-Typ
switch (tileType) {
case 'cornerTopLeft':
// L-förmige Kurve: oben-links
if (currentDirection === 'right') possibleDirections.push('up');
else if (currentDirection === 'down') possibleDirections.push('left');
break;
case 'cornerTopRight':
// L-förmige Kurve: oben-rechts
if (currentDirection === 'left') possibleDirections.push('up');
else if (currentDirection === 'down') possibleDirections.push('right');
break;
case 'cornerBottomLeft':
// L-förmige Kurve: unten-links
if (currentDirection === 'right') possibleDirections.push('down');
else if (currentDirection === 'up') possibleDirections.push('left');
break;
case 'cornerBottomRight':
// L-förmige Kurve: unten-rechts
if (currentDirection === 'left') possibleDirections.push('down');
else if (currentDirection === 'up') possibleDirections.push('right');
break;
case 'crossing':
// Kreuzung: alle Richtungen außer zurück
switch (currentDirection) {
case 'right': possibleDirections.push('up', 'down'); break;
case 'left': possibleDirections.push('up', 'down'); break;
case 'up': possibleDirections.push('left', 'right'); break;
case 'down': possibleDirections.push('left', 'right'); break;
} }
break;
default:
// Für andere Tile-Typen: nur geradeaus
possibleDirections.push(currentDirection);
break;
}
// Fallback: wenn keine Richtungen gefunden, geradeaus
if (possibleDirections.length === 0) {
possibleDirections.push(currentDirection);
}
return possibleDirections[Math.floor(Math.random() * possibleDirections.length)];
},
// Bestimme Zielspur basierend auf Zielrichtung
getTargetLane(targetDirection) {
switch (targetDirection) {
case 'left':
return { y: 0.375 + Math.random() * 0.125 }; // y=0.375-0.5
case 'right':
return { y: 0.5 + Math.random() * 0.125 }; // y=0.5-0.625
case 'down':
return { x: 0.375 + Math.random() * 0.125 }; // x=0.375-0.5
case 'up':
return { x: 0.5 + Math.random() * 0.125 }; // x=0.5-0.625
}
},
// Prüfe, ob am aktuellen Tile der Abbiegepunkt erreicht ist
shouldCarTurnAtIntersection(car) {
const rx = car.x / this.tiles.size;
const ry = car.y / this.tiles.size;
const tileType = this.mapTileTypeToStreetCoordinates(this.getCurrentTileType());
// Engeres Toleranzfenster: 0.495..0.505
const nearYCenter = ry > 0.495 && ry < 0.505; // vertikale Mitte erreicht
const nearXCenter = rx > 0.495 && rx < 0.505; // horizontale Mitte erreicht
switch (tileType) {
case 'cornerBottomRight':
// Straße: unten -> rechts. Von unten kommend (up) am Y-Zentrum nach rechts abbiegen.
if (car.direction === 'up') return nearYCenter;
// Von links kommend (left) am X-Zentrum nach unten abbiegen.
if (car.direction === 'left') return nearXCenter;
return false;
case 'cornerBottomLeft':
// Straße: unten -> links
if (car.direction === 'up') return nearYCenter; // dann links abbiegen
if (car.direction === 'right') return nearXCenter; // dann runter abbiegen
return false;
case 'cornerTopRight':
// Straße: oben -> rechts
if (car.direction === 'down') return nearYCenter; // dann rechts
if (car.direction === 'left') return nearXCenter; // dann hoch
return false;
case 'cornerTopLeft':
// Straße: oben -> links
if (car.direction === 'down') return nearYCenter; // dann links
if (car.direction === 'right') return nearXCenter; // dann hoch
return false;
default:
// Für Kreuzungen etc. beide Zentren oder explizit angeforderte Zielrichtung
return nearXCenter && nearYCenter;
}
},
// Auto zur Zielrichtung abbiegen
turnCarToTarget(car) {
const targetDirection = car.targetDirection;
switch (targetDirection) {
case 'left':
car.angle = Math.PI;
car.direction = 'left';
break;
case 'right':
car.angle = 0;
car.direction = 'right';
break;
case 'down':
car.angle = Math.PI / 2;
car.direction = 'down';
break;
case 'up':
car.angle = -Math.PI / 2;
car.direction = 'up';
break;
}
},
// Passe die Richtung eines Autos an, wenn es nicht weiterfahren kann
adjustCarDirection(car) {
const directions = [
{ angle: 0, name: 'right' },
{ angle: Math.PI, name: 'left' },
{ angle: Math.PI / 2, name: 'down' },
{ angle: -Math.PI / 2, name: 'up' }
];
// Gegengerichtete Richtung (zurückfahren) ausschließen
const opposite = {
right: 'left',
left: 'right',
up: 'down',
down: 'up'
}[car.direction];
// Versuche verschiedene Richtungen, zuerst die geplante Zielrichtung, dann andere (ohne zurück)
const preferredOrder = [];
if (car.targetDirection && car.targetDirection !== opposite) preferredOrder.push(car.targetDirection);
directions.forEach(d => {
if (d.name !== opposite && d.name !== (preferredOrder[0] || '')) preferredOrder.push(d.name);
});
for (let i = 0; i < preferredOrder.length; i++) {
const name = preferredOrder[i];
const angle = name === 'right' ? 0 : (name === 'left' ? Math.PI : (name === 'down' ? Math.PI/2 : -Math.PI/2));
const testX = car.x + Math.cos(angle) * car.speed;
const testY = car.y + Math.sin(angle) * car.speed;
if (this.isPositionDriveableForCars(testX, testY, name)) {
car.angle = angle;
car.direction = name;
return;
} }
} }
// Keine befahrbare Richtung gefunden - bewege zurück zur letzten Position // 2) Bewege Auto in die aktuelle Richtung und halte es auf seiner Spur
car.x = car.lastPosition.x; let newX = car.x;
car.y = car.lastPosition.y; let newY = car.y;
},
switch (car.direction) {
case 'up': newY = car.y - car.speed; break;
case 'down': newY = car.y + car.speed; break;
case 'left': newX = car.x - car.speed; break;
case 'right': newX = car.x + car.speed; break;
}
// Querachse fixieren
if (car.laneAxis === 'H') {
const centerY = car.laneCoord;
newY = centerY - car.height / 2;
} else if (car.laneAxis === 'V') {
const centerX = car.laneCoord;
newX = centerX - car.width / 2;
}
// 3) Position übernehmen
car.x = newX;
car.y = newY;
console.log(`[🚗 MOVE] Car ID: ${car.id} - Moved to: x=${car.x.toFixed(2)}, y=${car.y.toFixed(2)}`);
// 4) Straße prüfen bleibt es auf befahrbarer Fläche?
if (!this.isCarOnRoad(car)) {
console.log(`[🚗 MOVE] Car ID: ${car.id} - OFF ROAD! Stopping car.`);
car.speed = 0;
}
},
getPassengerTimeLeft(passenger) { getPassengerTimeLeft(passenger) {
const now = Date.now(); const now = Date.now();
const age = now - passenger.createdAt; const age = now - passenger.createdAt;
@@ -1917,15 +1952,20 @@ export default {
startGame() { startGame() {
this.gameRunning = true; this.gameRunning = true;
// Lösche alle alten Autos (falls welche existieren)
this.cars = [];
console.log('[🚗 GAME] Cleared all cars on game start');
// Setze Spielstart-Zeit // Setze Spielstart-Zeit
if (!this.gameStartTime) { if (!this.gameStartTime) {
this.gameStartTime = Date.now(); this.gameStartTime = Date.now();
} }
// Stoppe bestehende Game-Loop falls vorhanden // Stoppe bestehende Game-Loop falls vorhanden
if (this.gameLoop) { if (this.gameLoop) {
cancelAnimationFrame(this.gameLoop); clearTimeout(this.gameLoop);
} }
this.gameLoop = requestAnimationFrame(this.update); this.gameLoop = setTimeout(this.update, 62);
}, },
update() { update() {
@@ -1934,6 +1974,13 @@ export default {
return; return;
} }
// Debug: Log every 60 frames (about once per second)
if (!this._frameCount) this._frameCount = 0;
this._frameCount++;
if (this._frameCount % 60 === 0) {
console.log(`[🔄 UPDATE] Frame ${this._frameCount}, gameRunning: ${this.gameRunning}, isPaused: ${this.isPaused}, cars: ${this.cars.length}`);
}
// Ampelschaltung tick // Ampelschaltung tick
this.updateTrafficLights(); this.updateTrafficLights();
@@ -1971,7 +2018,8 @@ export default {
this.lastMinimapDraw = nowTs; this.lastMinimapDraw = nowTs;
} }
this.gameLoop = requestAnimationFrame(this.update); // Update-Rate schneller: ~16x pro Sekunde (62ms)
this.gameLoop = setTimeout(this.update, 62);
}, },
updateTaxi() { updateTaxi() {
@@ -2604,7 +2652,7 @@ export default {
// Game Loop stoppen // Game Loop stoppen
if (this.gameLoop) { if (this.gameLoop) {
cancelAnimationFrame(this.gameLoop); clearTimeout(this.gameLoop);
this.gameLoop = null; this.gameLoop = null;
} }
@@ -2668,8 +2716,13 @@ export default {
// Game Loop sicher neu starten (unabhängig vom vorherigen Zustand) // Game Loop sicher neu starten (unabhängig vom vorherigen Zustand)
this.gameRunning = true; this.gameRunning = true;
try { cancelAnimationFrame(this.gameLoop); } catch (_) {}
this.gameLoop = requestAnimationFrame(this.update); // Lösche alle alten Autos auch hier (falls welche existieren)
this.cars = [];
console.log('[🚗 GAME] Cleared all cars after dialog close');
try { clearTimeout(this.gameLoop); } catch (_) {}
this.gameLoop = setTimeout(this.update, 62);
// Taxi bleibt auf dem aktuellen Tile, mittig platzieren // Taxi bleibt auf dem aktuellen Tile, mittig platzieren
this.taxi.speed = 0; this.taxi.speed = 0;
@@ -3321,15 +3374,46 @@ export default {
// Zeichne alle Autos // Zeichne alle Autos
drawCars() { drawCars() {
this.cars.forEach(car => { console.log(`[🚗 DRAW] Drawing ${this.cars.length} cars`);
this.ctx.save(); this.cars.forEach((car, index) => {
this.ctx.translate(car.x + car.width/2, car.y + car.height/2); const centerX = car.x + car.width/2;
const centerY = car.y + car.height/2;
// Korrigiere die Rotation um 90° + weitere 180° - das Auto-Bild zeigt bereits in die richtige Richtung // Auto ist sichtbar wenn IRGENDEIN Teil im Canvas ist (nicht nur das Zentrum)
this.ctx.rotate(car.angle + Math.PI / 2 + Math.PI); const isVisible = !(car.x + car.width < 0 || car.x > 500 || car.y + car.height < 0 || car.y > 500);
console.log(`[🚗 DRAW] Car ${index}: x=${car.x.toFixed(2)}, y=${car.y.toFixed(2)}, centerX=${centerX.toFixed(2)}, centerY=${centerY.toFixed(2)}, direction=${car.direction}, speed=${car.speed.toFixed(2)}, VISIBLE=${isVisible}`);
this.ctx.save();
// Auto-Position als linke obere Ecke (car.x, car.y ist die linke obere Ecke)
// Transformiere zum Zentrum des Autos für Rotation
this.ctx.translate(centerX, centerY);
// Rotiere basierend auf der Fahrtrichtung (nicht angle)
let rotationAngle = 0;
switch (car.direction) {
case 'up':
rotationAngle = -Math.PI / 2;
break;
case 'down':
rotationAngle = Math.PI / 2;
break;
case 'left':
rotationAngle = Math.PI;
break;
case 'right':
rotationAngle = 0;
break;
}
// Auto-Bild korrigieren (90° + 180° für korrekte Ausrichtung)
const finalAngle = rotationAngle + Math.PI / 2 + Math.PI;
this.ctx.rotate(finalAngle);
if (this.carImage) { if (this.carImage) {
// Zeichne Auto-Bild console.log(`[🚗 DRAW] Drawing car ${index} with image at x=${car.x.toFixed(2)}, y=${car.y.toFixed(2)}`);
// Zeichne Auto-Bild zentriert um den Transformationspunkt
this.ctx.drawImage( this.ctx.drawImage(
this.carImage, this.carImage,
-car.width/2, -car.width/2,
@@ -3338,6 +3422,7 @@ export default {
car.height car.height
); );
} else { } else {
console.log(`[🚗 DRAW] Drawing car ${index} with fallback rectangle at x=${car.x.toFixed(2)}, y=${car.y.toFixed(2)}`);
// Fallback: Zeichne farbiges Rechteck wenn Bild nicht geladen // Fallback: Zeichne farbiges Rechteck wenn Bild nicht geladen
this.ctx.fillStyle = car.color; this.ctx.fillStyle = car.color;
this.ctx.fillRect(-car.width/2, -car.height/2, car.width, car.height); this.ctx.fillRect(-car.width/2, -car.height/2, car.width, car.height);
@@ -4331,7 +4416,7 @@ export default {
// Game Loop stoppen // Game Loop stoppen
if (this.gameLoop) { if (this.gameLoop) {
cancelAnimationFrame(this.gameLoop); clearTimeout(this.gameLoop);
this.gameLoop = null; this.gameLoop = null;
} }
@@ -4349,7 +4434,7 @@ export default {
// Game Loop neu starten // Game Loop neu starten
if (this.gameRunning && !this.gameLoop) { if (this.gameRunning && !this.gameLoop) {
this.gameLoop = requestAnimationFrame(this.update); this.gameLoop = setTimeout(this.update, 62);
} }
// Motor startet automatisch bei der nächsten Beschleunigung // Motor startet automatisch bei der nächsten Beschleunigung

Binary file not shown.