158 lines
4.1 KiB
Vue
158 lines
4.1 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, ref, watch } from 'vue'
|
|
import { Container, Sprite } from 'pixi.js'
|
|
import { useTextures } from './composables/useTextures'
|
|
import { useWorldStore } from '../../stores/world'
|
|
import type { RegionSummary } from '../../types/core'
|
|
|
|
const TILE_SIZE = 64
|
|
const mapContainer = ref<Container>()
|
|
const { textures, isLoaded, loadSectTexture } = useTextures()
|
|
const worldStore = useWorldStore()
|
|
const regionStyleCache = new Map<string, Record<string, unknown>>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'mapLoaded', payload: { width: number, height: number }): void
|
|
(e: 'regionSelected', payload: { type: 'region'; id: string; name?: string }): void
|
|
}>()
|
|
|
|
onMounted(() => {
|
|
// Map data is loaded by worldStore.initialize() in App.vue or similar
|
|
if (isLoaded.value && worldStore.isLoaded) {
|
|
renderMap()
|
|
}
|
|
})
|
|
|
|
watch(
|
|
() => [isLoaded.value, worldStore.isLoaded],
|
|
([texturesReady, mapReady]) => {
|
|
if (texturesReady && mapReady) {
|
|
renderMap()
|
|
}
|
|
}
|
|
)
|
|
|
|
async function renderMap() {
|
|
if (!mapContainer.value || !worldStore.mapData.length) return
|
|
|
|
await preloadSectTextures()
|
|
mapContainer.value.removeChildren()
|
|
|
|
const rows = worldStore.mapData.length
|
|
const cols = worldStore.mapData[0]?.length ?? 0
|
|
|
|
for (let y = 0; y < rows; y++) {
|
|
for (let x = 0; x < cols; x++) {
|
|
const type = worldStore.mapData[y][x]
|
|
|
|
let tex = textures.value[type]
|
|
|
|
if (type === 'SECT') {
|
|
tex = resolveSectTexture(x, y) ?? textures.value['CITY']
|
|
}
|
|
|
|
if (!tex) {
|
|
tex = textures.value['PLAIN']
|
|
}
|
|
|
|
if (!tex) continue
|
|
|
|
const sprite = new Sprite(tex)
|
|
sprite.x = x * TILE_SIZE
|
|
sprite.y = y * TILE_SIZE
|
|
// 开启像素取整,消除 Tile 之间的黑边缝隙
|
|
sprite.roundPixels = true
|
|
|
|
sprite.width = TILE_SIZE
|
|
sprite.height = TILE_SIZE
|
|
|
|
sprite.eventMode = 'none'
|
|
mapContainer.value.addChild(sprite)
|
|
}
|
|
}
|
|
|
|
emit('mapLoaded', {
|
|
width: cols * TILE_SIZE,
|
|
height: rows * TILE_SIZE
|
|
})
|
|
}
|
|
|
|
async function preloadSectTextures() {
|
|
const regions = Array.from(worldStore.regions.values());
|
|
const sectNames = Array.from(
|
|
new Set(
|
|
regions
|
|
.filter(region => region.type === 'sect' && region.sect_name)
|
|
.map(region => region.sect_name as string)
|
|
)
|
|
)
|
|
await Promise.all(sectNames.map(name => loadSectTexture(name)))
|
|
}
|
|
|
|
function resolveSectTexture(x: number, y: number) {
|
|
const regions = Array.from(worldStore.regions.values());
|
|
const region = regions.find(r =>
|
|
r.type === 'sect' && Math.abs(r.x - x) < 3 && Math.abs(r.y - y) < 3
|
|
)
|
|
if (region?.sect_name) {
|
|
const key = `SECT_${region.sect_name}`
|
|
return textures.value[key] ?? null
|
|
}
|
|
return null
|
|
}
|
|
|
|
function getRegionStyle(type: string) {
|
|
if (regionStyleCache.has(type)) {
|
|
return regionStyleCache.get(type)
|
|
}
|
|
const style = {
|
|
fontFamily: '"Microsoft YaHei", sans-serif',
|
|
fontSize: type === 'sect' ? 85 : 100,
|
|
fill: type === 'sect' ? '#ffcc00' : (type === 'city' ? '#ccffcc' : '#ffffff'),
|
|
stroke: { color: '#000000', width: 7, join: 'round' },
|
|
align: 'center',
|
|
dropShadow: {
|
|
color: '#000000',
|
|
blur: 4,
|
|
angle: Math.PI / 6,
|
|
distance: 4,
|
|
alpha: 0.8
|
|
}
|
|
}
|
|
regionStyleCache.set(type, style)
|
|
return style
|
|
}
|
|
|
|
function handleRegionSelect(region: RegionSummary) {
|
|
emit('regionSelected', {
|
|
type: 'region',
|
|
id: String(region.id),
|
|
name: region.name
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<container>
|
|
<!-- Tile Layer -->
|
|
<container ref="mapContainer" />
|
|
|
|
<!-- Region Labels Layer (Above tiles) -->
|
|
<container :z-index="200">
|
|
<!-- @vue-ignore -->
|
|
<text
|
|
v-for="r in Array.from(worldStore.regions.values())"
|
|
:key="r.name"
|
|
:text="r.name"
|
|
:x="r.x * TILE_SIZE + TILE_SIZE / 2"
|
|
:y="r.y * TILE_SIZE + TILE_SIZE * 1.5"
|
|
:anchor="0.5"
|
|
:style="getRegionStyle(r.type)"
|
|
event-mode="static"
|
|
cursor="pointer"
|
|
@pointertap="handleRegionSelect(r)"
|
|
/>
|
|
</container>
|
|
</container>
|
|
</template>
|