Änderung: Erweiterung der Taxi-Map-Logik und Verbesserung der Benutzeroberfläche
Änderungen: - Hinzufügung neuer Modelle für TaxiMapTile, TaxiStreetName und TaxiMapTileStreet zur Unterstützung der Tile- und Straßennamen-Logik. - Anpassung der TaxiMap- und TaxiMapService-Logik zur Verwaltung von Tiles und Straßennamen. - Implementierung von Methoden zur Upsert-Logik für Tiles und Straßennamen in der TaxiMapService. - Verbesserung der Benutzeroberfläche in TaxiToolsView.vue zur Anzeige und Bearbeitung von Straßennamen und zusätzlichen Elementen. Diese Anpassungen verbessern die Funktionalität und Benutzererfahrung im Taxi-Minispiel erheblich, indem sie eine detailliertere Verwaltung von Karten und Straßennamen ermöglichen.
This commit is contained in:
@@ -43,6 +43,22 @@
|
||||
<li>Vermeide Kollisionen mit anderen Fahrzeugen</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Straßenname-Legende -->
|
||||
<div class="game-objectives" v-if="streetLegend.length">
|
||||
<h4>Straßennamen</h4>
|
||||
<ul>
|
||||
<li
|
||||
v-for="item in streetLegend"
|
||||
:key="item.num"
|
||||
class="legend-street-item"
|
||||
:class="{ selected: selectedStreetName === item.name }"
|
||||
@click="onSelectStreet(item.name)"
|
||||
>
|
||||
{{ item.num }}: {{ item.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Spiel-Canvas mit Tacho -->
|
||||
@@ -228,7 +244,40 @@ export default {
|
||||
keys: {},
|
||||
motorSound: null,
|
||||
audioContext: null,
|
||||
audioUnlockHandler: null
|
||||
audioUnlockHandler: null,
|
||||
selectedStreetName: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
toggleIcon() {
|
||||
return this.isStatsExpanded ? '−' : '+';
|
||||
},
|
||||
streetLegend() {
|
||||
// Sammle alle Straßennamen aus currentMap.tileStreets und vergebe laufende Nummern
|
||||
try {
|
||||
const legend = [];
|
||||
if (!this.currentMap || !Array.isArray(this.currentMap.tileStreets)) return legend;
|
||||
const nameToNum = new Map();
|
||||
let counter = 1;
|
||||
for (const ts of this.currentMap.tileStreets) {
|
||||
if (ts.streetNameH && ts.streetNameH.name) {
|
||||
const n = ts.streetNameH.name;
|
||||
if (!nameToNum.has(n)) nameToNum.set(n, counter++);
|
||||
}
|
||||
if (ts.streetNameV && ts.streetNameV.name) {
|
||||
const n = ts.streetNameV.name;
|
||||
if (!nameToNum.has(n)) nameToNum.set(n, counter++);
|
||||
}
|
||||
}
|
||||
// Sortiert nach zugewiesener Nummer
|
||||
const entries = Array.from(nameToNum.entries()).sort((a,b) => a[1]-b[1]);
|
||||
for (const [name, num] of entries) {
|
||||
legend.push({ num, name });
|
||||
}
|
||||
return legend;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@@ -245,6 +294,74 @@ export default {
|
||||
this.cleanup();
|
||||
},
|
||||
methods: {
|
||||
// Hilfsfunktion: Liefert laufende Nummer für gegebenen Straßennamen
|
||||
getStreetNumberByName(name) {
|
||||
const item = this.streetLegend.find(it => it.name === name);
|
||||
return item ? item.num : null;
|
||||
},
|
||||
|
||||
// Zeichnet die Straßen-Nr. auf die Minimap, je nach Tile-Typ und Position (pro Name nur einmal)
|
||||
drawStreetNumbersOnMinimap(ctx, x, y, size, tileType, col, row, drawnNames) {
|
||||
if (!this.currentMap || !Array.isArray(this.currentMap.tileStreets)) return;
|
||||
|
||||
// Finde etwaige StreetName-Einträge für dieses Tile
|
||||
const entry = this.currentMap.tileStreets.find(ts => ts.x === col && ts.y === row);
|
||||
if (!entry) return;
|
||||
|
||||
ctx.save();
|
||||
const baseFontSize = Math.max(9, Math.floor(size * 0.22) - 1);
|
||||
ctx.font = `${baseFontSize}px sans-serif`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
const left = x + size * 0.2;
|
||||
const right = x + size * 0.8;
|
||||
const top = y + size * 0.2;
|
||||
const bottom = y + size * 0.8;
|
||||
|
||||
const hName = entry.streetNameH?.name || null;
|
||||
const vName = entry.streetNameV?.name || null;
|
||||
const hNum = hName ? this.getStreetNumberByName(hName) : null;
|
||||
const vNum = vName ? this.getStreetNumberByName(vName) : null;
|
||||
|
||||
const t = tileType;
|
||||
// Regeln gemäß Vorgabe
|
||||
// Horizontal linke Seite:
|
||||
const hLeftTiles = new Set(['cornerbottomleft','cornertopleft','horizontal','tdown','tup','cross','fuelhorizontal','tleft']);
|
||||
// Horizontal rechte Seite:
|
||||
const hRightTiles = new Set(['cornerbottomright','cornertopright','tright']);
|
||||
// Vertical oben:
|
||||
const vTopTiles = new Set(['cornertopleft','cornertopright','vertical','tup','tleft','tright','cross','fuelvertical']);
|
||||
// Vertical unten:
|
||||
const vBottomTiles = new Set(['cornerbottomleft','cornerbottomright','tdown']);
|
||||
|
||||
// Zusätzliche Kanten-spezifische Regeln aus der Einleitung
|
||||
// cornerbottomright: horizontal rechts, vertical unten
|
||||
// cornerbottomleft: horizontal links, vertical unten
|
||||
// ... ist bereits über Sets abgedeckt
|
||||
|
||||
if (hNum !== null && hName && !drawnNames.has(hName)) {
|
||||
const hx = hRightTiles.has(t) ? right : left;
|
||||
const hy = y + size * 0.5 + 1; // 1px nach unten
|
||||
const isSelH = (this.selectedStreetName && this.selectedStreetName === hName);
|
||||
ctx.fillStyle = isSelH ? '#ffffff' : '#111';
|
||||
ctx.font = isSelH ? `bold ${baseFontSize}px sans-serif` : `${baseFontSize}px sans-serif`;
|
||||
ctx.fillText(String(hNum), hx, hy);
|
||||
drawnNames.add(hName);
|
||||
}
|
||||
|
||||
if (vNum !== null && vName && !drawnNames.has(vName)) {
|
||||
const vx = x + size * 0.5;
|
||||
const vy = vTopTiles.has(t) ? top : (vBottomTiles.has(t) ? bottom : top);
|
||||
const isSelV = (this.selectedStreetName && this.selectedStreetName === vName);
|
||||
ctx.fillStyle = isSelV ? '#ffffff' : '#111';
|
||||
ctx.font = isSelV ? `bold ${baseFontSize}px sans-serif` : `${baseFontSize}px sans-serif`;
|
||||
ctx.fillText(String(vNum), vx, vy);
|
||||
drawnNames.add(vName);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
// Mapping zwischen Spiel-Tile-Namen (lowercase) und streetCoordinates.json (camelCase)
|
||||
mapTileTypeToStreetCoordinates(tileType) {
|
||||
const mapping = {
|
||||
@@ -256,7 +373,11 @@ export default {
|
||||
'vertical': 'vertical',
|
||||
'cross': 'cross',
|
||||
'fuelhorizontal': 'fuelHorizontal',
|
||||
'fuelvertical': 'fuelVertical'
|
||||
'fuelvertical': 'fuelVertical',
|
||||
'tleft': 'tLeft',
|
||||
'tright': 'tRight',
|
||||
'tup': 'tUp',
|
||||
'tdown': 'tDown'
|
||||
};
|
||||
return mapping[tileType] || tileType;
|
||||
},
|
||||
@@ -588,9 +709,9 @@ export default {
|
||||
this.lastSteerChange = currentTime;
|
||||
}
|
||||
|
||||
// Aktualisiere Position (1/4 der ursprünglichen Bewegung)
|
||||
this.taxi.x += Math.cos(this.taxi.angle) * this.taxi.speed * 0.1;
|
||||
this.taxi.y += Math.sin(this.taxi.angle) * this.taxi.speed * 0.1;
|
||||
// Aktualisiere Position (+20% Geschwindigkeit)
|
||||
this.taxi.x += Math.cos(this.taxi.angle) * this.taxi.speed * 0.12;
|
||||
this.taxi.y += Math.sin(this.taxi.angle) * this.taxi.speed * 0.12;
|
||||
|
||||
// Aktualisiere aktuelle Tile-Position
|
||||
this.updateCurrentTile();
|
||||
@@ -995,9 +1116,12 @@ export default {
|
||||
if (this.tiles.images[tileType]) {
|
||||
this.ctx.drawImage(this.tiles.images[tileType], 0, 0, tileSize, tileSize);
|
||||
}
|
||||
// Straßennummern auf Haupt-Canvas zeichnen
|
||||
this.drawStreetNumbersOnMainCanvas(this.ctx, tileSize, tileType, this.currentTile.col, this.currentTile.row);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// Fallback: Zeichne Standard-Tile wenn keine Map geladen ist
|
||||
const tileType = 'cornertopleft'; // Standard-Tile
|
||||
const streetTileType = this.mapTileTypeToStreetCoordinates(tileType);
|
||||
@@ -1009,8 +1133,53 @@ export default {
|
||||
if (this.tiles.images[tileType]) {
|
||||
this.ctx.drawImage(this.tiles.images[tileType], 0, 0, tileSize, tileSize);
|
||||
}
|
||||
// Straßennummern auch im Fallback dezent zeichnen (Spalten/Zeilen 0)
|
||||
this.drawStreetNumbersOnMainCanvas(this.ctx, tileSize, tileType, 0, 0);
|
||||
}
|
||||
},
|
||||
|
||||
drawStreetNumbersOnMainCanvas(ctx, size, tileType, col, row) {
|
||||
if (!this.currentMap || !Array.isArray(this.currentMap.tileStreets)) return;
|
||||
const absCol = col + (this.currentMap.offsetX || 0);
|
||||
const absRow = row + (this.currentMap.offsetY || 0);
|
||||
const entry = this.currentMap.tileStreets.find(ts => ts.x === absCol && ts.y === absRow);
|
||||
if (!entry) return;
|
||||
|
||||
ctx.save();
|
||||
ctx.fillStyle = 'rgba(10,10,10,0.8)';
|
||||
ctx.font = `${Math.max(11, Math.floor(size * 0.06) - 1)}px sans-serif`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
const left = size * 0.2;
|
||||
const right = size * 0.8;
|
||||
const top = size * 0.2;
|
||||
const bottom = size * 0.8;
|
||||
|
||||
const hName = entry.streetNameH?.name || null;
|
||||
const vName = entry.streetNameV?.name || null;
|
||||
const hNum = hName ? this.getStreetNumberByName(hName) : null;
|
||||
const vNum = vName ? this.getStreetNumberByName(vName) : null;
|
||||
|
||||
const t = tileType;
|
||||
const hLeftTiles = new Set(['cornerbottomleft','cornertopleft','horizontal','tdown','tup','cross','fuelhorizontal','tleft']);
|
||||
const hRightTiles = new Set(['cornerbottomright','cornertopright','tright']);
|
||||
const vTopTiles = new Set(['cornertopleft','cornertopright','vertical','tup','tleft','tright','cross','fuelvertical']);
|
||||
const vBottomTiles = new Set(['cornerbottomleft','cornerbottomright','tdown']);
|
||||
|
||||
if (hNum !== null) {
|
||||
const hx = hRightTiles.has(t) ? right : left;
|
||||
const hy = size * 0.5 + 1; // 1px nach unten
|
||||
ctx.fillText(String(hNum), hx, hy);
|
||||
}
|
||||
if (vNum !== null) {
|
||||
const vx = size * 0.5;
|
||||
const vy = vTopTiles.has(t) ? top : (vBottomTiles.has(t) ? bottom : top);
|
||||
ctx.fillText(String(vNum), vx, vy);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
getTileType(row, col, rows, cols) {
|
||||
// Ecken
|
||||
@@ -1133,12 +1302,14 @@ export default {
|
||||
async loadTiles() {
|
||||
const tileNames = [
|
||||
'cornerbottomleft', 'cornerbottomright', 'cornertopleft', 'cornertopright',
|
||||
'cross', 'fuelhorizontal', 'fuelvertical', 'horizontal', 'vertical'
|
||||
'cross', 'fuelhorizontal', 'fuelvertical', 'horizontal', 'vertical',
|
||||
'tleft', 'tright', 'tup', 'tdown'
|
||||
];
|
||||
|
||||
const mapTileNames = [
|
||||
'map-cornerbottomleft', 'map-cornerbottomright', 'map-cornertopleft', 'map-cornertopright',
|
||||
'map-cross', 'map-fuelhorizontal', 'map-fuelvertical', 'map-horizontal', 'map-vertical'
|
||||
'map-cross', 'map-fuelhorizontal', 'map-fuelvertical', 'map-horizontal', 'map-vertical',
|
||||
'map-tleft', 'map-tright', 'map-tup', 'map-tdown'
|
||||
];
|
||||
|
||||
// Lade normale Tiles
|
||||
@@ -1173,12 +1344,11 @@ export default {
|
||||
async loadMaps() {
|
||||
try {
|
||||
const response = await apiClient.get('/api/taxi-maps/maps');
|
||||
this.maps = response.data.data || [];
|
||||
this.maps = (response.data.data || []).map(m => this.buildMapDataFromTiles(m));
|
||||
|
||||
// Verwende die erste verfügbare Map als Standard
|
||||
if (this.maps.length > 0) {
|
||||
this.currentMap = this.maps[0];
|
||||
this.selectedMap = this.maps[0]; // Auch selectedMap setzen
|
||||
this.selectedMapId = this.maps[0].id;
|
||||
|
||||
// Canvas-Größe an geladene Map anpassen
|
||||
@@ -1198,7 +1368,7 @@ export default {
|
||||
// Wechsle zur ausgewählten Map
|
||||
const selectedMap = this.maps.find(map => map.id === this.selectedMapId);
|
||||
if (selectedMap) {
|
||||
this.currentMap = selectedMap;
|
||||
this.currentMap = this.buildMapDataFromTiles({ ...selectedMap });
|
||||
// console.log('Gewechselt zu Map:', selectedMap);
|
||||
|
||||
// Canvas-Größe an neue Map anpassen
|
||||
@@ -1248,6 +1418,9 @@ export default {
|
||||
const offsetX = (canvas.width - cols * tileSize) / 2;
|
||||
const offsetY = (canvas.height - rows * tileSize) / 2;
|
||||
|
||||
// Set, um pro Straßennamen nur eine Nummer zu zeichnen
|
||||
const drawnNames = new Set();
|
||||
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const x = offsetX + col * tileSize;
|
||||
@@ -1261,6 +1434,14 @@ export default {
|
||||
if (this.tiles.images[mapTileType]) {
|
||||
ctx.drawImage(this.tiles.images[mapTileType], x, y, tileSize, tileSize);
|
||||
}
|
||||
|
||||
// Straßensegment der ausgewählten Straße hervorheben
|
||||
const absCol = col + (this.currentMap.offsetX || 0);
|
||||
const absRow = row + (this.currentMap.offsetY || 0);
|
||||
this.drawStreetFillOnMinimap(ctx, x, y, tileSize, tileType, absCol, absRow);
|
||||
|
||||
// Straßennamen-Nummern zeichnen, basierend auf tileStreets (nur einmal pro Name)
|
||||
this.drawStreetNumbersOnMinimap(ctx, x, y, tileSize, tileType, absCol, absRow, drawnNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1289,6 +1470,8 @@ export default {
|
||||
if (this.tiles.images[mapTileType]) {
|
||||
ctx.drawImage(this.tiles.images[mapTileType], x, y, tileSize, tileSize);
|
||||
}
|
||||
|
||||
// Keine echten Namen im Fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1336,12 +1519,86 @@ export default {
|
||||
ctx.moveTo(taxiX, taxiY);
|
||||
ctx.lineTo(endX, endY);
|
||||
ctx.stroke();
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
toggleIcon() {
|
||||
return this.isStatsExpanded ? '−' : '+';
|
||||
},
|
||||
buildMapDataFromTiles(map) {
|
||||
if (!map || !Array.isArray(map.tiles) || map.tiles.length === 0) {
|
||||
map.mapData = null;
|
||||
map.offsetX = 0;
|
||||
map.offsetY = 0;
|
||||
return map;
|
||||
}
|
||||
const xs = map.tiles.map(t => t.x);
|
||||
const ys = map.tiles.map(t => t.y);
|
||||
const minX = Math.min(...xs);
|
||||
const maxX = Math.max(...xs);
|
||||
const minY = Math.min(...ys);
|
||||
const maxY = Math.max(...ys);
|
||||
const width = (maxX - minX + 1) || 1;
|
||||
const height = (maxY - minY + 1) || 1;
|
||||
const grid = Array.from({ length: height }, () => Array(width).fill(null));
|
||||
for (const t of map.tiles) {
|
||||
const col = t.x - minX;
|
||||
const row = t.y - minY;
|
||||
if (row >= 0 && row < height && col >= 0 && col < width) {
|
||||
grid[row][col] = t.tileType;
|
||||
}
|
||||
}
|
||||
map.mapData = grid;
|
||||
map.offsetX = minX;
|
||||
map.offsetY = minY;
|
||||
return map;
|
||||
},
|
||||
onSelectStreet(name) {
|
||||
this.selectedStreetName = (this.selectedStreetName === name) ? null : name;
|
||||
},
|
||||
// Variante B: gesamte Straßenfläche im Tile halbtransparent rot füllen
|
||||
drawStreetFillOnMinimap(ctx, x, y, size, tileType, col, row) {
|
||||
if (!this.selectedStreetName || !this.currentMap || !Array.isArray(this.currentMap.tileStreets)) return;
|
||||
const entry = this.currentMap.tileStreets.find(ts => ts.x === col && ts.y === row);
|
||||
if (!entry) return;
|
||||
|
||||
const wantH = entry.streetNameH?.name === this.selectedStreetName;
|
||||
const wantV = entry.streetNameV?.name === this.selectedStreetName;
|
||||
if (!wantH && !wantV) return;
|
||||
|
||||
const streetTileType = this.mapTileTypeToStreetCoordinates(tileType);
|
||||
const regions = streetCoordinates.getDriveableRegions(streetTileType, size);
|
||||
if (!regions || regions.length === 0) return;
|
||||
|
||||
// Banddicke exakt 12px (bei Referenz 50px) skaliert auf aktuelle size
|
||||
const scale = size / 50;
|
||||
const bandThickness = 12 * scale; // px
|
||||
|
||||
// Clip: Tile-Rechteck MINUS Hindernis-Regionen => übrig bleibt die graue Straßenfläche
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
// 1) volles Tile
|
||||
ctx.rect(x, y, size, size);
|
||||
// 2) Hindernis-Regionen hinzufügen (EvenOdd)
|
||||
for (const region of regions) {
|
||||
if (!region || region.length === 0) continue;
|
||||
ctx.moveTo(x + region[0].x, y + region[0].y);
|
||||
for (let i = 1; i < region.length; i++) {
|
||||
ctx.lineTo(x + region[i].x, y + region[i].y);
|
||||
}
|
||||
ctx.closePath();
|
||||
}
|
||||
// EvenOdd erhält Fläche außerhalb der Regionen (Straße)
|
||||
ctx.clip('evenodd');
|
||||
|
||||
ctx.fillStyle = '#ff4d4d';
|
||||
const marginRight = 1; // rechts 1px kürzen
|
||||
const marginBottom = 1; // unten 1px kürzen
|
||||
if (wantH) {
|
||||
const by = y + size * 0.5 - bandThickness / 2;
|
||||
ctx.fillRect(x, by, size, bandThickness);
|
||||
}
|
||||
if (wantV) {
|
||||
const bx = x + size * 0.5 - bandThickness / 2;
|
||||
ctx.fillRect(bx, y, bandThickness, size);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1793,4 +2050,11 @@ export default {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.legend-street-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
.legend-street-item.selected {
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user