diff --git a/frontend/src/views/minigames/TaxiGame.vue b/frontend/src/views/minigames/TaxiGame.vue index 8cf3f2e..e04c75f 100644 --- a/frontend/src/views/minigames/TaxiGame.vue +++ b/frontend/src/views/minigames/TaxiGame.vue @@ -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; - - // 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; + 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 @@ -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,8 +3268,25 @@ 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