feat(game/ui/MapTransitionModal): 新建地图切换过渡弹窗,3秒后自动进入下一关
This commit is contained in:
187
game/ui/MapTransitionModal.ts
Normal file
187
game/ui/MapTransitionModal.ts
Normal 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> | 获得奖励: <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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user