Änderung: Verbesserung der Kollisionserkennung und Anpassung der Spawn-Logik im Taxi-Spiel

Änderungen:
- Anpassung der Spawn-Positionen für Autos, um die Logik für die Straßenseiten zu optimieren.
- Verfeinerung der Abbiegeerkennung an Kreuzungen mit engeren Toleranzfenstern.
- Implementierung einer präziseren Kollisionserkennung mittels orientierter Bounding Boxes (OBB).
- Einführung von Debugging-Overlays zur Visualisierung von Kollisionen und Hitboxen.

Diese Anpassungen erhöhen die Genauigkeit der Kollisionserkennung und verbessern die Spielmechanik durch optimierte Spawn-Logik und Debugging-Funktionen.
This commit is contained in:
Torsten Schulz (local)
2025-10-06 12:39:21 +02:00
parent 828e45df35
commit 67701272c5

View File

@@ -1402,16 +1402,16 @@ export default {
const tileSize = this.tiles.size;
// Definiere die erlaubten Spawn-Positionen (relativ 0-1)
// Mit korrekter Straßenseite basierend auf echten Straßenkoordinaten
// Strengere Spur-Bänder: links/oben bis 0.45, rechts/unten ab 0.55
const spawnPositions = [
// Links spawnen, nach rechts fahren, auf der rechten Straßenseite (y=0.5-0.625)
{ relativeX: 0.1, relativeY: 0.5 + Math.random() * 0.125, angle: 0, direction: 'right' },
// Rechts spawnen, nach links fahren, auf der linken Straßenseite (y=0.375-0.5)
{ relativeX: 0.9, relativeY: 0.375 + Math.random() * 0.125, angle: Math.PI, direction: 'left' },
// Oben spawnen, nach unten fahren, auf der linken Straßenseite (x=0.375-0.5)
{ relativeX: 0.375 + Math.random() * 0.125, relativeY: 0.1, angle: Math.PI / 2, direction: 'down' },
// Unten spawnen, nach oben fahren, auf der rechten Straßenseite (x=0.5-0.625)
{ relativeX: 0.5 + Math.random() * 0.125, relativeY: 0.9, angle: -Math.PI / 2, direction: 'up' }
// Links spawnen, nach rechts fahren, auf der rechten Straßenseite (y=0.55-0.625)
{ relativeX: 0.1, 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: 0.9, 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.1, angle: Math.PI / 2, direction: 'down' },
// Unten spawnen, nach oben fahren, auf der rechten Straßenseite (x=0.55-0.625)
{ relativeX: 0.55 + Math.random() * 0.075, relativeY: 0.9, angle: -Math.PI / 2, direction: 'up' }
];
// Wähle eine zufällige Spawn-Position
@@ -1621,15 +1621,41 @@ export default {
}
},
// Prüfe ob Auto an Kreuzung zur Zielrichtung abbiegen soll
// Prüfe, ob am aktuellen Tile der Abbiegepunkt erreicht ist
shouldCarTurnAtIntersection(car) {
const relativeX = car.x / this.tiles.size;
const relativeY = car.y / this.tiles.size;
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
// Prüfe ob Auto sehr nah an der Straßenmitte ist (genauere Kreuzungserkennung)
const atCenter = (relativeX > 0.48 && relativeX < 0.52) && (relativeY > 0.48 && relativeY < 0.52);
return atCenter;
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
@@ -2250,14 +2276,14 @@ export default {
// Prüfe Hindernisse nur wenn das Spiel nicht pausiert ist
if (!this.isPaused) {
this.obstacles.forEach(obstacle => {
if (this.checkCollision(this.taxi, obstacle)) {
if (this.checkCollision(this.taxi, obstacle, true)) {
this.handleCrash();
}
});
// Prüfe Autos-Kollisionen
this.cars.forEach(car => {
if (this.checkCollision(this.taxi, car)) {
if (this.checkCollision(this.taxi, car, true)) {
this.handleCrash('auto');
}
});
@@ -3034,25 +3060,122 @@ export default {
return 'cornertopleft';
},
checkCollision(rect1, rect2) {
// Optionaler Puffer, um die Kollision weniger sensibel zu machen (sichtbarer Kontakt nötig)
// Für Car-vs-Taxi prüfen wir einen negativen Puffer (verkleinert Hitboxen)
const pad = (rect1.isCar || rect2.isCar) ? 4 : 0; // 4px Puffer pro Seite bei Autos (präziser Crash)
checkCollision(a, b, recordDebug = false) {
// Präzise Kollision über OBB (oriented bounding boxes) mittels SAT
// Fallback auf AABB, wenn Winkel fehlen
const angleA = ((typeof a.angle === 'number') ? a.angle : 0) + ((typeof a.imageAngle === 'number') ? a.imageAngle : 0);
const angleB = ((typeof b.angle === 'number') ? b.angle : 0) + ((typeof b.imageAngle === 'number') ? b.imageAngle : 0);
const aLeft = rect1.x + pad;
const aRight = rect1.x + rect1.width - pad;
const aTop = rect1.y + pad;
const aBottom = rect1.y + rect1.height - pad;
// Wenn beide quasi unrotiert sind, nutze performantes AABB
const small = 1e-6;
if (Math.abs(angleA) < small && Math.abs(angleB) < small) {
const pad = (a.isCar || b.isCar) ? 4 : 0;
const aLeft = a.x + pad;
const aRight = a.x + a.width - pad;
const aTop = a.y + pad;
const aBottom = a.y + a.height - pad;
const bLeft = b.x + pad;
const bRight = b.x + b.width - pad;
const bTop = b.y + pad;
const bBottom = b.y + b.height - pad;
const hit = aLeft < bRight && aRight > bLeft && aTop < bBottom && aBottom > bTop;
if (recordDebug) {
this._collisionDebug = {
aCorners: [
{x:aLeft,y:aTop},{x:aRight,y:aTop},{x:aRight,y:aBottom},{x:aLeft,y:aBottom}
],
bCorners: [
{x:bLeft,y:bTop},{x:bRight,y:bTop},{x:bRight,y:bBottom},{x:bLeft,y:bBottom}
],
ttl: 60
};
}
return hit;
}
const bLeft = rect2.x + pad;
const bRight = rect2.x + rect2.width - pad;
const bTop = rect2.y + pad;
const bBottom = rect2.y + rect2.height - pad;
const pad = (a.isCar || b.isCar) ? 3 : 0; // etwas größerer Puffer bei OBB
return aLeft < bRight &&
aRight > bLeft &&
aTop < bBottom &&
aBottom > bTop;
const obbA = this._buildObb(a, pad);
const obbB = this._buildObb(b, pad);
// Achsen: Normalen der Kanten beider Rechtecke (4 Achsen)
const axes = [
this._edgeAxis(obbA.corners[0], obbA.corners[1]),
this._edgeAxis(obbA.corners[1], obbA.corners[2]),
this._edgeAxis(obbB.corners[0], obbB.corners[1]),
this._edgeAxis(obbB.corners[1], obbB.corners[2])
];
for (let i = 0; i < axes.length; i++) {
const axis = axes[i];
const projA = this._projectOntoAxis(obbA.corners, axis);
const projB = this._projectOntoAxis(obbB.corners, axis);
if (projA.max < projB.min || projB.max < projA.min) {
return false; // trennende Achse gefunden
}
}
if (recordDebug) {
this._collisionDebug = { aCorners: obbA.corners, bCorners: obbB.corners, ttl: 60 };
}
return true; // alle Intervalle überlappen
},
_buildObb(rect, pad = 0) {
const cx = rect.x + rect.width / 2;
const cy = rect.y + rect.height / 2;
// Spezifische Kollisionsmaße:
// - Taxi: schmaler als Zeichnungs-Bounding-Box (Bild enthält weiße Ränder)
// - Auto: verwende reale width/height
let w = rect.width;
let h = rect.height;
if (rect === this.taxi) {
w = Math.max(0, rect.width * 0.70); // 70% der visuellen Breite
h = Math.max(0, rect.height * 0.90); // 90% der visuellen Höhe
} else if (rect.isCar) {
// Autos fahren nur links/rechts → Fahrzeuglänge = horizontal (w), Fahrzeugbreite = vertikal (h)
// Daher: Länge kaum kürzen, Breite deutlich schmaler
w = Math.max(0, rect.width * 0.90); // Länge ~90%
h = Math.max(0, rect.height * 0.57); // Breite ~65%
}
const hw = Math.max(0, w / 2 - pad);
const hh = Math.max(0, h / 2 - pad);
const ang = ((rect.angle || 0) + (rect.imageAngle || 0));
const c = Math.cos(ang);
const s = Math.sin(ang);
// lokale Ecken
const local = [
{ x: -hw, y: -hh },
{ x: hw, y: -hh },
{ x: hw, y: hh },
{ x: -hw, y: hh }
];
// rotiere + verschiebe
const corners = local.map(p => ({ x: cx + p.x * c - p.y * s, y: cy + p.x * s + p.y * c }));
return { corners };
},
_edgeAxis(p1, p2) {
// Kantenrichtung
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
// Normale (senkrecht)
const nx = -dy;
const ny = dx;
// normieren
const len = Math.hypot(nx, ny) || 1;
return { x: nx / len, y: ny / len };
},
_projectOntoAxis(corners, axis) {
let min = Infinity, max = -Infinity;
for (let i = 0; i < corners.length; i++) {
const p = corners[i];
const val = p.x * axis.x + p.y * axis.y;
if (val < min) min = val;
if (val > max) max = val;
}
return { min, max };
},
render() {
@@ -3082,6 +3205,14 @@ export default {
// Zeichne Autos
this.drawCars();
// Debug: Kollision-Overlays zeichnen (falls vorhanden)
if (this._collisionDebug && this._collisionDebug.ttl > 0) {
const dbg = this._collisionDebug;
this._drawDebugPoly(dbg.aCorners, 'rgba(255,0,0,0.35)', '#ff0000');
this._drawDebugPoly(dbg.bCorners, 'rgba(0,128,255,0.35)', '#0080ff');
dbg.ttl -= 1;
}
// Zeichne Taxi
this.ctx.save();
this.ctx.translate(this.taxi.x + this.taxi.width/2, this.taxi.y + this.taxi.height/2);
@@ -3137,9 +3268,26 @@ export default {
}
this.ctx.restore();
// Debug: permanenten Crash-Bereich (Hitbox) der Autos anzeigen
const obb = this._buildObb(car, 2);
this._drawDebugPoly(obb.corners, 'rgba(255,165,0,0.15)', '#ffa500');
});
},
_drawDebugPoly(corners, fill, stroke) {
if (!corners || corners.length < 3) return;
const ctx = this.ctx;
ctx.save();
ctx.beginPath();
ctx.moveTo(corners[0].x, corners[0].y);
for (let i = 1; i < corners.length; i++) ctx.lineTo(corners[i].x, corners[i].y);
ctx.closePath();
if (fill) { ctx.fillStyle = fill; ctx.fill(); }
if (stroke) { ctx.strokeStyle = stroke; ctx.lineWidth = 2; ctx.stroke(); }
ctx.restore();
},
drawRoads() {
const tileSize = this.tiles.size; // 400x400px