feat(game/ui/MapTransitionModal): 新建地图切换过渡弹窗,3秒后自动进入下一关

This commit is contained in:
Cloud Bot
2026-03-21 09:45:27 +00:00
parent 5f75895d41
commit 9f3b0bdbf1

View File

@@ -0,0 +1,187 @@
import type { MapConfig } from '../data/mapConfigs'
import { ALL_MAPS } from '../data/mapConfigs'
import { GameManager } from '../GameManager'
/**
* 地图切换过渡弹窗DOM 层,覆盖 canvas
* 显示当前地图通关信息3秒后自动关闭并触发下一张地图加载
*/
export class MapTransitionModal {
private container: HTMLElement | null = null
private autoCloseTimer: ReturnType<typeof setTimeout> | null = null
/**
* 显示地图过关弹窗
* @param clearedMap 刚通关的地图配置
* @param onNext 3秒后自动触发的回调加载下一张地图
*/
show(clearedMap: MapConfig, onNext: () => void): void {
this.cleanup()
const manager = GameManager.getInstance()
const nextMapIndex = manager.currentMapIndex + 1
const nextMap = ALL_MAPS[nextMapIndex]
const isFinalMap = !nextMap
this.createDOM(clearedMap, nextMap ?? null, isFinalMap, onNext)
}
private createDOM(
clearedMap: MapConfig,
nextMap: MapConfig | null,
isFinalMap: boolean,
onNext: () => void
): void {
const manager = GameManager.getInstance()
const kpi = manager.kpi
const hcReward = 100
const overlay = document.createElement('div')
overlay.id = 'map-transition-overlay'
overlay.style.cssText = `
position: fixed;
inset: 0;
background: rgba(0,0,0,0.85);
z-index: 9998;
display: flex;
align-items: center;
justify-content: center;
font-family: 'VT323', monospace;
animation: mtFadeIn 0.4s ease;
`
const card = document.createElement('div')
card.style.cssText = `
background: #0f1b2d;
border: 2px solid #7c3aed;
border-radius: 16px;
padding: 36px 48px;
max-width: 520px;
width: 90%;
box-shadow: 0 0 60px rgba(124,58,237,0.6);
text-align: center;
animation: mtSlideIn 0.4s ease;
`
// 过关标题
const titleEl = document.createElement('div')
titleEl.textContent = isFinalMap ? '全部通关!最终胜利!' : `过关!`
titleEl.style.cssText = `
font-size: 36px;
color: #fbbf24;
margin-bottom: 8px;
letter-spacing: 3px;
`
card.appendChild(titleEl)
// 地图名称
const mapNameEl = document.createElement('div')
mapNameEl.textContent = `${clearedMap.name}」已清场`
mapNameEl.style.cssText = `
font-size: 22px;
color: #a78bfa;
margin-bottom: 20px;
`
card.appendChild(mapNameEl)
// 分割线
const hr = document.createElement('div')
hr.style.cssText = `
height: 1px;
background: linear-gradient(90deg, transparent, #7c3aed, transparent);
margin-bottom: 16px;
`
card.appendChild(hr)
// KPI & 奖励
const statsEl = document.createElement('div')
statsEl.innerHTML = `KPI: <span style="color:#34d399">${kpi}%</span> &nbsp;|&nbsp; 获得奖励: <span style="color:#fbbf24">+${hcReward} HC</span>`
statsEl.style.cssText = `
font-size: 20px;
color: #e2e8f0;
margin-bottom: 24px;
`
card.appendChild(statsEl)
// 下一关提示 / 最终胜利提示
const nextEl = document.createElement('div')
if (isFinalMap) {
nextEl.textContent = '恭喜完成全部关卡!你是真正的打工人传奇!'
nextEl.style.cssText = `
font-size: 18px;
color: #fbbf24;
background: rgba(251,191,36,0.1);
border: 1px solid #fbbf24;
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 20px;
`
} else {
nextEl.innerHTML = `<span style="color:#9ca3af">下一关:</span> <span style="color:#60a5fa">${nextMap!.name}</span>`
nextEl.style.cssText = `
font-size: 20px;
color: #e2e8f0;
background: rgba(96,165,250,0.1);
border: 1px solid #3b82f6;
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 20px;
`
}
card.appendChild(nextEl)
// 倒计时提示
const countdownEl = document.createElement('div')
countdownEl.id = 'mt-countdown'
countdownEl.textContent = isFinalMap ? '3秒后返回主菜单...' : '3秒后自动进入下一关...'
countdownEl.style.cssText = `
font-size: 16px;
color: #6b7280;
`
card.appendChild(countdownEl)
// 注入动画
const style = document.createElement('style')
style.textContent = `
@keyframes mtFadeIn { from { opacity: 0 } to { opacity: 1 } }
@keyframes mtSlideIn { from { transform: scale(0.85); opacity: 0 } to { transform: scale(1); opacity: 1 } }
`
document.head.appendChild(style)
overlay.appendChild(card)
document.body.appendChild(overlay)
this.container = overlay
// 给 HC 奖励
manager.addHC(hcReward)
// 3秒自动触发
let remaining = 3
const tick = () => {
remaining--
const el = document.getElementById('mt-countdown')
if (el) el.textContent = isFinalMap
? `${remaining}秒后返回主菜单...`
: `${remaining}秒后自动进入下一关...`
}
const interval = setInterval(tick, 1000)
this.autoCloseTimer = setTimeout(() => {
clearInterval(interval)
this.cleanup()
onNext()
}, 3000)
}
cleanup(): void {
if (this.autoCloseTimer !== null) {
clearTimeout(this.autoCloseTimer)
this.autoCloseTimer = null
}
const el = document.getElementById('map-transition-overlay')
if (el) el.remove()
this.container = null
}
destroy(): void {
this.cleanup()
}
}