add dynamic water

This commit is contained in:
bridge
2025-12-06 15:31:15 +08:00
parent 4c75d647bd
commit e04be9f012
2 changed files with 128 additions and 11 deletions

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'
import { Container, Sprite } from 'pixi.js'
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { Container, Sprite, TilingSprite, Graphics, Ticker } from 'pixi.js'
import { useTextures } from './composables/useTextures'
import { useWorldStore } from '../../stores/world'
import { getRegionTextStyle } from '../../utils/mapStyles'
@@ -11,6 +11,11 @@ const mapContainer = ref<Container>()
const { textures, isLoaded, loadSectTexture, loadCityTexture } = useTextures()
const worldStore = useWorldStore()
// 动态水面相关变量
let ticker: Ticker | null = null
let seaLayer: TilingSprite | null = null
let waterLayer: TilingSprite | null = null
const emit = defineEmits<{
(e: 'mapLoaded', payload: { width: number, height: number }): void
(e: 'regionSelected', payload: { type: 'region'; id: string; name?: string }): void
@@ -23,6 +28,10 @@ onMounted(() => {
}
})
onUnmounted(() => {
cleanupTicker()
})
watch(
() => [isLoaded.value, worldStore.isLoaded],
([texturesReady, mapReady]) => {
@@ -32,20 +41,86 @@ watch(
}
)
function cleanupTicker() {
if (ticker) {
ticker.stop()
ticker.destroy()
ticker = null
}
}
async function renderMap() {
if (!mapContainer.value || !worldStore.mapData.length) return
await preloadRegionTextures()
// 清理旧资源
cleanupTicker()
mapContainer.value.removeChildren()
await preloadRegionTextures()
const rows = worldStore.mapData.length
const cols = worldStore.mapData[0]?.length ?? 0
const mapWidth = cols * TILE_SIZE
const mapHeight = rows * TILE_SIZE
// --- 1. 准备动态水面层 (TilingSprite + Mask) ---
// 创建海洋层 (底层水)
const seaTex = textures.value['SEA_FULL'] || textures.value['SEA']
// Pixi v8 style
seaLayer = new TilingSprite({
texture: seaTex,
width: mapWidth,
height: mapHeight
})
// 尝试缩小纹理比例,确保能看到花纹
seaLayer.tileScale.set(0.5, 0.5)
const seaMask = new Graphics()
seaLayer.mask = seaMask
// 创建淡水层 (河流/湖泊)
const waterTex = textures.value['WATER_FULL'] || textures.value['WATER']
waterLayer = new TilingSprite({
texture: waterTex,
width: mapWidth,
height: mapHeight
})
waterLayer.tileScale.set(0.5, 0.5)
const waterMask = new Graphics()
waterLayer.mask = waterMask
// 容器用于存放普通地块(非水面)
const groundContainer = new Container()
// --- 2. 遍历地图数据 ---
let hasSea = false
let hasWater = false
// 1. Render Base Tiles
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const type = worldStore.mapData[y][x]
// 处理特殊地块:海与水
if (type === 'SEA') {
seaMask.rect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE)
seaMask.fill(0xffffff)
hasSea = true
continue
}
if (type === 'WATER') {
waterMask.rect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE)
waterMask.fill(0xffffff)
hasWater = true
continue
}
// 处理普通地块
let tex = textures.value[type]
if (type === 'SECT') {
@@ -62,23 +137,63 @@ async function renderMap() {
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)
groundContainer.addChild(sprite)
}
}
// 2. Render Large Regions (2x2)
// v8 不需要 endFill
// seaMask.endFill()
// waterMask.endFill()
// --- 3. 组装图层 ---
// 顺序:海 -> 水 -> 陆地
if (hasSea && seaLayer) {
mapContainer.value.addChild(seaLayer)
// Mask 通常不需要 addChild但如果 mask 行为异常,可以尝试 addChild 但设置 renderable = false
// 在 v8 中通常不需要
mapContainer.value.addChild(seaMask)
}
if (hasWater && waterLayer) {
mapContainer.value.addChild(waterLayer)
mapContainer.value.addChild(waterMask)
}
mapContainer.value.addChild(groundContainer)
// --- 4. 启动动画 Ticker ---
if (hasSea || hasWater) {
ticker = new Ticker()
ticker.add((tickerInstance: any) => {
// v8: deltaMS / deltaTime
const speed = 0.5 * tickerInstance.deltaTime
if (hasSea && seaLayer) {
// 海洋稍微向左下流动
seaLayer.tilePosition.x -= speed * 0.5
seaLayer.tilePosition.y += speed * 0.5
}
if (hasWater && waterLayer) {
// 河流向右流动
waterLayer.tilePosition.x += speed
waterLayer.tilePosition.y += speed * 0.2
}
})
ticker.start()
}
// 5. Render Large Regions (2x2) - 宗门、城市等覆盖层
renderLargeRegions()
emit('mapLoaded', {
width: cols * TILE_SIZE,
height: rows * TILE_SIZE
width: mapWidth,
height: mapHeight
})
}

View File

@@ -33,6 +33,8 @@ export function useTextures() {
'PLAIN': '/assets/tiles/plain.png',
'WATER': '/assets/tiles/water.png',
'SEA': '/assets/tiles/sea.png',
'WATER_FULL': '/assets/tiles/water_full.jpg',
'SEA_FULL': '/assets/tiles/sea_full.jpg',
'MOUNTAIN': '/assets/tiles/mountain.png',
'FOREST': '/assets/tiles/forest.png',
'CITY': '/assets/tiles/city.png',