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