540 lines
12 KiB
Markdown
540 lines
12 KiB
Markdown
# 🎬 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**
|
|
```javascript
|
|
// 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**
|
|
```javascript
|
|
// 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**
|
|
```javascript
|
|
// 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**
|
|
```javascript
|
|
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:
|
|
```javascript
|
|
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:**
|
|
```javascript
|
|
// 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:**
|
|
```javascript
|
|
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:**
|
|
|
|
```javascript
|
|
// 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):**
|
|
|
|
```javascript
|
|
// 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):**
|
|
|
|
```javascript
|
|
// 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):**
|
|
|
|
```javascript
|
|
// 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:**
|
|
|
|
```javascript
|
|
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:**
|
|
```javascript
|
|
// 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:**
|
|
```javascript
|
|
// Phaser 3 built-in post-processing
|
|
scene.cameras.main.setPostPipeline('BlurPostFX');
|
|
scene.cameras.main.setPostPipeline('VignettePostFX');
|
|
```
|
|
|
|
---
|
|
|
|
## 🎬 **CUTSCENE SYSTEM**
|
|
|
|
### **Simple Dialogue + VFX:**
|
|
```javascript
|
|
// 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!
|