Ä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:
@@ -66,13 +66,18 @@
|
||||
<!-- Tacho-Display -->
|
||||
<div class="tacho-display">
|
||||
<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-icon">🚦</span>
|
||||
<span class="redlight-value">{{ redLightViolations }}</span>
|
||||
</span>
|
||||
<span class="tacho-icon">⏱️</span>
|
||||
<span class="speed-value">{{ taxi.speed * 5 }}</span>
|
||||
<span class="speed-unit">km/h</span>
|
||||
<span class="speed-group">
|
||||
<span class="tacho-icon">⏱️</span>
|
||||
<span class="speed-value">{{ taxi.speed * 5 }}</span>
|
||||
<span class="speed-unit">km/h</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -143,15 +148,7 @@
|
||||
<span class="stat-label">{{ $t('minigames.taxi.passengers') }}</span>
|
||||
</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>
|
||||
|
||||
@@ -244,8 +241,18 @@ export default {
|
||||
lastTrafficLightTick: 0,
|
||||
redLightViolations: 0,
|
||||
redLightLastCount: {},
|
||||
lastViolationSound: 0,
|
||||
lastMinimapDraw: 0,
|
||||
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
|
||||
currentMap: null, // Aktuell verwendete Map
|
||||
selectedMapId: null, // ID der ausgewählten Map
|
||||
@@ -466,36 +473,55 @@ export default {
|
||||
let newRow = this.currentTile.row;
|
||||
let newCol = this.currentTile.col;
|
||||
let moved = false;
|
||||
let moveDir = null; // 'right'|'left'|'down'|'up'
|
||||
|
||||
// Nach rechts: sobald rechte Kante den Rand erreicht (>=)
|
||||
if (this.taxi.x + this.taxi.width >= tileSize && newCol < cols - 1) {
|
||||
this.taxi.x = 0; // direkt an linken Rand des neuen Tiles
|
||||
newCol += 1;
|
||||
moved = true;
|
||||
moveDir = 'right';
|
||||
}
|
||||
// Nach links: sobald linke Kante den Rand erreicht (<= 0)
|
||||
if (!moved && this.taxi.x <= 0 && newCol > 0) {
|
||||
this.taxi.x = tileSize - this.taxi.width; // direkt an rechten Rand des neuen Tiles
|
||||
newCol -= 1;
|
||||
moved = true;
|
||||
moveDir = 'left';
|
||||
}
|
||||
// Nach unten: sobald untere Kante den Rand erreicht (>=)
|
||||
if (!moved && this.taxi.y + this.taxi.height >= tileSize && newRow < rows - 1) {
|
||||
this.taxi.y = 0; // direkt an oberen Rand des neuen Tiles
|
||||
newRow += 1;
|
||||
moved = true;
|
||||
moveDir = 'down';
|
||||
}
|
||||
// Nach oben: sobald obere Kante den Rand erreicht (<= 0)
|
||||
if (!moved && this.taxi.y <= 0 && newRow > 0) {
|
||||
this.taxi.y = tileSize - this.taxi.height; // direkt an unteren Rand des neuen Tiles
|
||||
newRow -= 1;
|
||||
moved = true;
|
||||
moveDir = 'up';
|
||||
}
|
||||
|
||||
// Falls Wechsel möglich war, übernehme neue Tile-Position
|
||||
if (moved) {
|
||||
this.currentTile.row = newRow;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -733,6 +759,9 @@ export default {
|
||||
this.checkCollisions();
|
||||
this.render();
|
||||
|
||||
// Radar-Messung prüfen (falls aktiv)
|
||||
this.checkRadarMeasurement();
|
||||
|
||||
// Minimap zeichnen (gedrosselt)
|
||||
const nowTs = Date.now();
|
||||
if (nowTs - this.lastMinimapDraw >= this.minimapDrawInterval) {
|
||||
@@ -961,9 +990,108 @@ export default {
|
||||
if (now - last > 500) { // 0.5s Sperrzeit
|
||||
this.redLightLastCount[key] = now;
|
||||
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
|
||||
}
|
||||
},
|
||||
|
||||
handleCrash() {
|
||||
// Verhindere mehrfache Crashes in kurzer Zeit
|
||||
@@ -1028,6 +1156,25 @@ export default {
|
||||
this.canvas.focus();
|
||||
}
|
||||
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;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1225,6 +1372,15 @@ export default {
|
||||
|
||||
// Zeichne Straßen
|
||||
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
|
||||
this.obstacles.forEach(obstacle => {
|
||||
@@ -1700,6 +1856,10 @@ export default {
|
||||
img.src = `/images/taxi/${key}.svg`;
|
||||
this.tiles.images[key] = img;
|
||||
}
|
||||
// Radar-Icon
|
||||
const radar = new Image();
|
||||
radar.src = '/images/taxi/radar.svg';
|
||||
this.tiles.images['radar'] = radar;
|
||||
},
|
||||
|
||||
loadTaxiImage() {
|
||||
@@ -2181,7 +2341,7 @@ export default {
|
||||
|
||||
.tacho-speed {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 10pt;
|
||||
@@ -2192,11 +2352,17 @@ export default {
|
||||
.redlight-counter { display: inline-flex; align-items: center; gap: 4px; margin-right: 8px; }
|
||||
.redlight-icon { font-size: 10pt; }
|
||||
.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 {
|
||||
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 {
|
||||
font-weight: bold;
|
||||
color: #2196F3;
|
||||
|
||||
Reference in New Issue
Block a user