Änderung: Erweiterung der Spielmechanik und Benutzeroberfläche im Taxi-Spiel

Änderungen:
- Hinzufügung von neuen Anzeigen für verbleibende Fahrzeuge, Treibstoff und Geschwindigkeitsverstöße im Tacho-Display.
- Implementierung einer Radar-Logik zur Geschwindigkeitsüberwachung mit akustischen Rückmeldungen bei Verstößen.
- Anpassung der Fahrzeugbewegungslogik zur Aktivierung des Radars beim Betreten neuer Tiles.
- Verbesserung der Benutzeroberfläche durch Anpassungen der CSS-Styles für eine bessere Darstellung.

Diese Anpassungen erhöhen die Interaktivität und Realitätsnähe des Spiels, indem sie eine präzisere Verkehrsüberwachung und visuelle Rückmeldungen bieten.
This commit is contained in:
Torsten Schulz (local)
2025-09-19 08:29:05 +02:00
parent 83e83f6ba9
commit 65b80c37b4

View File

@@ -66,13 +66,18 @@
<!-- Tacho-Display --> <!-- Tacho-Display -->
<div class="tacho-display"> <div class="tacho-display">
<div class="tacho-speed"> <div class="tacho-speed">
<span class="lives" title="Verbleibende Fahrzeuge"> {{ vehicleCount }}</span>
<span class="fuel" title="Treibstoff"> {{ Math.round(fuel) }}%</span>
<span class="speed-violations" title="Geschwindigkeitsverstöße">📷 {{ speedViolations }}</span>
<span class="redlight-counter" title="Rote Ampeln überfahren"> <span class="redlight-counter" title="Rote Ampeln überfahren">
<span class="redlight-icon">🚦</span> <span class="redlight-icon">🚦</span>
<span class="redlight-value">{{ redLightViolations }}</span> <span class="redlight-value">{{ redLightViolations }}</span>
</span> </span>
<span class="speed-group">
<span class="tacho-icon"></span> <span class="tacho-icon"></span>
<span class="speed-value">{{ taxi.speed * 5 }}</span> <span class="speed-value">{{ taxi.speed * 5 }}</span>
<span class="speed-unit">km/h</span> <span class="speed-unit">km/h</span>
</span>
</div> </div>
</div> </div>
@@ -143,15 +148,7 @@
<span class="stat-label">{{ $t('minigames.taxi.passengers') }}</span> <span class="stat-label">{{ $t('minigames.taxi.passengers') }}</span>
</div> </div>
<div class="stat-row">
<span class="stat-value crash-value">{{ crashes }}</span>
<span class="stat-label">Unfälle</span>
</div>
<div class="stat-row">
<span class="stat-value fuel-value">{{ fuel }}%</span>
<span class="stat-label">{{ $t('minigames.taxi.fuel') }}</span>
</div>
</div> </div>
</div> </div>
@@ -244,8 +241,18 @@ export default {
lastTrafficLightTick: 0, lastTrafficLightTick: 0,
redLightViolations: 0, redLightViolations: 0,
redLightLastCount: {}, redLightLastCount: {},
lastViolationSound: 0,
lastMinimapDraw: 0, lastMinimapDraw: 0,
minimapDrawInterval: 120, minimapDrawInterval: 120,
radarImg: null,
activeRadar: false,
radarAtTopEdge: true, // legacy flag (nicht mehr genutzt)
radarTicketShown: false,
speedViolations: 0,
radarAxis: null, // 'H' (horizontale Messlinie: y=konstant) oder 'V' (vertikale Messlinie: x=konstant)
radarLinePos: 0,
vehicleCount: 5,
redLightSincePenalty: 0,
maps: [], // Geladene Maps aus der Datenbank maps: [], // Geladene Maps aus der Datenbank
currentMap: null, // Aktuell verwendete Map currentMap: null, // Aktuell verwendete Map
selectedMapId: null, // ID der ausgewählten Map selectedMapId: null, // ID der ausgewählten Map
@@ -466,36 +473,55 @@ export default {
let newRow = this.currentTile.row; let newRow = this.currentTile.row;
let newCol = this.currentTile.col; let newCol = this.currentTile.col;
let moved = false; let moved = false;
let moveDir = null; // 'right'|'left'|'down'|'up'
// Nach rechts: sobald rechte Kante den Rand erreicht (>=) // Nach rechts: sobald rechte Kante den Rand erreicht (>=)
if (this.taxi.x + this.taxi.width >= tileSize && newCol < cols - 1) { if (this.taxi.x + this.taxi.width >= tileSize && newCol < cols - 1) {
this.taxi.x = 0; // direkt an linken Rand des neuen Tiles this.taxi.x = 0; // direkt an linken Rand des neuen Tiles
newCol += 1; newCol += 1;
moved = true; moved = true;
moveDir = 'right';
} }
// Nach links: sobald linke Kante den Rand erreicht (<= 0) // Nach links: sobald linke Kante den Rand erreicht (<= 0)
if (!moved && this.taxi.x <= 0 && newCol > 0) { if (!moved && this.taxi.x <= 0 && newCol > 0) {
this.taxi.x = tileSize - this.taxi.width; // direkt an rechten Rand des neuen Tiles this.taxi.x = tileSize - this.taxi.width; // direkt an rechten Rand des neuen Tiles
newCol -= 1; newCol -= 1;
moved = true; moved = true;
moveDir = 'left';
} }
// Nach unten: sobald untere Kante den Rand erreicht (>=) // Nach unten: sobald untere Kante den Rand erreicht (>=)
if (!moved && this.taxi.y + this.taxi.height >= tileSize && newRow < rows - 1) { if (!moved && this.taxi.y + this.taxi.height >= tileSize && newRow < rows - 1) {
this.taxi.y = 0; // direkt an oberen Rand des neuen Tiles this.taxi.y = 0; // direkt an oberen Rand des neuen Tiles
newRow += 1; newRow += 1;
moved = true; moved = true;
moveDir = 'down';
} }
// Nach oben: sobald obere Kante den Rand erreicht (<= 0) // Nach oben: sobald obere Kante den Rand erreicht (<= 0)
if (!moved && this.taxi.y <= 0 && newRow > 0) { if (!moved && this.taxi.y <= 0 && newRow > 0) {
this.taxi.y = tileSize - this.taxi.height; // direkt an unteren Rand des neuen Tiles this.taxi.y = tileSize - this.taxi.height; // direkt an unteren Rand des neuen Tiles
newRow -= 1; newRow -= 1;
moved = true; moved = true;
moveDir = 'up';
} }
// Falls Wechsel möglich war, übernehme neue Tile-Position // Falls Wechsel möglich war, übernehme neue Tile-Position
if (moved) { if (moved) {
this.currentTile.row = newRow; this.currentTile.row = newRow;
this.currentTile.col = newCol; this.currentTile.col = newCol;
// Radar mit 12% Wahrscheinlichkeit aktivieren beim Betreten des neuen Tiles
this.activeRadar = Math.random() < 0.12;
// Achse und Position festlegen: Messung findet bei 150px vom Rand statt
if (this.activeRadar) {
const size = this.tiles.size;
if (moveDir === 'right') { this.radarAxis = 'V'; this.radarLinePos = 150; }
else if (moveDir === 'left') { this.radarAxis = 'V'; this.radarLinePos = size - 150; }
else if (moveDir === 'down') { this.radarAxis = 'H'; this.radarLinePos = 150; }
else if (moveDir === 'up') { this.radarAxis = 'H'; this.radarLinePos = size - 150; }
else { this.radarAxis = 'H'; this.radarLinePos = 150; }
} else {
this.radarAxis = null;
}
this.radarTicketShown = false;
return; return;
} }
@@ -733,6 +759,9 @@ export default {
this.checkCollisions(); this.checkCollisions();
this.render(); this.render();
// Radar-Messung prüfen (falls aktiv)
this.checkRadarMeasurement();
// Minimap zeichnen (gedrosselt) // Minimap zeichnen (gedrosselt)
const nowTs = Date.now(); const nowTs = Date.now();
if (nowTs - this.lastMinimapDraw >= this.minimapDrawInterval) { if (nowTs - this.lastMinimapDraw >= this.minimapDrawInterval) {
@@ -961,7 +990,106 @@ export default {
if (now - last > 500) { // 0.5s Sperrzeit if (now - last > 500) { // 0.5s Sperrzeit
this.redLightLastCount[key] = now; this.redLightLastCount[key] = now;
this.redLightViolations += 1; this.redLightViolations += 1;
this.playRedLightSound();
// Drei Rotlichtverstöße => Crash-Penalty
this.redLightSincePenalty = (this.redLightSincePenalty || 0) + 1;
if (this.redLightSincePenalty >= 3) {
this.redLightSincePenalty = 0;
this.crashes += 1;
this.decrementVehicle('redlight');
const msg = 'Du hast wegen Rotlicht-Übertretungen ein Auto verloren.';
const title = 'Hinweis';
this.$root?.$refs?.messageDialog?.open?.(msg, title, {}, () => {
if (this.vehicleCount === 0) {
this.handleOutOfVehicles();
} }
});
}
}
}
},
checkRadarMeasurement() {
if (!this.activeRadar || this.radarTicketShown || !this.radarAxis) return;
const size = this.tiles.size;
const thresholdSpeedLevel = 10; // >50 km/h
const speedLevel = this.taxi.speed;
const prevCX = this.prevTaxiX + this.taxi.width / 2;
const prevCY = this.prevTaxiY + this.taxi.height / 2;
const currCX = this.taxi.x + this.taxi.width / 2;
const currCY = this.taxi.y + this.taxi.height / 2;
if (this.radarAxis === 'H') {
const y = this.radarLinePos;
// Richtung beachten: von oben nach unten über y (wenn y nahe oben), sonst umgekehrt
if (y <= size / 2) {
if (speedLevel > thresholdSpeedLevel && prevCY < y && currCY >= y) this.issueSpeedTicket();
} else {
if (speedLevel > thresholdSpeedLevel && prevCY > y && currCY <= y) this.issueSpeedTicket();
}
} else if (this.radarAxis === 'V') {
const x = this.radarLinePos;
if (x <= size / 2) {
if (speedLevel > thresholdSpeedLevel && prevCX < x && currCX >= x) this.issueSpeedTicket();
} else {
if (speedLevel > thresholdSpeedLevel && prevCX > x && currCX <= x) this.issueSpeedTicket();
}
}
},
issueSpeedTicket() {
this.radarTicketShown = true;
this.speedViolations += 1;
this.playCameraSound();
},
playCameraSound() {
try {
this.ensureAudioUnlockedInEvent();
if (!this.audioContext) return;
const now = this.audioContext.currentTime;
const osc = this.audioContext.createOscillator();
const gain = this.audioContext.createGain();
// kurzer Klick (Kamera-Shutter)
osc.type = 'square';
osc.frequency.setValueAtTime(2400, now);
gain.gain.setValueAtTime(0, now);
gain.gain.linearRampToValueAtTime(0.4, now + 0.005);
gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.06);
osc.connect(gain).connect(this.audioContext.destination);
osc.start(now);
osc.stop(now + 0.07);
} catch (e) {
// ignore
}
},
playRedLightSound() {
// Ein kurzer Piepton über die bestehende AudioContext-Pipeline
try {
this.ensureAudioUnlockedInEvent();
if (!this.audioContext) return;
const now = this.audioContext.currentTime;
// Entprellen global: max 1x pro 550ms (Ton ist länger)
const wall = (this._rlLastWall || 0);
if (now < wall) return;
this._rlLastWall = now + 0.55;
const osc = this.audioContext.createOscillator();
const gain = this.audioContext.createGain();
osc.type = 'square';
// etwas höher
osc.frequency.setValueAtTime(1320, now);
gain.gain.setValueAtTime(0, now);
// etwas lauter und länger
gain.gain.linearRampToValueAtTime(0.35, now + 0.01);
gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.5);
osc.connect(gain).connect(this.audioContext.destination);
osc.start(now);
osc.stop(now + 0.52);
} catch (e) {
// ignore
} }
}, },
@@ -1028,6 +1156,25 @@ export default {
this.canvas.focus(); this.canvas.focus();
} }
console.log('Nach nextTick - isPaused:', this.isPaused); console.log('Nach nextTick - isPaused:', this.isPaused);
if (this.vehicleCount === 0) {
this.handleOutOfVehicles();
}
});
},
decrementVehicle(reason = '') {
this.vehicleCount = Math.max(0, this.vehicleCount - 1);
},
handleOutOfVehicles() {
const title = 'Hinweis';
const msg = 'Keine Fahrzeuge mehr. Neustart.';
this.$root?.$refs?.messageDialog?.open?.(msg, title, {}, () => {
this.restartLevel();
this.vehicleCount = 5;
this.crashes = 0;
this.redLightViolations = 0;
this.redLightSincePenalty = 0;
}); });
}, },
@@ -1226,6 +1373,15 @@ export default {
// Zeichne Straßen // Zeichne Straßen
this.drawRoads(); this.drawRoads();
// Radar-Icon oben rechts (50x50), wenn aktiv
if (this.activeRadar) {
const img = this.tiles.images['radar'];
if (img && img.complete) {
const size = 50;
this.ctx.drawImage(img, this.canvas.width - size - 4, 4, size, size);
}
}
// Zeichne Hindernisse // Zeichne Hindernisse
this.obstacles.forEach(obstacle => { this.obstacles.forEach(obstacle => {
this.ctx.fillStyle = '#666'; this.ctx.fillStyle = '#666';
@@ -1700,6 +1856,10 @@ export default {
img.src = `/images/taxi/${key}.svg`; img.src = `/images/taxi/${key}.svg`;
this.tiles.images[key] = img; this.tiles.images[key] = img;
} }
// Radar-Icon
const radar = new Image();
radar.src = '/images/taxi/radar.svg';
this.tiles.images['radar'] = radar;
}, },
loadTaxiImage() { loadTaxiImage() {
@@ -2181,7 +2341,7 @@ export default {
.tacho-speed { .tacho-speed {
display: flex; display: flex;
justify-content: flex-end; justify-content: space-between;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
font-size: 10pt; font-size: 10pt;
@@ -2192,11 +2352,17 @@ export default {
.redlight-counter { display: inline-flex; align-items: center; gap: 4px; margin-right: 8px; } .redlight-counter { display: inline-flex; align-items: center; gap: 4px; margin-right: 8px; }
.redlight-icon { font-size: 10pt; } .redlight-icon { font-size: 10pt; }
.redlight-value { font-weight: 700; min-width: 16px; text-align: right; } .redlight-value { font-weight: 700; min-width: 16px; text-align: right; }
.speed-group { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.redlight-counter { white-space: nowrap; }
.tacho-icon { .tacho-icon {
font-size: 10pt; font-size: 10pt;
} }
.lives { font-weight: 700; color: #d60000; }
.fuel { font-weight: 600; color: #0a7c00; margin-left: 8px; }
.speed-violations { font-weight: 700; color: #8a2be2; margin-left: 8px; }
.speed-value { .speed-value {
font-weight: bold; font-weight: bold;
color: #2196F3; color: #2196F3;