Ä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 -->
|
<!-- 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="tacho-icon">⏱️</span>
|
<span class="speed-group">
|
||||||
<span class="speed-value">{{ taxi.speed * 5 }}</span>
|
<span class="tacho-icon">⏱️</span>
|
||||||
<span class="speed-unit">km/h</span>
|
<span class="speed-value">{{ taxi.speed * 5 }}</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,10 +990,109 @@ 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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
handleCrash() {
|
handleCrash() {
|
||||||
// Verhindere mehrfache Crashes in kurzer Zeit
|
// Verhindere mehrfache Crashes in kurzer Zeit
|
||||||
if (this.isPaused) {
|
if (this.isPaused) {
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user