refactor: extract particles, effects, projectiles from FightScene
Move spawnSparks, spawnBulletHoles, spawnExhaust to fight/particles.ts, glitchRGB, scanlineGlitch, vhsTracking, dimensionalShift to fight/effects.ts, and spawnProjectile to fight/projectiles.ts. All accept FightContext as first param. FightScene.ts reduced by ~180 lines. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
020ab3f1f8
commit
08786a9b06
+22
-179
@@ -2,9 +2,12 @@ import kaplay from 'kaplay'
|
||||
import type { GameObj, SpriteComp, PosComp, ScaleComp, AnchorComp, OpacityComp, ColorComp, RotateComp, ZComp, LoadSpriteOpt } from 'kaplay'
|
||||
|
||||
import { generateSpriteSheet, generateJudgeSpriteSheet, generateHumanSpriteSheet, generateHumanFighterSpriteSheet, getBotColors, FRAME_SIZE, MAX_FRAMES, TOTAL_ROWS, ANIMATIONS, JUDGE_ANIMATIONS, JUDGE_MAX_FRAMES, JUDGE_ROWS, HUMAN_ANIMATIONS, HUMAN_MAX_FRAMES, HUMAN_ROWS, type SpriteCustomization } from './sprites'
|
||||
import type { Fighter, FightSceneConfig, RoundEvent } from './fight/types'
|
||||
import type { Fighter, FightSceneConfig, RoundEvent, FightContext } from './fight/types'
|
||||
export type { FightSceneConfig, RoundEvent } from './fight/types'
|
||||
import { ARENA_THEMES, spriteAnims, CHALLENGE_THEMED, TIER_ULTIMATES, CREATOR_MOVES, CREATOR_ULTIMATES } from './fight/constants'
|
||||
import { spawnSparks as _spawnSparks, spawnBulletHoles as _spawnBulletHoles, spawnExhaust as _spawnExhaust } from './fight/particles'
|
||||
import { glitchRGB as _glitchRGB, scanlineGlitch as _scanlineGlitch, vhsTracking as _vhsTracking, dimensionalShift as _dimensionalShift } from './fight/effects'
|
||||
import { spawnProjectile as _spawnProjectile } from './fight/projectiles'
|
||||
import {
|
||||
sfxPunch, sfxKick, sfxSpecial, sfxCritical, sfxGunshot, sfxBulletHit,
|
||||
sfxJetpack, sfxExplosion, sfxKO, sfxWin, sfxWinAnnounce, sfxPerfect,
|
||||
@@ -274,120 +277,32 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
cleanupTimers.delete(id)
|
||||
}
|
||||
// Scale factor so sprites shrink on small canvases (reference: 500px tall)
|
||||
|
||||
// Fight context for extracted modules
|
||||
const fightCtx: FightContext = {
|
||||
k, W, H, theme,
|
||||
trackedTimeout, trackedInterval, clearTracked,
|
||||
safeColor: (color: string) => safeColor(k, color),
|
||||
}
|
||||
|
||||
// Wrappers for extracted particle/effect/projectile functions
|
||||
const spawnSparks = (x: number, y: number, count: number, color: string) => _spawnSparks(fightCtx, x, y, count, color)
|
||||
const spawnBulletHoles = (x: number, y: number, count: number) => _spawnBulletHoles(fightCtx, x, y, count)
|
||||
const spawnExhaust = (x: number, y: number, duration: number) => _spawnExhaust(fightCtx, x, y, duration)
|
||||
const spawnProjectile = (fromX: number, fromY: number, toX: number, toY: number, color: string, size: number = 8) => _spawnProjectile(fightCtx, fromX, fromY, toX, toY, color, size)
|
||||
const glitchRGB = (duration: number = 0.2) => _glitchRGB(fightCtx, duration)
|
||||
const scanlineGlitch = (duration: number = 0.3) => _scanlineGlitch(fightCtx, duration)
|
||||
const vhsTracking = (duration: number = 0.4) => _vhsTracking(fightCtx, duration)
|
||||
const dimensionalShift = (duration: number = 0.6) => _dimensionalShift(fightCtx, duration)
|
||||
|
||||
const SF = Math.min(1, H / 500)
|
||||
const GROUND_Y = H * 0.78
|
||||
const HOME_A = W * 0.28
|
||||
const HOME_B = W * 0.72
|
||||
|
||||
// Particle helpers
|
||||
function spawnSparks(x: number, y: number, count: number, color: string) {
|
||||
const intense = count > 8
|
||||
for (let i = 0; i < count; i++) {
|
||||
const angle = Math.random() * Math.PI * 2
|
||||
const speed = (intense ? 150 : 100) + Math.random() * (intense ? 400 : 300)
|
||||
const size = (intense ? 3 : 2) + Math.random() * (intense ? 6 : 4)
|
||||
const rotSpeed = (Math.random() - 0.5) * 600
|
||||
const spark = k.add([
|
||||
k.rect(size, size * (0.5 + Math.random() * 0.5)),
|
||||
k.pos(x, y),
|
||||
k.color(safeColor(k,color)),
|
||||
k.opacity(1),
|
||||
k.z(20),
|
||||
k.rotate(Math.random() * 360),
|
||||
k.anchor('center'),
|
||||
{ vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed - 100, rotV: rotSpeed },
|
||||
])
|
||||
spark.onUpdate(() => {
|
||||
spark.pos.x += (spark as any).vx * k.dt()
|
||||
spark.pos.y += (spark as any).vy * k.dt()
|
||||
;(spark as any).vy += 500 * k.dt() // gravity
|
||||
spark.angle += (spark as any).rotV * k.dt()
|
||||
spark.opacity -= 1.5 * k.dt()
|
||||
if (spark.opacity <= 0) spark.destroy()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function spawnBulletHoles(x: number, y: number, count: number) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
const hole = k.add([
|
||||
k.circle(3 + Math.random() * 3),
|
||||
k.pos(x + (Math.random() - 0.5) * 40, y + (Math.random() - 0.5) * 60),
|
||||
k.color(safeColor(k,'#000000')),
|
||||
k.opacity(0.8),
|
||||
k.z(9), // behind fighters
|
||||
])
|
||||
// Fade out after a bit
|
||||
trackedTimeout(() => {
|
||||
const fadeInterval = trackedInterval(() => {
|
||||
hole.opacity -= 0.02
|
||||
if (hole.opacity <= 0) { hole.destroy(); clearTracked(fadeInterval) }
|
||||
}, 50)
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
|
||||
function spawnExhaust(x: number, y: number, duration: number) {
|
||||
const endTime = performance.now() + duration * 1000
|
||||
const interval = trackedInterval(() => {
|
||||
if (performance.now() > endTime) { clearTracked(interval); return }
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const p = k.add([
|
||||
k.circle(3 + Math.random() * 5),
|
||||
k.pos(x + (Math.random() - 0.5) * 15, y),
|
||||
k.color(safeColor(k,Math.random() > 0.5 ? '#ff6600' : '#ffcc00')),
|
||||
k.opacity(0.8),
|
||||
k.z(8),
|
||||
])
|
||||
p.onUpdate(() => {
|
||||
p.pos.y += 100 * k.dt()
|
||||
p.pos.x += (Math.random() - 0.5) * 50 * k.dt()
|
||||
p.opacity -= 2.5 * k.dt()
|
||||
if (p.opacity <= 0) p.destroy()
|
||||
})
|
||||
}
|
||||
}, 40)
|
||||
return interval
|
||||
}
|
||||
|
||||
function spawnProjectile(fromX: number, fromY: number, toX: number, toY: number, color: string, size: number = 8): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const proj = k.add([
|
||||
k.circle(size),
|
||||
k.pos(fromX, fromY),
|
||||
k.color(safeColor(k,color)),
|
||||
k.opacity(1),
|
||||
k.z(15),
|
||||
])
|
||||
// Trail
|
||||
const trail = trackedInterval(() => {
|
||||
const t = k.add([
|
||||
k.circle(size * 0.6),
|
||||
k.pos(proj.pos.x, proj.pos.y),
|
||||
k.color(safeColor(k,color)),
|
||||
k.opacity(0.5),
|
||||
k.z(14),
|
||||
])
|
||||
t.onUpdate(() => { t.opacity -= 3 * k.dt(); if (t.opacity <= 0) t.destroy() })
|
||||
}, 30)
|
||||
|
||||
const dx = toX - fromX
|
||||
const dy = toY - fromY
|
||||
const dist = Math.sqrt(dx * dx + dy * dy)
|
||||
const speed = 800
|
||||
const dur = dist / speed
|
||||
|
||||
k.tween(0, 1, dur, (t) => {
|
||||
proj.pos.x = fromX + dx * t
|
||||
proj.pos.y = fromY + dy * t
|
||||
}, k.easings.linear).then(() => {
|
||||
clearTracked(trail)
|
||||
proj.destroy()
|
||||
spawnSparks(toX, toY, 8, color)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function spawnBullet(fromX: number, fromY: number, toX: number, toY: number): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
@@ -427,81 +342,9 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
|
||||
// === VISUAL CHAOS EFFECTS ===
|
||||
|
||||
// RGB split glitch — offsets red/blue channels using colored overlays
|
||||
function glitchRGB(duration: number = 0.2) {
|
||||
const layers = [
|
||||
{ color: '#ff0000', dx: 4 + Math.random() * 6, dy: -2 },
|
||||
{ color: '#0000ff', dx: -(4 + Math.random() * 6), dy: 2 },
|
||||
]
|
||||
for (const l of layers) {
|
||||
const overlay = k.add([
|
||||
k.rect(W, H), k.pos(l.dx, l.dy),
|
||||
k.color(safeColor(k,l.color)), k.opacity(0.12), k.z(55),
|
||||
])
|
||||
overlay.onUpdate(() => {
|
||||
overlay.pos.x = l.dx + (Math.random() - 0.5) * 4
|
||||
overlay.pos.y = l.dy + (Math.random() - 0.5) * 2
|
||||
})
|
||||
setTimeout(() => { if (overlay.exists()) overlay.destroy() }, duration * 1000)
|
||||
}
|
||||
}
|
||||
|
||||
// Scanline glitch — horizontal bands flicker
|
||||
function scanlineGlitch(duration: number = 0.3) {
|
||||
const lines: GameObj[] = []
|
||||
const count = 6 + Math.floor(Math.random() * 8)
|
||||
for (let i = 0; i < count; i++) {
|
||||
const ly = Math.random() * H
|
||||
const lh = 1 + Math.random() * 3
|
||||
const line = k.add([
|
||||
k.rect(W, lh), k.pos(0, ly),
|
||||
k.color(safeColor(k,Math.random() > 0.5 ? '#ffffff' : '#000000')),
|
||||
k.opacity(0.15 + Math.random() * 0.2), k.z(54),
|
||||
])
|
||||
line.onUpdate(() => {
|
||||
line.pos.x = (Math.random() - 0.5) * 8
|
||||
line.opacity = Math.random() > 0.3 ? 0.15 : 0
|
||||
})
|
||||
lines.push(line)
|
||||
}
|
||||
setTimeout(() => { lines.forEach(l => { if (l.exists()) l.destroy() }) }, duration * 1000)
|
||||
}
|
||||
|
||||
// VHS tracking distortion — horizontal offset bands
|
||||
function vhsTracking(duration: number = 0.4) {
|
||||
const bands: GameObj[] = []
|
||||
const bandCount = 3
|
||||
for (let i = 0; i < bandCount; i++) {
|
||||
const by = Math.random() * H
|
||||
const bh = 10 + Math.random() * 30
|
||||
const band = k.add([
|
||||
k.rect(W, bh), k.pos(0, by),
|
||||
k.color(safeColor(k,theme.accent)), k.opacity(0.06), k.z(53),
|
||||
])
|
||||
band.onUpdate(() => {
|
||||
band.pos.x = Math.sin(k.time() * 20 + i * 3) * 15
|
||||
band.pos.y += (Math.random() - 0.5) * 2
|
||||
})
|
||||
bands.push(band)
|
||||
}
|
||||
setTimeout(() => { bands.forEach(b => { if (b.exists()) b.destroy() }) }, duration * 1000)
|
||||
}
|
||||
|
||||
// Dimensional shift — background color/hue flash cycle
|
||||
function dimensionalShift(duration: number = 0.6) {
|
||||
const colors = ['#ff00ff', '#00ffff', '#ff0000', '#0000ff', '#ffff00']
|
||||
let idx = 0
|
||||
const overlay = k.add([
|
||||
k.rect(W, H), k.pos(0, 0),
|
||||
k.color(safeColor(k,colors[0])), k.opacity(0.08), k.z(1),
|
||||
])
|
||||
const interval = trackedInterval(() => {
|
||||
idx = (idx + 1) % colors.length
|
||||
overlay.color = safeColor(k,colors[idx])
|
||||
overlay.opacity = 0.05 + Math.random() * 0.06
|
||||
}, 80)
|
||||
trackedTimeout(() => { clearTracked(interval); if (overlay.exists()) overlay.destroy() }, duration * 1000)
|
||||
}
|
||||
|
||||
// Schizo cut — rapid zoom/position jitter simulating jump cuts
|
||||
async function schizoCut() {
|
||||
|
||||
Reference in New Issue
Block a user