Files
cmr-mini/miniprogram/game/feedback/uiEffectDirector.ts

195 lines
5.6 KiB
TypeScript

import { type GameEffect } from '../core/gameResult'
import {
DEFAULT_GAME_UI_EFFECTS_CONFIG,
type FeedbackCueKey,
type GameUiEffectsConfig,
type UiContentCardMotion,
type UiMapPulseMotion,
type UiPunchButtonMotion,
type UiPunchFeedbackMotion,
type UiStageMotion,
} from './feedbackConfig'
export interface UiEffectHost {
showPunchFeedback: (text: string, tone: 'neutral' | 'success' | 'warning', motionClass?: string) => void
showContentCard: (title: string, body: string, motionClass?: string) => void
setPunchButtonFxClass: (className: string) => void
showMapPulse: (controlId: string, motionClass?: string) => void
showStageFx: (className: string) => void
}
export class UiEffectDirector {
enabled: boolean
config: GameUiEffectsConfig
host: UiEffectHost
punchButtonMotionTimer: number
punchButtonMotionToggle: boolean
constructor(host: UiEffectHost, config: GameUiEffectsConfig = DEFAULT_GAME_UI_EFFECTS_CONFIG) {
this.enabled = true
this.host = host
this.config = config
this.punchButtonMotionTimer = 0
this.punchButtonMotionToggle = false
}
configure(config: GameUiEffectsConfig): void {
this.config = config
this.clearPunchButtonMotion()
}
setEnabled(enabled: boolean): void {
this.enabled = enabled
if (!enabled) {
this.clearPunchButtonMotion()
}
}
destroy(): void {
this.clearPunchButtonMotion()
}
clearPunchButtonMotion(): void {
if (this.punchButtonMotionTimer) {
clearTimeout(this.punchButtonMotionTimer)
this.punchButtonMotionTimer = 0
}
this.host.setPunchButtonFxClass('')
}
getPunchFeedbackMotionClass(motion: UiPunchFeedbackMotion): string {
if (motion === 'warning') {
return 'game-punch-feedback--fx-warning'
}
if (motion === 'success') {
return 'game-punch-feedback--fx-success'
}
if (motion === 'pop') {
return 'game-punch-feedback--fx-pop'
}
return ''
}
getContentCardMotionClass(motion: UiContentCardMotion): string {
if (motion === 'finish') {
return 'game-content-card--fx-finish'
}
if (motion === 'pop') {
return 'game-content-card--fx-pop'
}
return ''
}
getMapPulseMotionClass(motion: UiMapPulseMotion): string {
if (motion === 'ready') {
return 'map-stage__map-pulse--ready'
}
if (motion === 'finish') {
return 'map-stage__map-pulse--finish'
}
if (motion === 'control') {
return 'map-stage__map-pulse--control'
}
return ''
}
getStageMotionClass(motion: UiStageMotion): string {
if (motion === 'finish') {
return 'map-stage__stage-fx--finish'
}
return ''
}
triggerPunchButtonMotion(motion: UiPunchButtonMotion, durationMs: number): void {
if (motion === 'none') {
return
}
this.punchButtonMotionToggle = !this.punchButtonMotionToggle
const variant = this.punchButtonMotionToggle ? 'a' : 'b'
const className = motion === 'warning'
? `map-punch-button--fx-warning-${variant}`
: `map-punch-button--fx-ready-${variant}`
this.host.setPunchButtonFxClass(className)
if (this.punchButtonMotionTimer) {
clearTimeout(this.punchButtonMotionTimer)
}
this.punchButtonMotionTimer = setTimeout(() => {
this.punchButtonMotionTimer = 0
this.host.setPunchButtonFxClass('')
}, durationMs) as unknown as number
}
getCue(key: FeedbackCueKey) {
if (!this.enabled || !this.config.enabled) {
return null
}
const cue = this.config.cues[key]
if (!cue || !cue.enabled) {
return null
}
return cue
}
handleEffects(effects: GameEffect[]): void {
for (const effect of effects) {
if (effect.type === 'punch_feedback' && effect.tone === 'warning') {
const cue = this.getCue('punch_feedback:warning')
this.host.showPunchFeedback(
effect.text,
effect.tone,
cue ? this.getPunchFeedbackMotionClass(cue.punchFeedbackMotion) : '',
)
if (cue) {
this.triggerPunchButtonMotion(cue.punchButtonMotion, cue.durationMs)
}
continue
}
if (effect.type === 'control_completed') {
const key: FeedbackCueKey = effect.controlKind === 'start'
? 'control_completed:start'
: effect.controlKind === 'finish'
? 'control_completed:finish'
: 'control_completed:control'
const cue = this.getCue(key)
this.host.showPunchFeedback(
`完成 ${typeof effect.sequence === 'number' ? effect.sequence : effect.label}`,
'success',
cue ? this.getPunchFeedbackMotionClass(cue.punchFeedbackMotion) : '',
)
this.host.showContentCard(
effect.displayTitle,
effect.displayBody,
cue ? this.getContentCardMotionClass(cue.contentCardMotion) : '',
)
if (cue && cue.mapPulseMotion !== 'none') {
this.host.showMapPulse(effect.controlId, this.getMapPulseMotionClass(cue.mapPulseMotion))
}
if (cue && cue.stageMotion !== 'none') {
this.host.showStageFx(this.getStageMotionClass(cue.stageMotion))
}
continue
}
if (effect.type === 'guidance_state_changed' && effect.guidanceState === 'ready') {
const cue = this.getCue('guidance:ready')
if (cue) {
this.triggerPunchButtonMotion(cue.punchButtonMotion, cue.durationMs)
if (cue.mapPulseMotion !== 'none' && effect.controlId) {
this.host.showMapPulse(effect.controlId, this.getMapPulseMotionClass(cue.mapPulseMotion))
}
}
continue
}
if (effect.type === 'session_finished') {
this.clearPunchButtonMotion()
}
}
}
}