333 lines
8.8 KiB
JavaScript
333 lines
8.8 KiB
JavaScript
/**
|
|
* VFX SYSTEM - COMPLETE IMPLEMENTATION
|
|
* All 13 visual effects for game polish
|
|
*/
|
|
|
|
export class VFXSystem {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
|
|
// Existing particle systems
|
|
this.particleEmitters = new Map();
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
console.log('VFX System initialized');
|
|
}
|
|
|
|
// ==========================================
|
|
// EXISTING VFX (7/13) - Already implemented
|
|
// ==========================================
|
|
|
|
playParticleBurst(x, y, particleType, quantity = 10) {
|
|
const particles = this.scene.add.particles(particleType);
|
|
const emitter = particles.createEmitter({
|
|
x, y,
|
|
speed: { min: 50, max: 150 },
|
|
angle: { min: 0, max: 360 },
|
|
scale: { start: 1, end: 0 },
|
|
alpha: { start: 1, end: 0 },
|
|
lifespan: 800,
|
|
blendMode: 'ADD',
|
|
quantity: quantity
|
|
});
|
|
|
|
emitter.explode();
|
|
this.scene.time.delayedCall(1000, () => particles.destroy());
|
|
}
|
|
|
|
// ==========================================
|
|
// NEW VFX (6/13) - Implementing now
|
|
// ==========================================
|
|
|
|
/**
|
|
* 1. SCREEN SHAKE SYSTEM
|
|
*/
|
|
screenShake(duration = 200, intensity = 0.01) {
|
|
this.scene.cameras.main.shake(duration, intensity);
|
|
}
|
|
|
|
impactShake() {
|
|
this.screenShake(200, 0.01); // Heavy hit
|
|
}
|
|
|
|
explosionShake() {
|
|
this.screenShake(400, 0.015); // Explosion
|
|
}
|
|
|
|
subtleShake() {
|
|
this.screenShake(100, 0.005); // Tool use
|
|
}
|
|
|
|
buildingCollapseShake() {
|
|
this.screenShake(500, 0.02); // Building fall
|
|
}
|
|
|
|
/**
|
|
* 2. FLASH EFFECTS
|
|
*/
|
|
damageFlash() {
|
|
this.scene.cameras.main.flash(100, 255, 0, 0); // Red
|
|
}
|
|
|
|
healFlash() {
|
|
this.scene.cameras.main.flash(150, 0, 255, 0); // Green
|
|
}
|
|
|
|
levelUpFlash() {
|
|
this.scene.cameras.main.flash(300, 255, 215, 0); // Gold
|
|
}
|
|
|
|
raidWarningFlash() {
|
|
this.scene.cameras.main.flash(1000, 255, 0, 0, true); // Red pulse
|
|
}
|
|
|
|
victoryFlash() {
|
|
this.scene.cameras.main.flash(500, 255, 255, 255); // White
|
|
}
|
|
|
|
/**
|
|
* 3. FLOATING DAMAGE NUMBERS
|
|
*/
|
|
showFloatingText(x, y, text, color = '#FF0000', size = 24) {
|
|
const floatingText = this.scene.add.text(x, y, text, {
|
|
font: `bold ${size}px Arial`,
|
|
fill: color,
|
|
stroke: '#000000',
|
|
strokeThickness: 3
|
|
}).setOrigin(0.5);
|
|
|
|
this.scene.tweens.add({
|
|
targets: floatingText,
|
|
y: y - 40,
|
|
alpha: 0,
|
|
duration: 1000,
|
|
ease: 'Quad.easeOut',
|
|
onComplete: () => floatingText.destroy()
|
|
});
|
|
}
|
|
|
|
showDamage(x, y, amount, isCrit = false) {
|
|
const color = isCrit ? '#FFD700' : '#FF0000';
|
|
this.showFloatingText(x, y, `-${amount}`, color, isCrit ? 32 : 24);
|
|
}
|
|
|
|
showHeal(x, y, amount) {
|
|
this.showFloatingText(x, y, `+${amount}`, '#00FF00', 24);
|
|
}
|
|
|
|
showXP(x, y, amount) {
|
|
this.showFloatingText(x, y, `+${amount} XP`, '#00BFFF', 20);
|
|
}
|
|
|
|
showCurrency(x, y, amount) {
|
|
this.showFloatingText(x, y, `+${amount}¢`, '#FFD700', 20);
|
|
}
|
|
|
|
/**
|
|
* 4. HIT STUN / KNOCKBACK
|
|
*/
|
|
applyHitStun(target, damage, sourceX, sourceY) {
|
|
// Freeze movement
|
|
if (target.setVelocity) {
|
|
target.setVelocity(0, 0);
|
|
}
|
|
|
|
// Red tint flash
|
|
target.setTint(0xFF0000);
|
|
this.scene.time.delayedCall(100, () => {
|
|
if (target.active) target.clearTint();
|
|
});
|
|
|
|
// Calculate knockback
|
|
const angle = Phaser.Math.Angle.Between(sourceX, sourceY, target.x, target.y);
|
|
const knockbackForce = Math.min(50, damage * 2);
|
|
|
|
// Apply knockback tween
|
|
this.scene.tweens.add({
|
|
targets: target,
|
|
x: target.x + Math.cos(angle) * knockbackForce,
|
|
y: target.y + Math.sin(angle) * knockbackForce,
|
|
duration: 300,
|
|
ease: 'Quad.easeOut'
|
|
});
|
|
|
|
// Resume after stun
|
|
this.scene.time.delayedCall(200, () => {
|
|
if (target.resumeMovement) {
|
|
target.resumeMovement();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 5. BUILDING CONSTRUCTION PROGRESS
|
|
*/
|
|
createProgressBar(building) {
|
|
const bar = this.scene.add.graphics();
|
|
building.progressBar = bar;
|
|
this.updateProgressBar(building);
|
|
return bar;
|
|
}
|
|
|
|
updateProgressBar(building) {
|
|
if (!building.progressBar) return;
|
|
|
|
const bar = building.progressBar;
|
|
bar.clear();
|
|
|
|
const progress = (building.constructionProgress || 0) / 100;
|
|
|
|
// Interpolate color from red (0%) to green (100%)
|
|
const r = Math.floor(255 * (1 - progress));
|
|
const g = Math.floor(255 * progress);
|
|
const color = Phaser.Display.Color.GetColor(r, g, 0);
|
|
|
|
// Background
|
|
bar.fillStyle(0x000000, 0.8);
|
|
bar.fillRect(building.x - 50, building.y - 60, 100, 8);
|
|
|
|
// Progress fill
|
|
bar.fillStyle(color);
|
|
bar.fillRect(building.x - 50, building.y - 60, 100 * progress, 8);
|
|
|
|
// White border
|
|
bar.lineStyle(2, 0xFFFFFF);
|
|
bar.strokeRect(building.x - 50, building.y - 60, 100, 8);
|
|
}
|
|
|
|
removeProgressBar(building) {
|
|
if (building.progressBar) {
|
|
building.progressBar.destroy();
|
|
building.progressBar = null;
|
|
}
|
|
}
|
|
|
|
completionBurst(x, y) {
|
|
// Confetti explosion
|
|
const colors = [0xFFD700, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF00FF];
|
|
|
|
colors.forEach((color, index) => {
|
|
this.scene.time.delayedCall(index * 50, () => {
|
|
const particles = this.scene.add.particles('particle_sparkle');
|
|
particles.setTint(color);
|
|
|
|
const emitter = particles.createEmitter({
|
|
x, y,
|
|
speed: { min: 100, max: 200 },
|
|
angle: { min: 0, max: 360 },
|
|
scale: { start: 1, end: 0 },
|
|
alpha: { start: 1, end: 0 },
|
|
lifespan: 1000,
|
|
blendMode: 'ADD',
|
|
quantity: 10
|
|
});
|
|
|
|
emitter.explode();
|
|
this.scene.time.delayedCall(1200, () => particles.destroy());
|
|
});
|
|
});
|
|
|
|
// Victory flash
|
|
this.victoryFlash();
|
|
}
|
|
|
|
/**
|
|
* 6. DEATH ANIMATIONS
|
|
*/
|
|
zombieDeath(zombie) {
|
|
// Dust burst
|
|
this.playParticleBurst(zombie.x, zombie.y, 'particle_dust', 15);
|
|
|
|
// Fade + shrink
|
|
this.scene.tweens.add({
|
|
targets: zombie,
|
|
alpha: 0,
|
|
scale: 0.5,
|
|
tint: 0x000000,
|
|
duration: 800,
|
|
ease: 'Quad.easeIn',
|
|
onComplete: () => {
|
|
if (zombie.active) {
|
|
zombie.destroy();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
raiderDeath(raider) {
|
|
// Spin + fade
|
|
this.scene.tweens.add({
|
|
targets: raider,
|
|
angle: 180,
|
|
alpha: 0,
|
|
y: raider.y + 20,
|
|
duration: 1000,
|
|
ease: 'Quad.easeOut',
|
|
onComplete: () => {
|
|
if (raider.active) {
|
|
raider.destroy();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
playerDeath(player) {
|
|
// Dramatic death
|
|
this.damageFlash();
|
|
this.screenShake(800, 0.015);
|
|
|
|
this.scene.tweens.add({
|
|
targets: player,
|
|
alpha: 0,
|
|
scale: 0,
|
|
angle: 360,
|
|
duration: 1500,
|
|
ease: 'Quad.easeIn',
|
|
onComplete: () => {
|
|
// Trigger game over
|
|
this.scene.events.emit('player_death');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* UTILITY: Play effect by name
|
|
*/
|
|
playEffect(effectName, x, y, options = {}) {
|
|
switch (effectName) {
|
|
case 'impact':
|
|
this.impactShake();
|
|
this.playParticleBurst(x, y, 'particle_dust', 8);
|
|
break;
|
|
case 'explosion':
|
|
this.explosionShake();
|
|
this.playParticleBurst(x, y, 'particle_fire', 20);
|
|
break;
|
|
case 'heal':
|
|
this.healFlash();
|
|
this.playParticleBurst(x, y, 'particle_sparkle', 12);
|
|
break;
|
|
case 'level_up':
|
|
this.levelUpFlash();
|
|
this.playParticleBurst(x, y, 'particle_sparkle', 30);
|
|
break;
|
|
case 'victory':
|
|
this.completionBurst(x, y);
|
|
break;
|
|
default:
|
|
console.warn(`Unknown effect: ${effectName}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleanup
|
|
*/
|
|
destroy() {
|
|
this.particleEmitters.forEach(emitter => emitter.destroy());
|
|
this.particleEmitters.clear();
|
|
}
|
|
}
|