Files
novafarma/docs/WATER_ANIMATION.md
2025-12-11 20:41:00 +01:00

536 lines
13 KiB
Markdown

b# 💧 Water Tile Animation Tutorial
**Project:** NovaFarma
**Date:** 11. December 2025
**Author:** Development Team
---
## 📚 **Table of Contents:**
1. [Overview](#overview)
2. [How It Works](#how-it-works)
3. [Implementation Steps](#implementation-steps)
4. [Code Breakdown](#code-breakdown)
5. [Customization](#customization)
6. [Troubleshooting](#troubleshooting)
7. [Advanced Techniques](#advanced-techniques)
---
## 🌊 **Overview:**
Water tiles in NovaFarma use a **4-frame animation system** to create realistic water movement with:
- **Isometric perspective** (diamond-shaped tiles)
- **3D depth effect** (visible side faces)
- **Animated waves** (sine wave pattern)
- **Sparkle effects** (light reflections)
- **Smooth transitions** (60 FPS animation)
**Result:** Living, breathing water that feels organic!
---
## 🔧 **How It Works:**
### **System Architecture:**
```
TerrainSystem.js
├── createWaterFrames() // Generates 4 animation frames
├── generate() // Creates water tiles on map
└── update() // Cycles through frames (60 FPS)
```
### **Animation Flow:**
```
Frame 0 → Frame 1 → Frame 2 → Frame 3 → Loop back to Frame 0
↓ ↓ ↓ ↓
Wave Wave Wave Wave
Offset Offset Offset Offset
0 +3 +6 +9
```
**Frame Duration:** ~200ms each
**Total Loop Time:** ~800ms
**FPS:** 60 (smooth transitions)
---
## 🚀 **Implementation Steps:**
### **Step 1: Generate Water Frames**
Location: `src/systems/TerrainSystem.js`
```javascript
createWaterFrames() {
const tileWidth = 48;
const tileHeight = 48;
const P = 2; // Padding for anti-aliasing
// Generate 4 frames
for (let frame = 0; frame < 4; frame++) {
const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
// ... drawing code ...
graphics.generateTexture(`water_frame_${frame}`, tileWidth + P * 2, tileHeight + P * 2);
graphics.destroy();
}
}
```
**Call this in constructor:**
```javascript
constructor(scene) {
// ...
this.createWaterFrames();
}
```
---
### **Step 2: Create Isometric Diamond Shape**
```javascript
// Define isometric coordinates
const xs = P; // Left edge
const xe = 48 + P; // Right edge
const midX = 24 + P; // Center X
const topY = P; // Top point
const midY = 12 + P; // Middle Y
const bottomY = 24 + P; // Bottom point
const depth = 14; // 3D depth for sides
// Draw diamond top surface
graphics.fillStyle(0x33ccff); // Light cyan
graphics.beginPath();
graphics.moveTo(xs, midY); // Left point
graphics.lineTo(midX, topY); // Top point
graphics.lineTo(xe, midY); // Right point
graphics.lineTo(midX, bottomY); // Bottom point
graphics.closePath();
graphics.fill();
```
---
### **Step 3: Add 3D Side Faces**
```javascript
// LEFT FACE - Dark blue
const cLeft = 0x0066aa;
graphics.fillStyle(cLeft);
graphics.beginPath();
graphics.moveTo(midX, bottomY);
graphics.lineTo(midX, bottomY + depth);
graphics.lineTo(xs, midY + depth);
graphics.lineTo(xs, midY);
graphics.closePath();
graphics.fill();
// RIGHT FACE - Darker blue
const cRight = 0x004488;
graphics.fillStyle(cRight);
graphics.beginPath();
graphics.moveTo(xe, midY);
graphics.lineTo(xe, midY + depth);
graphics.lineTo(midX, bottomY + depth);
graphics.lineTo(midX, bottomY);
graphics.closePath();
graphics.fill();
```
---
### **Step 4: Add Wave Animation**
```javascript
// Offset changes per frame (0, 3, 6, 9)
const offset = frame * 3;
// Draw 3 wave lines
graphics.lineStyle(1, 0x66ddff, 0.3); // Semi-transparent white
for (let i = 0; i < 3; i++) {
graphics.beginPath();
const baseY = topY + 6 + i * 5; // Vertical spacing
for (let px = xs; px <= xe; px += 2) {
const relativeX = px - xs;
const waveOffset = Math.sin((relativeX + offset + i * 10) * 0.15) * 1.5;
const py = baseY + waveOffset;
if (px === xs) graphics.moveTo(px, py);
else graphics.lineTo(px, py);
}
graphics.strokePath();
}
```
**Wave Formula Explained:**
```javascript
Math.sin(
(relativeX + offset + i * 10) // Position + animation + wave offset
* 0.15 // Frequency (lower = wider waves)
) * 1.5 // Amplitude (height of waves)
```
---
### **Step 5: Add Sparkle Effects**
```javascript
graphics.fillStyle(0xffffff); // White sparkles
const sparkles = [
{ x: midX - 10 + (frame * 2) % 20, y: midY + 3 },
{ x: midX + 8 - (frame * 3) % 16, y: midY + 8 },
{ x: midX - 4 + Math.floor(frame * 1.5) % 8, y: midY + 13 }
];
sparkles.forEach(s => {
// Draw cross pattern (5 pixels)
graphics.fillRect(s.x, s.y, 1, 1); // Center
graphics.fillRect(s.x - 2, s.y, 1, 1); // Left
graphics.fillRect(s.x + 2, s.y, 1, 1); // Right
graphics.fillRect(s.x, s.y - 2, 1, 1); // Top
graphics.fillRect(s.x, s.y + 2, 1, 1); // Bottom
});
```
**Sparkle Animation:**
- Each sparkle moves across the tile per frame
- Uses modulo (`%`) to loop position
- Creates realistic light reflection effect
---
### **Step 6: Animate Water Tiles**
Location: `src/systems/TerrainSystem.js` - `update()` method
```javascript
update(time, delta) {
// Update every 200ms
if (!this.lastWaterUpdate) this.lastWaterUpdate = 0;
if (time - this.lastWaterUpdate > 200) {
this.lastWaterUpdate = time;
this.currentWaterFrame = (this.currentWaterFrame + 1) % 4;
// Update all water tiles
for (let key in this.tiles) {
const tile = this.tiles[key];
if (tile.type === 'WATER_DEEP') {
tile.sprite.setTexture(`water_frame_${this.currentWaterFrame}`);
}
}
}
}
```
**Performance Note:** Only updates texture reference, not recreating sprites!
---
## 🎨 **Customization:**
### **Change Water Color:**
```javascript
// Original (light cyan)
const waterColor = 0x33ccff;
// Variations:
const waterColor = 0x0099ff; // Deeper blue
const waterColor = 0x55eeff; // Bright cyan
const waterColor = 0x2266aa; // Dark ocean
const waterColor = 0x88ff88; // Toxic green (swamp)
```
---
### **Adjust Wave Speed:**
```javascript
// Faster animation (100ms per frame)
if (time - this.lastWaterUpdate > 100) { ... }
// Slower animation (500ms per frame)
if (time - this.lastWaterUpdate > 500) { ... }
```
---
### **Change Wave Pattern:**
```javascript
// More waves (5 instead of 3)
for (let i = 0; i < 5; i++) {
const baseY = topY + 4 + i * 3; // Closer spacing
// ...
}
// Bigger waves (higher amplitude)
const waveOffset = Math.sin(...) * 3.0; // Was 1.5
// Faster waves (higher frequency)
const waveOffset = Math.sin((relativeX + offset + i * 10) * 0.3) * 1.5; // Was 0.15
```
---
### **More Sparkles:**
```javascript
const sparkles = [
{ x: midX - 15 + (frame * 2) % 30, y: midY + 2 },
{ x: midX - 5 + (frame * 3) % 10, y: midY + 6 },
{ x: midX + 5 - (frame * 2) % 10, y: midY + 10 },
{ x: midX + 10 + (frame * 4) % 20, y: midY + 14 },
{ x: midX - 8 + Math.floor(frame * 1.5) % 16, y: midY + 18 }
];
```
---
## 🐛 **Troubleshooting:**
### **Problem: Water not animating**
**Solution 1:** Check if `update()` is called
```javascript
// In GameScene.js update()
if (this.terrainSystem && this.terrainSystem.update) {
this.terrainSystem.update(Date.now(), delta);
}
```
**Solution 2:** Verify frames exist
```javascript
// In browser console:
game.textures.list
// Should show: water_frame_0, water_frame_1, water_frame_2, water_frame_3
```
---
### **Problem: Water tiles are black/missing**
**Solution:** Ensure `createWaterFrames()` is called before `generate()`
```javascript
constructor(scene) {
// ...
this.createWaterFrames(); // MUST come first
this.generate(); // Then generate tiles
}
```
---
### **Problem: Animation is choppy**
**Solution:** Reduce update frequency
```javascript
// Too fast (every frame = choppy)
if (time - this.lastWaterUpdate > 16) { ... }
// Better (200ms = smooth)
if (time - this.lastWaterUpdate > 200) { ... }
```
---
### **Problem: Water looks pixelated**
**Solution:** Add padding (anti-aliasing)
```javascript
const P = 2; // Padding MUST be at least 2
graphics.generateTexture(`water_frame_${frame}`, tileWidth + P * 2, tileHeight + P * 2);
```
---
## 🚀 **Advanced Techniques:**
### **1. Shore Transitions:**
Create special water tiles for edges:
```javascript
createShoreWaterFrame(frame, edgeType) {
// edgeType: 'north', 'south', 'east', 'west'
const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
// Draw water diamond
// ... (same as before)
// Add sand/grass edge overlay
if (edgeType === 'north') {
graphics.fillStyle(0xddaa77); // Sand color
graphics.beginPath();
graphics.moveTo(midX - 10, topY);
graphics.lineTo(midX + 10, topY);
graphics.lineTo(midX, topY + 5);
graphics.closePath();
graphics.fill();
}
graphics.generateTexture(`water_shore_${edgeType}_${frame}`, 52, 52);
graphics.destroy();
}
```
---
### **2. Depth Variations:**
Different tile heights for shallow/deep water:
```javascript
createWaterFrames() {
// Shallow water (lighter, less depth)
this.createWaterVariant('shallow', {
topColor: 0x66ddff,
sideColor: 0x33aadd,
depth: 8
});
// Deep water (darker, more depth)
this.createWaterVariant('deep', {
topColor: 0x0066aa,
sideColor: 0x003366,
depth: 20
});
}
```
---
### **3. Particle Effects:**
Add water droplets on click:
```javascript
// In GameScene.js
this.input.on('pointerdown', (pointer) => {
const tile = this.terrainSystem.getTileAt(pointer.x, pointer.y);
if (tile && tile.type === 'WATER_DEEP') {
this.createWaterSplash(pointer.x, pointer.y);
}
});
createWaterSplash(x, y) {
for (let i = 0; i < 10; i++) {
const particle = this.add.circle(x, y, 2, 0x66ddff);
this.tweens.add({
targets: particle,
x: x + (Math.random() - 0.5) * 30,
y: y - Math.random() * 20,
alpha: 0,
duration: 500,
onComplete: () => particle.destroy()
});
}
}
```
---
### **4. Reflection Effect:**
Mirror sprites above water:
```javascript
createReflection(sprite, gridX, gridY) {
const waterTile = this.tiles[`${gridX},${gridY}`];
if (waterTile && waterTile.type === 'WATER_DEEP') {
const reflection = this.scene.add.sprite(
sprite.x,
sprite.y + 20, // Offset below sprite
sprite.texture.key
);
reflection.setOrigin(0.5, 0);
reflection.setAlpha(0.3);
reflection.setFlipY(true); // Mirror vertically
reflection.setDepth(waterTile.sprite.depth - 1);
return reflection;
}
}
```
---
## 📖 **Best Practices:**
### **Performance:**
- ✅ Generate frames ONCE in constructor
- ✅ Use texture swapping (not sprite recreation)
- ✅ Update at 200ms intervals (not every frame)
- ✅ Use object pooling for particles
- ❌ Don't recreate graphics every update
### **Visual Quality:**
- ✅ Use padding (P = 2) for smooth edges
- ✅ Add sparkle effects for realism
- ✅ Use isometric perspective for depth
- ✅ Vary side face colors (left darker than right)
- ❌ Don't make waves too fast (looks jittery)
### **Code Organization:**
- ✅ Separate frame generation from animation
- ✅ Use constants for colors/sizes
- ✅ Comment complex math formulas
- ✅ Log creation success (`console.log`)
- ❌ Don't hardcode magic numbers
---
## 💡 **Pro Tips:**
1. **Debug mode:** Press F12 and type `game.textures.list` to see all frames
2. **Performance:** Monitor FPS with `game.loop.actualFps`
3. **Testing:** Change `this.currentWaterFrame` manually in console
4. **Variations:** Create multiple water types (ocean, river, swamp)
5. **Polish:** Add sound effects (water splash, waves)
---
## 🎓 **Summary:**
**What you learned:**
- ✅ How to create animated isometric tiles
- ✅ Sine wave animation technique
- ✅ Texture generation in Phaser
- ✅ Frame-based animation system
- ✅ 3D depth effect with side faces
**Next steps:**
- Experiment with colors and speeds
- Add shore transitions
- Create particle effects
- Implement reflection system
---
## 📚 **Related Files:**
```
c:\novafarma\src\systems\TerrainSystem.js 👈 Main implementation
c:\novafarma\docs\WATER_ANIMATION.md 👈 This file
```
**Reference implementation:**
Lines 237-324 in `TerrainSystem.js`
---
**Happy animating! 💧🌊**
*Last updated: 11.12.2025 - 20:12*