Erweiterung der Fahrzeug-Logik und Anpassung der Abbiege- sowie Spawn-Mechanik im Taxi-Spiel

Änderungen:
- Erhöhung des Toleranzfensters für die Abbiegeerkennung, um die Fahrzeugbewegung zu verbessern.
- Implementierung einer neuen Logik für T-Kreuzungen, die das Abbiegen von Fahrzeugen steuert.
- Anpassung der erlaubten Spawn-Richtungen, um das Spawn-Verhalten zu optimieren und das Fahren nach unten zu verhindern.
- Strikter Zwang für Fahrzeuge, die von oben kommen, um nicht geradeaus weiterzufahren.

Diese Anpassungen verbessern die Interaktion der Fahrzeuge und optimieren die Spielmechanik durch präzisere Abbiege- und Spawn-Logik.
This commit is contained in:
Torsten Schulz (local)
2025-10-20 17:30:43 +02:00
parent a542d99eb9
commit 8366567c2c

View File

@@ -467,7 +467,7 @@ export default {
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 tol = 0.03; // ~15px bei 500px Tilegröße breitere Toleranz
const nearYCenter = ry > 0.5 - tol && ry < 0.5 + tol;
const nearXCenter = rx > 0.5 - tol && rx < 0.5 + tol;
@@ -492,6 +492,29 @@ export default {
if (car.direction === 'down' && nearYCenter) return 'left';
if (car.direction === 'right' && nearXCenter) return 'up';
return null;
case 'tdown': {
// T-Kreuzung: unten, links, rechts offen; oben verboten
if (!(nearXCenter || nearYCenter)) {
// Sonderfall: Von oben kommend früher zwingen, bevor die Mitte exakt erreicht ist
if (car.direction === 'up' && ry <= 0.5 + tol) {
return Math.random() < 0.5 ? 'left' : 'right';
}
return null;
}
const choices = [];
if (car.direction === 'down') {
// Von oben kommend: NICHT weiter nach unten fahren
choices.push('left', 'right');
} else if (car.direction === 'left' || car.direction === 'right') {
// Geradeaus erlaubt (links/rechts), außerdem nach unten
choices.push(car.direction, 'down');
} else if (car.direction === 'up') {
// Von oben: nur links oder rechts, niemals weiter nach oben
choices.push('left', 'right');
}
if (choices.length === 0) return null;
return choices[Math.floor(Math.random() * choices.length)];
}
default:
// Kreuzung: bei Zentrumslinien abbiegen erlaubt Richtung später wählen
if (nearXCenter || nearYCenter) return this.getRandomTargetDirection(car.direction);
@@ -698,6 +721,10 @@ export default {
if (moved) {
this.currentTile.row = newRow;
this.currentTile.col = newCol;
// Entferne alle Autos vom alten Tile
this.cars = [];
// Nach Tile-Wechsel: eine Frame lang Rotlicht-Prüfung aussetzen
// und prev-Position auf aktuelle setzen, um false positives zu vermeiden
this.prevTaxiX = this.taxi.x;
@@ -1472,7 +1499,14 @@ export default {
lastPosition: { x: spawnPosition.x, y: spawnPosition.y }, // Letzte Position für Bewegung
hasTurnedAtIntersection: false, // Flag um mehrfaches Abbiegen zu verhindern
targetDirection: null, // Zielrichtung an der Kreuzung (wird beim Tile-Betreten festgelegt)
targetLane: null // Zielspur basierend auf Zielrichtung
targetLane: null, // Zielspur basierend auf Zielrichtung
laneAxis: spawnPosition.laneAxis,
laneCoord: spawnPosition.laneCoord,
willTurn: spawnPosition.willTurn,
turnDirection: spawnPosition.turnDirection,
turnX: spawnPosition.turnX,
turnY: spawnPosition.turnY,
turnAngle: spawnPosition.turnAngle
};
this.cars.push(car);
@@ -1486,13 +1520,13 @@ export default {
// Basierend auf der tile-Struktur und welche Seiten offen sind
switch (tileType) {
case 'cornertopleft':
return ['left', 'up']; // Bewegt sich nach links (von rechts spawnen) und nach oben (von unten spawnen)
return ['right', 'down']; // Kommt von links (bewegt sich nach rechts) und von oben (bewegt sich nach unten)
case 'cornertopright':
return ['right', 'up']; // Bewegt sich nach rechts (von links spawnen) und nach oben (von unten spawnen)
return ['left', 'down']; // Kommt von rechts (bewegt sich nach links) und von oben (bewegt sich nach unten)
case 'cornerbottomleft':
return ['left', 'down']; // Bewegt sich nach links (von rechts spawnen) und nach unten (von oben spawnen)
return ['right', 'up']; // Kommt von links (bewegt sich nach rechts) und von unten (bewegt sich nach oben)
case 'cornerbottomright':
return ['left', 'up']; // Bewegt sich nach links (von rechts spawnen) und nach oben (von unten spawnen)
return ['left', 'up']; // Kommt von rechts (bewegt sich nach links) und von unten (bewegt sich nach oben)
case 'horizontal':
case 'fuelhorizontal':
return ['left', 'right']; // Bewegt sich nach links (von rechts spawnen) und nach rechts (von links spawnen)
@@ -1504,16 +1538,190 @@ export default {
case 'tup':
return ['up', 'left', 'right']; // Bewegt sich nach oben, links und rechts
case 'tdown':
return ['down', 'left', 'right']; // Bewegt sich nach unten, links und rechts
return ['up', 'left', 'right']; // Fahrzeuge kommen von unten/links/rechts (nicht von oben)
case 'tleft':
return ['left', 'up', 'down']; // Bewegt sich nach links, oben und unten
return ['right', 'up', 'down']; // Kommt von links (bewegt sich nach rechts), von oben (bewegt sich nach unten) und von unten (bewegt sich nach oben)
case 'tright':
return ['right', 'up', 'down']; // Bewegt sich nach rechts, oben und unten
return ['left', 'up', 'down']; // Kommt von rechts (bewegt sich nach links), von oben (bewegt sich nach unten) und von unten (bewegt sich nach oben)
default:
return []; // Keine erlaubten Spawn-Richtungen
}
},
// Berechnet ob und wo ein Auto abbiegen wird
calculateTurnInfo(tileType, direction) {
const tileSize = this.tiles.size;
// Für tdown: Autos müssen abbiegen
if (tileType === 'tdown') {
if (direction === 'up') {
// Kommt von unten, muss nach links oder rechts abbiegen
const turnDirection = Math.random() < 0.5 ? 'left' : 'right';
const turnX = turnDirection === 'left' ? 0.375 * tileSize : 0.625 * tileSize;
const turnY = 0.5 * tileSize; // Mitte des Tiles
const turnAngle = turnDirection === 'left' ? Math.PI : 0;
return {
willTurn: true,
turnDirection: turnDirection,
turnX: turnX,
turnY: turnY,
turnAngle: turnAngle
};
}
}
// Für cornerBottomRight: Verbindet untere und rechte Seite
// Kurve von unten nach rechts oder von rechts nach unten
if (tileType === 'cornerbottomright') {
if (direction === 'left') {
// Kommt von rechts (Y-Spur ~212.5px = 0.425), biegt nach unten ab (X-Spur ~212.5px = 0.425)
const laneMargin = 0.03;
const laneCenter = 0.375 + (0.5 - 0.375) / 2; // Mitte der linken/oberen Spur = 0.4375
const turnX = laneCenter * tileSize; // X-Position nach dem Abbiegen
const turnY = laneCenter * tileSize; // Y-Position beim Abbiegen
const turnAngle = Math.PI / 2; // nach unten
return {
willTurn: true,
turnDirection: 'down',
turnX: turnX,
turnY: turnY,
turnAngle: turnAngle
};
} else if (direction === 'up') {
// Kommt von unten (X-Spur ~281px = 0.5625), biegt nach rechts ab (Y-Spur ~281px = 0.5625)
const laneCenter = 0.5 + (0.625 - 0.5) / 2; // Mitte der rechten/unteren Spur = 0.5625
const turnX = laneCenter * tileSize; // X-Position beim Abbiegen
const turnY = laneCenter * tileSize; // Y-Position nach dem Abbiegen
const turnAngle = 0; // nach rechts
return {
willTurn: true,
turnDirection: 'right',
turnX: turnX,
turnY: turnY,
turnAngle: turnAngle
};
}
}
// Für cornerTopRight: Verbindet obere und rechte Seite
// Kurve von oben nach rechts oder von rechts nach oben
if (tileType === 'cornertopright') {
if (direction === 'left') {
// Kommt von rechts (Y-Spur ~212.5px), biegt nach oben ab (X-Spur ~281px)
const laneCenterLower = 0.375 + (0.5 - 0.375) / 2; // 0.4375
const laneCenterUpper = 0.5 + (0.625 - 0.5) / 2; // 0.5625
const turnX = laneCenterUpper * tileSize; // X-Position nach dem Abbiegen
const turnY = laneCenterLower * tileSize; // Y-Position beim Abbiegen
const turnAngle = -Math.PI / 2; // nach oben
return {
willTurn: true,
turnDirection: 'up',
turnX: turnX,
turnY: turnY,
turnAngle: turnAngle
};
} else if (direction === 'down') {
// Kommt von oben (X-Spur ~281px), biegt nach rechts ab (Y-Spur ~281px)
const laneCenter = 0.5 + (0.625 - 0.5) / 2; // 0.5625
const turnX = laneCenter * tileSize;
const turnY = laneCenter * tileSize;
const turnAngle = 0; // nach rechts
return {
willTurn: true,
turnDirection: 'right',
turnX: turnX,
turnY: turnY,
turnAngle: turnAngle
};
}
}
// Für cornerTopLeft: Verbindet obere und linke Seite
// Kurve von oben nach links oder von links nach oben
if (tileType === 'cornertopleft') {
if (direction === 'right') {
// Kommt von links (Y-Spur ~281px), biegt nach oben ab (X-Spur ~281px)
const laneCenter = 0.5 + (0.625 - 0.5) / 2; // 0.5625
const turnX = laneCenter * tileSize;
const turnY = laneCenter * tileSize;
const turnAngle = -Math.PI / 2; // nach oben
return {
willTurn: true,
turnDirection: 'up',
turnX: turnX,
turnY: turnY,
turnAngle: turnAngle
};
} else if (direction === 'down') {
// Kommt von oben (X-Spur ~212.5px), biegt nach links ab (Y-Spur ~281px)
const laneCenterLower = 0.375 + (0.5 - 0.375) / 2; // 0.4375
const laneCenterUpper = 0.5 + (0.625 - 0.5) / 2; // 0.5625
const turnX = laneCenterLower * tileSize; // X-Position beim Abbiegen
const turnY = laneCenterUpper * tileSize; // Y-Position nach dem Abbiegen
const turnAngle = Math.PI; // nach links
return {
willTurn: true,
turnDirection: 'left',
turnX: turnX,
turnY: turnY,
turnAngle: turnAngle
};
}
}
// Für cornerBottomLeft: Verbindet untere und linke Seite
// Kurve von unten nach links oder von links nach unten
if (tileType === 'cornerbottomleft') {
if (direction === 'right') {
// Kommt von links (Y-Spur ~281px), biegt nach unten ab (X-Spur ~212.5px)
const laneCenterLower = 0.375 + (0.5 - 0.375) / 2; // 0.4375
const laneCenterUpper = 0.5 + (0.625 - 0.5) / 2; // 0.5625
const turnX = laneCenterLower * tileSize; // X-Position nach dem Abbiegen
const turnY = laneCenterUpper * tileSize; // Y-Position beim Abbiegen
const turnAngle = Math.PI / 2; // nach unten
return {
willTurn: true,
turnDirection: 'down',
turnX: turnX,
turnY: turnY,
turnAngle: turnAngle
};
} else if (direction === 'up') {
// Kommt von unten (X-Spur ~281px), biegt nach links ab (Y-Spur ~212.5px)
const laneCenterLower = 0.375 + (0.5 - 0.375) / 2; // 0.4375
const laneCenterUpper = 0.5 + (0.625 - 0.5) / 2; // 0.5625
const turnX = laneCenterUpper * tileSize; // X-Position beim Abbiegen
const turnY = laneCenterLower * tileSize; // Y-Position nach dem Abbiegen
const turnAngle = Math.PI; // nach links
return {
willTurn: true,
turnDirection: 'left',
turnX: turnX,
turnY: turnY,
turnAngle: turnAngle
};
}
}
// Standard: Kein Abbiegen
return {
willTurn: false,
turnDirection: null,
turnX: null,
turnY: null,
turnAngle: null
};
},
// Finde eine zufällige befahrbare Spawn-Position für ein Auto
getRandomCarSpawnPosition() {
if (!this.currentMap || !this.currentMap.tiles) {
@@ -1548,26 +1756,35 @@ export default {
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' }
'up': { pickRelX: () => 0.5 + laneMargin + Math.random() * (0.625 - 0.5 - 2 * laneMargin), relativeY: 1.02, angle: -Math.PI / 2, direction: 'up', laneAxis: 'V' },
'down': { pickRelX: () => 0.375 + laneMargin + Math.random() * (0.5 - 0.375 - 2 * laneMargin), relativeY: -0.02, angle: Math.PI / 2, direction: 'down', laneAxis: 'V' }
};
// Wähle eine zufällige erlaubte Richtung
const randomDirection = allowedDirections[Math.floor(Math.random() * allowedDirections.length)];
const filtered = allowedDirections;
const randomDirection = filtered[Math.floor(Math.random() * filtered.length)];
const spawnPos = spawnPositionsMap[randomDirection];
if (!spawnPos) {
return null;
}
// Berechne ob und wo das Auto abbiegen wird
const turnInfo = this.calculateTurnInfo(tileType, randomDirection);
// Konvertiere relative Koordinaten zu absoluten Pixeln
// Alle Autos spawnen außerhalb des Tiles (normale Positionierung)
// x und y sind die Mittelpunkte der Spawn-Position
let x = (spawnPos.relativeX != null ? spawnPos.relativeX : (spawnPos.pickRelX ? spawnPos.pickRelX() : 0.5)) * tileSize;
let y = (spawnPos.relativeY != null ? spawnPos.relativeY : (spawnPos.pickRelY ? spawnPos.pickRelY() : 0.5)) * tileSize;
let angle = spawnPos.angle;
let laneCoord;
// Berechne laneCoord basierend auf der Mittelpunkt-Position (BEVOR wir zu linker oberer Ecke konvertieren)
// laneCoord ist IMMER die aktuelle Spur-Position auf der Querachse
// Beim Abbiegen wird laneCoord später in updateCarMovement aktualisiert
laneCoord = spawnPos.laneAxis === 'H' ? y : x;
// Jetzt konvertiere zu linker oberer Ecke für die Rendering-Position
// Passe Koordinaten an: Die Spawn-Position ist die Straßenlinie, aber wir brauchen die linke obere Ecke des Autos
// Das Auto muss auf der Linie zentriert sein
switch (randomDirection) {
@@ -1586,22 +1803,19 @@ export default {
if (y > tileSize) y = tileSize; // Unten außerhalb
break;
}
// laneCoord speichert die Zielspur (Mitte) auf der Querachse
// Verwende die bereits berechneten absoluten x/y, um exakt dieselbe Spur zu halten
const laneCoord = spawnPos.laneAxis === 'H'
? (y + carHeight / 2) // Y-Mitte
: (x + carWidth / 2); // X-Mitte
return {
x: x,
y: y,
angle: spawnPos.angle,
direction: spawnPos.direction,
angle: angle,
direction: randomDirection,
laneAxis: spawnPos.laneAxis,
laneCoord: laneCoord
laneCoord: laneCoord,
willTurn: turnInfo.willTurn,
turnDirection: turnInfo.turnDirection,
turnX: turnInfo.turnX,
turnY: turnInfo.turnY,
turnAngle: turnInfo.turnAngle
};
},
@@ -1757,9 +1971,129 @@ export default {
// Aktualisiere die Bewegung eines einzelnen Autos (vereinfachte Taxi-Logik)
updateCarMovement(car) {
// 0) Prüfe ob das Auto abbiegen sollte (neue Turn-Logik)
if (car.willTurn && !car.hasTurnedAtIntersection) {
// Auto soll abbiegen - prüfe ob es die Turn-Position erreicht oder überschritten hat
const carCenterX = car.x + car.width / 2;
const carCenterY = car.y + car.height / 2;
let shouldTurn = false;
// Prüfe ob das Auto die Turn-Position erreicht hat (abhängig von der Fahrtrichtung)
if (car.direction === 'up') {
// Fährt nach oben - prüfe ob Y-Position erreicht ist
shouldTurn = carCenterY <= car.turnY;
} else if (car.direction === 'down') {
// Fährt nach unten - prüfe ob Y-Position erreicht ist
shouldTurn = carCenterY >= car.turnY;
} else if (car.direction === 'left') {
// Fährt nach links - prüfe ob X-Position erreicht ist
shouldTurn = carCenterX <= car.turnX;
} else if (car.direction === 'right') {
// Fährt nach rechts - prüfe ob X-Position erreicht ist
shouldTurn = carCenterX >= car.turnX;
}
if (shouldTurn) {
// Merke alte Richtung und Position für Animation
const oldDirection = car.direction;
car.direction = car.turnDirection;
car.hasTurnedAtIntersection = true;
// Starte Animation für sanfte Drehung (0.5 Sekunden)
car.isTurning = true;
car.turnStartAngle = car.angle;
car.turnTargetAngle = car.turnAngle;
car.turnStartTime = Date.now();
car.turnDuration = 250; // 0.25 Sekunden
// Speichere Start- und Ziel-Position für smooth Interpolation
car.turnStartX = car.x;
car.turnStartY = car.y;
// Berechne Ziel-Position
if (oldDirection === 'up' || oldDirection === 'down') {
// War vertikal, wird horizontal - setze Y-Position auf turnY
car.turnTargetY = car.turnY - car.height / 2;
car.turnTargetX = car.x; // X bleibt gleich
} else {
// War horizontal, wird vertikal - setze X-Position auf turnX
car.turnTargetX = car.turnX - car.width / 2;
car.turnTargetY = car.y; // Y bleibt gleich
}
// Aktualisiere laneAxis und laneCoord für die neue Richtung
// laneCoord ist die Position auf der Querachse (wo das Auto bleiben soll)
if (car.turnDirection === 'left' || car.turnDirection === 'right') {
// Fährt jetzt horizontal - X ändert sich, Y bleibt fix
car.laneAxis = 'H';
car.laneCoord = car.turnY; // Bleibe auf dieser Y-Position
} else {
// Fährt jetzt vertikal - Y ändert sich, X bleibt fix
car.laneAxis = 'V';
car.laneCoord = car.turnX; // Bleibe auf dieser X-Position
}
}
}
// Animiere Drehung wenn Auto gerade abbiegt
if (car.isTurning) {
const now = Date.now();
const elapsed = now - car.turnStartTime;
const progress = Math.min(elapsed / car.turnDuration, 1.0); // 0.0 bis 1.0
// Easing-Funktion für smooth animation (ease-in-out)
const eased = progress < 0.5
? 2 * progress * progress
: 1 - Math.pow(-2 * progress + 2, 2) / 2;
// Normalisiere Winkel in [-π, π]
const normalizeAngle = (a) => {
while (a > Math.PI) a -= 2 * Math.PI;
while (a < -Math.PI) a += 2 * Math.PI;
return a;
};
// Interpoliere Winkel
let startAngle = normalizeAngle(car.turnStartAngle);
let targetAngle = normalizeAngle(car.turnTargetAngle);
// Kürzesten Weg für Rotation finden mit atan2
// Berechne den Unterschied und verwende atan2 für korrekten Quadranten
let diff = Math.atan2(Math.sin(targetAngle - startAngle), Math.cos(targetAngle - startAngle));
// Debug: Log bei Start der Animation
if (elapsed < 50 && !car._loggedTurn) {
console.log(`Turn animation: start=${(startAngle * 180 / Math.PI).toFixed(1)}° target=${(targetAngle * 180 / Math.PI).toFixed(1)}° diff=${(diff * 180 / Math.PI).toFixed(1)}°`);
car._loggedTurn = true;
}
car.angle = normalizeAngle(startAngle + diff * eased);
// Animation beenden wenn fertig
if (progress >= 1.0) {
car.isTurning = false;
car.angle = targetAngle;
}
}
// 1) An Abbiegepunkt? Dann (ggf.) vorgegebene Zielrichtung übernehmen
const forcedDir = this.shouldCarTurnAtIntersection(car);
// 1) Alte Turn-Logik (nur für Autos ohne vorberechnete Turns - z.B. T-Kreuzungen ohne willTurn)
// Diese Logik wird nur noch für Tile-Typen ohne vorberechnete Turn-Info verwendet
let forcedDir = null;
if (!car.hasTurnedAtIntersection && !car.willTurn) {
forcedDir = this.shouldCarTurnAtIntersection(car);
// Strikter Zwang für T-Down: von unten kommend (direction 'up') niemals geradeaus weiter
const streetType = this.mapTileTypeToStreetCoordinates(this.getCurrentTileType());
if (!forcedDir && streetType === 'tDown' && car.direction === 'up') {
const cy = car.y + car.height / 2;
const tolAbs = 0.03 * this.tiles.size; // ~15px
const centerYAbs = 0.5 * this.tiles.size;
if (Math.abs(cy - centerYAbs) <= tolAbs || cy <= centerYAbs + tolAbs) {
forcedDir = Math.random() < 0.5 ? 'left' : 'right';
}
}
}
if (forcedDir) {
const tileType = this.getCurrentTileType();
// forcedDir hat Vorrang NICHT durch allowedDirections einschränken
@@ -1804,11 +2138,14 @@ export default {
case 'right': newX = car.x + car.speed; break;
}
// Querachse fixieren
// Querachse fixieren - Auto bleibt auf seiner Spur
// Während der Turn-Animation: Sanft zur neuen Spur gleiten
if (car.laneAxis === 'H') {
// Horizontale Bewegung - Y-Position fixieren
const centerY = car.laneCoord;
newY = centerY - car.height / 2;
} else if (car.laneAxis === 'V') {
// Vertikale Bewegung - X-Position fixieren
const centerX = car.laneCoord;
newX = centerX - car.width / 2;
}
@@ -3377,25 +3714,9 @@ export default {
// 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;
}
// Verwende car.angle für smooth rotation während des Abbiegens
// Auto-Bild korrigieren (90° + 180° für korrekte Ausrichtung)
const finalAngle = rotationAngle + Math.PI / 2 + Math.PI;
const finalAngle = car.angle + Math.PI / 2 + Math.PI;
this.ctx.rotate(finalAngle);
if (this.carImage) {