caro5 / client /src /game /effects /WinEffect.ts
Pedro de Carvalho
Add game effect
dbb749a
Raw
History Blame Contribute Delete
4.36 kB
import { Container, Graphics, Ticker } from 'pixi.js'
import { CELL_SIZE } from 'shared/board.ts'
import { theme } from '../../theme.ts'
import { ParticleSystem } from './ParticleSystem.ts'
import type { CameraController } from '../CameraController.ts'
const ticker = Ticker.shared
export class WinEffect {
private effectLayer: Container
private particleSys: ParticleSystem
private camera: CameraController
private vw: number
private vh: number
private screenLayer: Container
private flashGraphics: Graphics
private highlightGraphics: Graphics | null = null
constructor(effectLayer: Container, particleSys: ParticleSystem, camera: CameraController, vw: number, vh: number, screenLayer: Container) {
this.effectLayer = effectLayer
this.particleSys = particleSys
this.camera = camera
this.vw = vw
this.vh = vh
this.screenLayer = screenLayer
this.flashGraphics = new Graphics()
}
clear(): void {
if (this.highlightGraphics) {
this.effectLayer.removeChild(this.highlightGraphics)
this.highlightGraphics.destroy()
this.highlightGraphics = null
}
}
play(cells: { x: number; y: number }[]): void {
if (cells.length === 0) return
this.clear()
this.cameraFocus(cells)
this.highlightLine(cells)
this.emitGold(cells)
this.screenFlash()
}
private cameraFocus(cells: { x: number; y: number }[]): void {
let sumX = 0, sumY = 0
for (const c of cells) { sumX += c.x; sumY += c.y }
const targetX = (sumX / cells.length) * CELL_SIZE + CELL_SIZE / 2
const targetY = (sumY / cells.length) * CELL_SIZE + CELL_SIZE / 2
const startX = this.camera.state.x
const startY = this.camera.state.y
let elapsed = 0
const dur = 0.3
let cb: () => void
const tick = (dt: number) => {
elapsed += dt
const t = Math.min(1, elapsed / dur)
this.camera.state.x = startX + (targetX - startX) * t
this.camera.state.y = startY + (targetY - startY) * t
this.camera.apply()
this.camera.triggerChange()
if (t >= 1) ticker.remove(cb)
}
if (ticker) {
cb = () => tick(ticker.deltaMS / 1000)
ticker.add(cb)
}
}
private highlightLine(cells: { x: number; y: number }[]): void {
const g = new Graphics()
this.highlightGraphics = g
this.effectLayer.addChild(g)
for (const c of cells) {
const lx = c.x * CELL_SIZE + CELL_SIZE / 2
const ly = c.y * CELL_SIZE + CELL_SIZE / 2
g.circle(lx, ly, CELL_SIZE * 0.5)
}
g.stroke({ width: 3, color: theme.winLineColor })
g.fill({ color: theme.winLineColor, alpha: 0.15 })
let elapsed = 0
const dur = 0.8
let cb: () => void
const tick = (dt: number) => {
elapsed += dt
const t = Math.min(1, elapsed / dur)
const pulse = 0.5 + 0.5 * Math.sin(t * Math.PI * 4)
g.alpha = 0.4 + 0.6 * pulse
if (t >= 1) {
g.alpha = 1
ticker.remove(cb)
}
}
if (ticker) {
cb = () => tick(ticker.deltaMS / 1000)
ticker.add(cb)
}
}
private emitGold(cells: { x: number; y: number }[]): void {
for (const c of cells) {
const lx = c.x * CELL_SIZE + CELL_SIZE / 2
const ly = c.y * CELL_SIZE + CELL_SIZE / 2
this.particleSys.emit({
count: 12,
x: lx,
y: ly,
color: theme.particleGold,
spread: 120,
speed: 150,
size: 3,
life: 0.8,
})
this.particleSys.emit({
count: 4,
x: lx,
y: ly,
color: theme.particleSpark,
spread: 80,
speed: 200,
size: 1.5,
life: 0.5,
})
}
}
private screenFlash(): void {
this.flashGraphics.clear()
this.flashGraphics.rect(0, 0, this.vw, this.vh)
this.flashGraphics.fill({ color: theme.screenFlash, alpha: 0.4 })
this.screenLayer.addChild(this.flashGraphics)
let elapsed = 0
const dur = 0.5
let cb: () => void
const tick = (dt: number) => {
elapsed += dt
const t = Math.min(1, elapsed / dur)
this.flashGraphics.alpha = 0.4 * (1 - t)
if (t >= 1) {
this.screenLayer.removeChild(this.flashGraphics)
this.flashGraphics.clear()
ticker.remove(cb)
}
}
if (ticker) {
cb = () => tick(ticker.deltaMS / 1000)
ticker.add(cb)
}
}
}