Ä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:
@@ -1402,16 +1402,16 @@ export default {
|
|||||||
const tileSize = this.tiles.size;
|
const tileSize = this.tiles.size;
|
||||||
|
|
||||||
// Definiere die erlaubten Spawn-Positionen (relativ 0-1)
|
// 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 = [
|
const spawnPositions = [
|
||||||
// Links spawnen, nach rechts fahren, auf der rechten Straßenseite (y=0.5-0.625)
|
// Links spawnen, nach rechts fahren, auf der rechten Straßenseite (y=0.55-0.625)
|
||||||
{ relativeX: 0.1, relativeY: 0.5 + Math.random() * 0.125, angle: 0, direction: 'right' },
|
{ 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.5)
|
// Rechts spawnen, nach links fahren, auf der linken Straßenseite (y=0.375-0.45)
|
||||||
{ relativeX: 0.9, relativeY: 0.375 + Math.random() * 0.125, angle: Math.PI, direction: 'left' },
|
{ 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.5)
|
// Oben spawnen, nach unten fahren, auf der linken Straßenseite (x=0.375-0.45)
|
||||||
{ relativeX: 0.375 + Math.random() * 0.125, relativeY: 0.1, angle: Math.PI / 2, direction: 'down' },
|
{ 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.5-0.625)
|
// Unten spawnen, nach oben fahren, auf der rechten Straßenseite (x=0.55-0.625)
|
||||||
{ relativeX: 0.5 + Math.random() * 0.125, relativeY: 0.9, angle: -Math.PI / 2, direction: 'up' }
|
{ relativeX: 0.55 + Math.random() * 0.075, relativeY: 0.9, angle: -Math.PI / 2, direction: 'up' }
|
||||||
];
|
];
|
||||||
|
|
||||||
// Wähle eine zufällige Spawn-Position
|
// 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) {
|
shouldCarTurnAtIntersection(car) {
|
||||||
const relativeX = car.x / this.tiles.size;
|
const rx = car.x / this.tiles.size;
|
||||||
const relativeY = car.y / this.tiles.size;
|
const ry = car.y / this.tiles.size;
|
||||||
|
const tileType = this.mapTileTypeToStreetCoordinates(this.getCurrentTileType());
|
||||||
// Prüfe ob Auto sehr nah an der Straßenmitte ist (genauere Kreuzungserkennung)
|
// Engeres Toleranzfenster: 0.495..0.505
|
||||||
const atCenter = (relativeX > 0.48 && relativeX < 0.52) && (relativeY > 0.48 && relativeY < 0.52);
|
const nearYCenter = ry > 0.495 && ry < 0.505; // vertikale Mitte erreicht
|
||||||
|
const nearXCenter = rx > 0.495 && rx < 0.505; // horizontale Mitte erreicht
|
||||||
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
|
// Auto zur Zielrichtung abbiegen
|
||||||
@@ -2250,14 +2276,14 @@ export default {
|
|||||||
// Prüfe Hindernisse nur wenn das Spiel nicht pausiert ist
|
// Prüfe Hindernisse nur wenn das Spiel nicht pausiert ist
|
||||||
if (!this.isPaused) {
|
if (!this.isPaused) {
|
||||||
this.obstacles.forEach(obstacle => {
|
this.obstacles.forEach(obstacle => {
|
||||||
if (this.checkCollision(this.taxi, obstacle)) {
|
if (this.checkCollision(this.taxi, obstacle, true)) {
|
||||||
this.handleCrash();
|
this.handleCrash();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prüfe Autos-Kollisionen
|
// Prüfe Autos-Kollisionen
|
||||||
this.cars.forEach(car => {
|
this.cars.forEach(car => {
|
||||||
if (this.checkCollision(this.taxi, car)) {
|
if (this.checkCollision(this.taxi, car, true)) {
|
||||||
this.handleCrash('auto');
|
this.handleCrash('auto');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -3034,25 +3060,122 @@ export default {
|
|||||||
return 'cornertopleft';
|
return 'cornertopleft';
|
||||||
},
|
},
|
||||||
|
|
||||||
checkCollision(rect1, rect2) {
|
checkCollision(a, b, recordDebug = false) {
|
||||||
// Optionaler Puffer, um die Kollision weniger sensibel zu machen (sichtbarer Kontakt nötig)
|
// Präzise Kollision über OBB (oriented bounding boxes) mittels SAT
|
||||||
// Für Car-vs-Taxi prüfen wir einen negativen Puffer (verkleinert Hitboxen)
|
// Fallback auf AABB, wenn Winkel fehlen
|
||||||
const pad = (rect1.isCar || rect2.isCar) ? 4 : 0; // 4px Puffer pro Seite bei Autos (präziser Crash)
|
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;
|
// Wenn beide quasi unrotiert sind, nutze performantes AABB
|
||||||
const aRight = rect1.x + rect1.width - pad;
|
const small = 1e-6;
|
||||||
const aTop = rect1.y + pad;
|
if (Math.abs(angleA) < small && Math.abs(angleB) < small) {
|
||||||
const aBottom = rect1.y + rect1.height - pad;
|
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 pad = (a.isCar || b.isCar) ? 3 : 0; // etwas größerer Puffer bei OBB
|
||||||
const bRight = rect2.x + rect2.width - pad;
|
|
||||||
const bTop = rect2.y + pad;
|
|
||||||
const bBottom = rect2.y + rect2.height - pad;
|
|
||||||
|
|
||||||
return aLeft < bRight &&
|
const obbA = this._buildObb(a, pad);
|
||||||
aRight > bLeft &&
|
const obbB = this._buildObb(b, pad);
|
||||||
aTop < bBottom &&
|
|
||||||
aBottom > bTop;
|
// 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() {
|
render() {
|
||||||
@@ -3082,6 +3205,14 @@ export default {
|
|||||||
// Zeichne Autos
|
// Zeichne Autos
|
||||||
this.drawCars();
|
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
|
// Zeichne Taxi
|
||||||
this.ctx.save();
|
this.ctx.save();
|
||||||
this.ctx.translate(this.taxi.x + this.taxi.width/2, this.taxi.y + this.taxi.height/2);
|
this.ctx.translate(this.taxi.x + this.taxi.width/2, this.taxi.y + this.taxi.height/2);
|
||||||
@@ -3137,8 +3268,25 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.ctx.restore();
|
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() {
|
drawRoads() {
|
||||||
const tileSize = this.tiles.size; // 400x400px
|
const tileSize = this.tiles.size; // 400x400px
|
||||||
|
|||||||
Reference in New Issue
Block a user