Files
novafarma/docs/VFX_EFFECTS_SYSTEM.md

12 KiB

🎬 VFX & INTERACTIVE EFFECTS SYSTEM

Project: Mrtva Dolina
Purpose: Visual feedback, emotional storytelling, player satisfaction
Style: ADHD-friendly, instant feedback, satisfying


1. AMNESIA EFFECT (Kai's Memory System)

Concept:

When Kai finds family heirlooms or Ana's belongings, trigger a blur-to-clear memory flashback with audio cues.

Visual Effect:

Stage 1: Discovery

// Player finds item (photo, Ana's necklace, family object)
onItemFound(familyItem) {
  // Show blurred image overlay
  showImage({
    src: familyItem.memoryImage,
    blur: 20,           // Heavy gaussian blur
    opacity: 0.8,
    fadeIn: 500ms
  });
  
  // Play mysterious sound
  playSound('memory_echo.mp3', volume: 0.5);
  
  // Show prompt: "E - Remember" (simple text)
  showPrompt("Pritisni E za spomin");
}

Stage 2: Memory Restoration

// Player presses E to "remember"
onRememberPressed() {
  // Blur clears gradually (2 seconds)
  animateBlur({
    from: 20,
    to: 0,
    duration: 2000ms,
    easing: 'easeOut'
  });
  
  // Play Ana's voice (short clip)
  playVoice('ana_flashback_01.mp3');
  // Example: "Kai, pomnš tisti dan...?" (short, 2-3 sec)
  
  // OR play emotional music sting
  playMusic('flashback_theme.mp3', fadeIn: 500ms);
  
  // Show caption (simple text)
  showCaption({
    text: "Spomin odklenjn...",  // ADHD-friendly: simple, typo intentional for domestic feel
    duration: 3000ms,
    color: '#FFD700'  // Gold
  });
  
  // Update quest log
  questLog.add('family_memory_' + itemID);
}

Stage 3: Reward

// After memory clears:
- Unlock new location on map (e.g., Capital City)
- Add clue to Ana's trail counter (X/50)
- Small stat boost (Hope +5%)
- Journal entry auto-saved

Audio Assets Needed:

  • memory_echo.mp3 - Mysterious hum/echo
  • flashback_theme.mp3 - Short emotional music (10-15 sec)
  • ana_voice_01-10.mp3 - 10 short voice clips from Ana

Visual Assets Needed:

  • Family photo (blurred + clear versions)
  • Ana's necklace (blurred + clear)
  • Old diary page (blurred + clear)
  • Kai's childhood toy (blurred + clear)

🌾 2. HARVESTING VFX (Stardew Valley Style)

Concept:

When player harvests crops, items bounce up with sparkles and fly into inventory. Instant satisfaction feedback.

Animation Sequence:

Step 1: Harvest Action

onCropHarvested(cropTile) {
  // 1. Crop disappears from tile
  cropTile.sprite.fadeOut(200ms);
  
  // 2. Item sprite bounces UP
  let itemSprite = createSprite(crop.harvestedItem);
  itemSprite.position = cropTile.position;
  
  // Bounce animation (arc trajectory)
  itemSprite.animate({
    y: cropTile.y - 40,  // Jump up 40px
    duration: 300ms,
    easing: 'easeOutQuad'
  });
  
  // 3. Sparkle particles
  emitParticles({
    position: cropTile.position,
    count: 8,
    sprite: 'sparkle_star.png',
    color: '#FFD700',    // Gold sparkles
    velocity: random(-50, 50),
    lifetime: 800ms,
    fadeOut: true
  });
  
  // 4. Sound effect
  playSound('harvest_pop.mp3', volume: 0.7);
  // Happy "pop" or "ding" sound
  
  // 5. Fly to inventory (UI corner)
  itemSprite.animate({
    x: inventoryIcon.x,
    y: inventoryIcon.y,
    duration: 600ms,
    easing: 'easeInQuad',
    onComplete: () => {
      // Add to inventory
      inventory.add(crop.harvestedItem);
      itemSprite.destroy();
      
      // Inventory icon pulse
      inventoryIcon.pulse();
    }
  });
}

Optional: Quality Tier Effects

Different sparkle colors for quality:

qualityColors = {
  basic: '#FFFFFF',      // White sparkles
  silver: '#C0C0C0',     // Silver sparkles
  gold: '#FFD700',       // Gold sparkles
  iridium: '#9D00FF'     // Purple sparkles (legendary)
}

Audio Assets Needed:

  • harvest_pop.mp3 - Satisfying "pop" sound
  • sparkle_ting.mp3 - Optional twinkle sound

Visual Assets Needed:

  • sparkle_star.png - 8x8px star particle
  • glow_particle.png - 4x4px glow dot

🐟 3. WATER LIFE (Fish Jump Animation)

Concept:

Fish occasionally jump out of water with splash effect. Visual hint for good fishing spots.

Animation Sequence:

Random Fish Jump:

// Every 5-15 seconds at fishing spots
setInterval(() => {
  if (Math.random() < 0.3) {  // 30% chance
    spawnFishJump();
  }
}, randomRange(5000, 15000));

function spawnFishJump() {
  let waterTile = getRandomWaterTile();
  
  // 1. Fish sprite jumps out
  let fish = createSprite('fish_jump.png', waterTile.position);
  
  fish.animate({
    // Arc jump animation
    keyframes: [
      { y: waterTile.y, time: 0 },
      { y: waterTile.y - 32, time: 300 },  // Peak of jump
      { y: waterTile.y, time: 600 }        // Back to water
    ],
    rotation: [0, 180, 360],  // Flip in air
    easing: 'easeInOutQuad'
  });
  
  // 2. Splash effect (start + end)
  createSplash(waterTile.position, scale: 1.0);
  
  setTimeout(() => {
    createSplash(waterTile.position, scale: 1.2);  // Bigger splash on landing
    fish.destroy();
  }, 600);
  
  // 3. Sound effect
  playSound('water_splash.mp3', volume: 0.6);
  
  // 4. Ripple effect
  createRipple(waterTile.position, {
    radius: [0, 48],
    duration: 1000ms,
    opacity: [0.8, 0]
  });
}

Splash Particle Effect:

function createSplash(position, scale) {
  emitParticles({
    position: position,
    count: 12,
    sprite: 'water_drop.png',
    color: '#4DB8FF',     // Water blue
    velocity: randomRange(-80, 80),
    gravity: 200,         // Drops fall down
    lifetime: 500ms,
    scale: scale,
    rotation: random(0, 360)
  });
}

Gameplay Integration:

  • Fish jump frequency indicates fish abundance
  • More jumps = better fishing spot
  • Rare fish have unique jump animations (golden sparkle)

Audio Assets Needed:

  • water_splash.mp3 - Splash sound
  • fish_jump.mp3 - Optional fish "plop"

Visual Assets Needed:

  • fish_jump.png - 16x16px fish mid-jump (Style 32)
  • water_drop.png - 4x4px water droplet particle
  • ripple.png - 32x32px circular ripple ring

🎥 4. DYNAMIC VISUALS (Cutscenes & Story Moments)

Cross-Fade Transitions:

// Scene transitions (smooth, cinematic)
function transitionScene(fromScene, toScene) {
  // Fade out current scene
  fromScene.fadeOut({
    duration: 1000ms,
    color: '#000000'  // Fade to black
  });
  
  // Wait for fade
  setTimeout(() => {
    // Switch scenes
    game.scene.stop(fromScene);
    game.scene.start(toScene);
    
    // Fade in new scene
    toScene.fadeIn({
      duration: 1000ms
    });
  }, 1000);
}

Vignette Effect (Important Moments):

// Adds dark edge vignette for dramatic moments
function applyVignette(intensity = 0.5) {
  let vignetteShader = {
    type: 'radialGradient',
    center: [screenWidth/2, screenHeight/2],
    radius: screenWidth * 0.6,
    colors: [
      { offset: 0, color: 'rgba(0,0,0,0)' },
      { offset: 1, color: `rgba(0,0,0,${intensity})` }
    ]
  };
  
  camera.applyPostFXShader(vignetteShader);
}

// Use during:
- Intro sequence (heavy vignette)
- Ana clue discoveries (medium vignette)
- Boss fights (light vignette)
- Emotional cutscenes (heavy vignette)

Blur Effect (Dream/Memory Sequences):

// Full-screen blur for dream states
function applyDreamBlur() {
  camera.applyPostFXShader({
    type: 'gaussianBlur',
    strength: 8,
    quality: 'medium'
  });
  
  // Desaturate colors slightly
  camera.applyColorMatrix({
    saturation: 0.5,  // 50% saturation
    brightness: 1.1   // Slightly brighter
  });
}

Slow-Motion Effect (Epic Moments):

// Slow-motion for dramatic moments
function applySlowMotion(duration = 2000, speed = 0.3) {
  game.time.timeScale = speed;  // 30% speed
  
  setTimeout(() => {
    // Return to normal speed
    tweenValue(game.time.timeScale, 1.0, {
      duration: 500ms,
      easing: 'easeOut'
    });
  }, duration);
  
  // Optional: Add motion blur
  camera.applyMotionBlur(strength: 0.5);
}

// Use during:
- Boss defeated moment
- Ana rescue cutscene
- Major discovery moments

🎨 PARTICLE SYSTEM LIBRARY

Reusable Particle Effects:

particlePresets = {
  // Sparkle (harvest, treasure)
  sparkle: {
    sprite: 'sparkle_star.png',
    count: 8,
    color: '#FFD700',
    velocity: random(-50, 50),
    lifetime: 800ms,
    fadeOut: true
  },
  
  // Smoke (campfire, forge)
  smoke: {
    sprite: 'smoke_puff.png',
    count: 3,
    color: '#888888',
    velocity: { x: random(-10, 10), y: -30 },
    lifetime: 2000ms,
    fadeOut: true,
    scaleUp: true
  },
  
  // Magic (enchanting, portals)
  magic: {
    sprite: 'glow_particle.png',
    count: 20,
    color: '#9D00FF',  // Purple
    velocity: spiral(radius: 40, speed: 2),
    lifetime: 1500ms,
    fadeOut: true,
    glow: true
  },
  
  // Blood (combat - optional, can be green "zombie goo")
  blood: {
    sprite: 'blood_splat.png',
    count: 6,
    color: '#00FF00',  // Green (zombie blood)
    velocity: random(-60, 60),
    gravity: 150,
    lifetime: 800ms,
    fadeOut: true
  },
  
  // Coins (quest rewards, sales)
  coins: {
    sprite: 'coin_spin.png',
    count: 10,
    color: '#FFD700',
    velocity: { x: random(-40, 40), y: -80 },
    gravity: 200,
    lifetime: 1200ms,
    rotation: true,
    bounce: true
  }
}

📊 VFX IMPLEMENTATION PRIORITY

Effect Priority Complexity Impact
Harvest Sparkles Low High satisfaction
Amnesia Blur Medium Emotional storytelling
Fish Jump Low World feels alive
Cross-Fade Low Professional polish
Vignette Low Dramatic moments
Slow-Motion Medium Epic boss moments

🎯 PHASER 3 IMPLEMENTATION NOTES

Particle Manager:

// src/systems/ParticleManager.js
class ParticleManager {
  constructor(scene) {
    this.scene = scene;
    this.emitters = {};
  }
  
  emit(preset, position, customParams = {}) {
    let params = { ...particlePresets[preset], ...customParams };
    let emitter = this.scene.add.particles(position.x, position.y, params.sprite, params);
    return emitter;
  }
  
  sparkle(position) {
    return this.emit('sparkle', position);
  }
  
  smoke(position) {
    return this.emit('smoke', position);
  }
  
  magic(position) {
    return this.emit('magic', position);
  }
}

Post-FX Pipeline:

// Phaser 3 built-in post-processing
scene.cameras.main.setPostPipeline('BlurPostFX');
scene.cameras.main.setPostPipeline('VignettePostFX');

🎬 CUTSCENE SYSTEM

Simple Dialogue + VFX:

// src/systems/CutsceneManager.js
class CutsceneManager {
  playMemoryFlashback(memoryData) {
    // 1. Freeze player
    player.freeze();
    
    // 2. Apply vignette
    applyVignette(0.7);
    
    // 3. Show blurred image
    showBlurredImage(memoryData.image);
    
    // 4. Play Ana's voice
    playVoice(memoryData.audioClip);
    
    // 5. Clear blur after 2 seconds
    setTimeout(() => {
      clearBlur(duration: 2000ms);
    }, 2000);
    
    // 6. Show dialogue
    setTimeout(() => {
      showDialogue({
        text: memoryData.caption,
        speaker: "Ana",
        duration: 4000ms
      });
    }, 4000);
    
    // 7. Resume game
    setTimeout(() => {
      removeVignette();
      player.unfreeze();
    }, 8000);
  }
}

🚀 ASSETS TO GENERATE

Particle Sprites:

  • sparkle_star.png - 8x8px gold star
  • glow_particle.png - 4x4px white glow
  • smoke_puff.png - 16x16px gray smoke
  • water_drop.png - 4x4px blue droplet
  • blood_splat.png - 8x8px green splat (zombie blood)
  • coin_spin.png - 8x8px gold coin

VFX Animations:

  • fish_jump.png - 16x16px fish sprite
  • ripple.png - 32x32px water ripple ring

Audio:

  • memory_echo.mp3
  • flashback_theme.mp3
  • ana_voice_01.mp3 through ana_voice_10.mp3
  • harvest_pop.mp3
  • sparkle_ting.mp3
  • water_splash.mp3

Status: 🟢 READY FOR IMPLEMENTATION
Estimated Time: 6-8 hours coding + 2 hours audio/visual assets
Emotional Impact: 🚀 MASSIVE - Game feels alive and responsive!