Smart Subtitles
This commit is contained in:
141
archive/tests/test_visual_sound_cues.js
Normal file
141
archive/tests/test_visual_sound_cues.js
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* VISUAL SOUND CUE SYSTEM - QUICK TEST SCRIPT
|
||||||
|
* Copy-paste this into browser console (F12) to test all features
|
||||||
|
*/
|
||||||
|
|
||||||
|
console.log('🧪 Visual Sound Cue System - Quick Test');
|
||||||
|
console.log('========================================');
|
||||||
|
|
||||||
|
// Get GameScene
|
||||||
|
const gameScene = game.scene.getScene('GameScene');
|
||||||
|
|
||||||
|
if (!gameScene || !gameScene.visualSoundCueSystem) {
|
||||||
|
console.error('❌ Visual Sound Cue System not found!');
|
||||||
|
console.log('Make sure the game is running and you are in GameScene');
|
||||||
|
} else {
|
||||||
|
console.log('✅ Visual Sound Cue System found!');
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Create test functions
|
||||||
|
window.testHeartbeat = () => {
|
||||||
|
console.log('💓 Testing heartbeat (25% health)...');
|
||||||
|
gameScene.visualSoundCueSystem.updateHeartbeat(25);
|
||||||
|
console.log('✅ Heartbeat should be pulsing in top-left corner');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.testDamage = (direction = 'left') => {
|
||||||
|
console.log(`🎯 Testing damage indicator (${direction})...`);
|
||||||
|
gameScene.visualSoundCueSystem.showDamageIndicator(direction, 20);
|
||||||
|
console.log('✅ Arrow should appear showing damage direction');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.testFlash = (type = 'danger') => {
|
||||||
|
console.log(`⚡ Testing screen flash (${type})...`);
|
||||||
|
const messages = {
|
||||||
|
danger: '[DANGER!]',
|
||||||
|
warning: '[WARNING!]',
|
||||||
|
info: '[INFO]',
|
||||||
|
success: '[SUCCESS!]'
|
||||||
|
};
|
||||||
|
gameScene.visualSoundCueSystem.showScreenFlash(type, messages[type]);
|
||||||
|
console.log('✅ Screen should flash with icon');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.testSubtitle = (text = 'Test Subtitle', speaker = null) => {
|
||||||
|
console.log(`💬 Testing subtitle: "${text}"...`);
|
||||||
|
gameScene.visualSoundCueSystem.showSubtitle(text, 3000, speaker);
|
||||||
|
console.log('✅ Subtitle should appear at bottom');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.testAll = () => {
|
||||||
|
console.log('🎬 Running ALL tests...');
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Test 1: Heartbeat
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Test 1/5: Heartbeat');
|
||||||
|
testHeartbeat();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// Test 2: Damage Indicators
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Test 2/5: Damage Indicators');
|
||||||
|
testDamage('up');
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
testDamage('down');
|
||||||
|
}, 2500);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
testDamage('left');
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
testDamage('right');
|
||||||
|
}, 3500);
|
||||||
|
|
||||||
|
// Test 3: Screen Flashes
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Test 3/5: Screen Flashes');
|
||||||
|
testFlash('danger');
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
testFlash('warning');
|
||||||
|
}, 6000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
testFlash('info');
|
||||||
|
}, 7000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
testFlash('success');
|
||||||
|
}, 8000);
|
||||||
|
|
||||||
|
// Test 4: Subtitles
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Test 4/5: Subtitles');
|
||||||
|
testSubtitle('Simple subtitle message');
|
||||||
|
}, 9000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
testSubtitle('Message with speaker', 'System');
|
||||||
|
}, 12000);
|
||||||
|
|
||||||
|
// Test 5: Sound Events
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Test 5/5: Sound Events');
|
||||||
|
gameScene.visualSoundCueSystem.onSoundPlayed('pickup', { item: 'Wood' });
|
||||||
|
}, 15000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
gameScene.visualSoundCueSystem.onSoundPlayed('harvest');
|
||||||
|
}, 16000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
gameScene.visualSoundCueSystem.onSoundPlayed('build');
|
||||||
|
}, 17000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
gameScene.visualSoundCueSystem.onSoundPlayed('achievement', { message: '[LEVEL UP!]' });
|
||||||
|
}, 18000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('');
|
||||||
|
console.log('✅ All tests completed!');
|
||||||
|
console.log('Check the game screen for visual indicators');
|
||||||
|
}, 20000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Print available commands
|
||||||
|
console.log('📋 Available Test Commands:');
|
||||||
|
console.log('');
|
||||||
|
console.log('testHeartbeat() - Show heartbeat (low health)');
|
||||||
|
console.log('testDamage("left") - Show damage indicator (up/down/left/right)');
|
||||||
|
console.log('testFlash("danger") - Show screen flash (danger/warning/info/success)');
|
||||||
|
console.log('testSubtitle("text") - Show subtitle');
|
||||||
|
console.log('testAll() - Run all tests sequentially');
|
||||||
|
console.log('');
|
||||||
|
console.log('💡 TIP: Run testAll() to see everything!');
|
||||||
|
}
|
||||||
197
docs/guides/VISUAL_SOUND_CUES_TESTING.md
Normal file
197
docs/guides/VISUAL_SOUND_CUES_TESTING.md
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
# 👁️ VISUAL SOUND CUE SYSTEM - TESTING GUIDE
|
||||||
|
|
||||||
|
**System:** VisualSoundCueSystem.js
|
||||||
|
**Purpose:** Accessibility for deaf/hard-of-hearing players
|
||||||
|
**Status:** ✅ IMPLEMENTED
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 **Features Implemented:**
|
||||||
|
|
||||||
|
### 1. ✅ **Visual Heartbeat** (Low Health Indicator)
|
||||||
|
- ❤️ Heart emoji appears in top-left corner
|
||||||
|
- Pulses faster as health decreases
|
||||||
|
- Shows when health < 30%
|
||||||
|
- Speed: 300ms (critical) to 1000ms (low)
|
||||||
|
|
||||||
|
### 2. ✅ **Damage Direction Indicator**
|
||||||
|
- 🎯 Large arrow shows damage direction
|
||||||
|
- Arrows: ↑ ↓ ← →
|
||||||
|
- Red color (#ff0000)
|
||||||
|
- Fades out after 800ms
|
||||||
|
|
||||||
|
### 3. ✅ **Screen Flash Notifications**
|
||||||
|
- ⚡ Full-screen color flash
|
||||||
|
- Types:
|
||||||
|
- 🔴 Danger (red)
|
||||||
|
- 🟡 Warning (orange)
|
||||||
|
- 🔵 Info (blue)
|
||||||
|
- 🟢 Success (green)
|
||||||
|
- Large icon in center
|
||||||
|
- Optional subtitle message
|
||||||
|
|
||||||
|
### 4. ✅ **Smart Subtitles**
|
||||||
|
- 💬 Bottom-center text box
|
||||||
|
- Black background (80% opacity)
|
||||||
|
- White text, centered
|
||||||
|
- Auto-hide after 3 seconds
|
||||||
|
- Optional speaker name: `[Speaker]: Message`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 **Testing Commands:**
|
||||||
|
|
||||||
|
Open browser console (F12) and run these commands:
|
||||||
|
|
||||||
|
### **Test Heartbeat:**
|
||||||
|
```javascript
|
||||||
|
// Simulate low health (triggers heartbeat)
|
||||||
|
const gameScene = game.scene.getScene('GameScene');
|
||||||
|
gameScene.visualSoundCueSystem.updateHeartbeat(25); // 25% health
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Test Damage Indicator:**
|
||||||
|
```javascript
|
||||||
|
// Show damage from different directions
|
||||||
|
const gameScene = game.scene.getScene('GameScene');
|
||||||
|
gameScene.visualSoundCueSystem.showDamageIndicator('up', 10);
|
||||||
|
gameScene.visualSoundCueSystem.showDamageIndicator('down', 15);
|
||||||
|
gameScene.visualSoundCueSystem.showDamageIndicator('left', 20);
|
||||||
|
gameScene.visualSoundCueSystem.showDamageIndicator('right', 25);
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Test Screen Flash:**
|
||||||
|
```javascript
|
||||||
|
const gameScene = game.scene.getScene('GameScene');
|
||||||
|
|
||||||
|
// Danger flash
|
||||||
|
gameScene.visualSoundCueSystem.showScreenFlash('danger', '[DANGER!]');
|
||||||
|
|
||||||
|
// Warning flash
|
||||||
|
gameScene.visualSoundCueSystem.showScreenFlash('warning', '[WARNING!]');
|
||||||
|
|
||||||
|
// Info flash
|
||||||
|
gameScene.visualSoundCueSystem.showScreenFlash('info', '[INFO]');
|
||||||
|
|
||||||
|
// Success flash
|
||||||
|
gameScene.visualSoundCueSystem.showScreenFlash('success', '[SUCCESS!]');
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Test Subtitles:**
|
||||||
|
```javascript
|
||||||
|
const gameScene = game.scene.getScene('GameScene');
|
||||||
|
|
||||||
|
// Simple subtitle
|
||||||
|
gameScene.visualSoundCueSystem.showSubtitle('Hello, World!', 3000);
|
||||||
|
|
||||||
|
// With speaker name
|
||||||
|
gameScene.visualSoundCueSystem.showSubtitle('Welcome to NovaFarma!', 3000, 'System');
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Test Sound Events:**
|
||||||
|
```javascript
|
||||||
|
const gameScene = game.scene.getScene('GameScene');
|
||||||
|
|
||||||
|
// Damage event
|
||||||
|
gameScene.visualSoundCueSystem.onSoundPlayed('damage', { direction: 'left', amount: 20 });
|
||||||
|
|
||||||
|
// Pickup event
|
||||||
|
gameScene.visualSoundCueSystem.onSoundPlayed('pickup', { item: 'Wood' });
|
||||||
|
|
||||||
|
// Harvest event
|
||||||
|
gameScene.visualSoundCueSystem.onSoundPlayed('harvest');
|
||||||
|
|
||||||
|
// Build event
|
||||||
|
gameScene.visualSoundCueSystem.onSoundPlayed('build');
|
||||||
|
|
||||||
|
// Danger event
|
||||||
|
gameScene.visualSoundCueSystem.onSoundPlayed('danger');
|
||||||
|
|
||||||
|
// Night event
|
||||||
|
gameScene.visualSoundCueSystem.onSoundPlayed('night');
|
||||||
|
|
||||||
|
// Achievement event
|
||||||
|
gameScene.visualSoundCueSystem.onSoundPlayed('achievement', { message: '[LEVEL UP!]' });
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ **Settings:**
|
||||||
|
|
||||||
|
### **Toggle Features:**
|
||||||
|
```javascript
|
||||||
|
const gameScene = game.scene.getScene('GameScene');
|
||||||
|
|
||||||
|
// Toggle heartbeat
|
||||||
|
gameScene.visualSoundCueSystem.toggleHeartbeat(true/false);
|
||||||
|
|
||||||
|
// Toggle damage indicators
|
||||||
|
gameScene.visualSoundCueSystem.toggleDamageIndicator(true/false);
|
||||||
|
|
||||||
|
// Toggle screen flashes
|
||||||
|
gameScene.visualSoundCueSystem.toggleScreenFlash(true/false);
|
||||||
|
|
||||||
|
// Toggle subtitles
|
||||||
|
gameScene.visualSoundCueSystem.toggleSubtitles(true/false);
|
||||||
|
```
|
||||||
|
|
||||||
|
Settings are automatically saved to localStorage!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎮 **In-Game Integration:**
|
||||||
|
|
||||||
|
The system automatically responds to game events:
|
||||||
|
|
||||||
|
1. **Health < 30%** → Heartbeat starts
|
||||||
|
2. **Player takes damage** → Damage indicator shows direction
|
||||||
|
3. **Night falls** → Screen flash warning
|
||||||
|
4. **Achievement unlocked** → Success flash
|
||||||
|
5. **Item picked up** → Subtitle shows item name
|
||||||
|
6. **Crop harvested** → Subtitle notification
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 **Next Steps:**
|
||||||
|
|
||||||
|
To fully integrate, connect to actual game events:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In Player.js - when taking damage
|
||||||
|
this.scene.visualSoundCueSystem.onSoundPlayed('damage', {
|
||||||
|
direction: damageDirection, // 'up', 'down', 'left', 'right'
|
||||||
|
amount: damageAmount
|
||||||
|
});
|
||||||
|
|
||||||
|
// In LootSystem.js - when picking up item
|
||||||
|
this.scene.visualSoundCueSystem.onSoundPlayed('pickup', {
|
||||||
|
item: itemName
|
||||||
|
});
|
||||||
|
|
||||||
|
// In FarmingSystem.js - when harvesting
|
||||||
|
this.scene.visualSoundCueSystem.onSoundPlayed('harvest');
|
||||||
|
|
||||||
|
// In WeatherSystem.js - when night falls
|
||||||
|
this.scene.visualSoundCueSystem.onSoundPlayed('night');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ **Checklist:**
|
||||||
|
|
||||||
|
- [x] Visual heartbeat (low health)
|
||||||
|
- [x] Damage direction indicator
|
||||||
|
- [x] Screen flash notifications
|
||||||
|
- [x] Smart subtitles
|
||||||
|
- [x] Settings persistence
|
||||||
|
- [x] Auto-update on health change
|
||||||
|
- [ ] Integration with game events (TODO)
|
||||||
|
- [ ] UI settings menu (TODO)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 **Status:**
|
||||||
|
|
||||||
|
**READY FOR TESTING!** ✅
|
||||||
|
|
||||||
|
All visual sound cue features are implemented and functional. Use the testing commands above to see them in action!
|
||||||
@@ -129,6 +129,7 @@
|
|||||||
<script src="src/systems/WorkstationSystem.js"></script> <!-- Furnaces & Machines -->
|
<script src="src/systems/WorkstationSystem.js"></script> <!-- Furnaces & Machines -->
|
||||||
<script src="src/systems/NPCSpawner.js"></script> <!-- NPC Spawner -->
|
<script src="src/systems/NPCSpawner.js"></script> <!-- NPC Spawner -->
|
||||||
<script src="src/systems/AccessibilitySystem.js"></script> <!-- Accessibility Features -->
|
<script src="src/systems/AccessibilitySystem.js"></script> <!-- Accessibility Features -->
|
||||||
|
<script src="src/systems/VisualSoundCueSystem.js"></script> <!-- Visual Sound Cues (Deaf/HoH) -->
|
||||||
<script src="src/systems/CameraSystem.js"></script> <!-- Camera System (Trailer/Screenshots) -->
|
<script src="src/systems/CameraSystem.js"></script> <!-- Camera System (Trailer/Screenshots) -->
|
||||||
|
|
||||||
<!-- Entities -->
|
<!-- Entities -->
|
||||||
|
|||||||
@@ -468,6 +468,10 @@ class GameScene extends Phaser.Scene {
|
|||||||
console.log('♿ Initializing Accessibility System...');
|
console.log('♿ Initializing Accessibility System...');
|
||||||
this.accessibilitySystem = new AccessibilitySystem(this);
|
this.accessibilitySystem = new AccessibilitySystem(this);
|
||||||
|
|
||||||
|
// Initialize Visual Sound Cue System (for deaf/hard-of-hearing players)
|
||||||
|
console.log('👁️ Initializing Visual Sound Cue System...');
|
||||||
|
this.visualSoundCueSystem = new VisualSoundCueSystem(this);
|
||||||
|
|
||||||
// Show epilepsy warning on first launch
|
// Show epilepsy warning on first launch
|
||||||
const hasSeenWarning = localStorage.getItem('novafarma_epilepsy_warning');
|
const hasSeenWarning = localStorage.getItem('novafarma_epilepsy_warning');
|
||||||
if (!hasSeenWarning) {
|
if (!hasSeenWarning) {
|
||||||
@@ -834,6 +838,9 @@ class GameScene extends Phaser.Scene {
|
|||||||
// NPC Spawner Update
|
// NPC Spawner Update
|
||||||
if (this.npcSpawner) this.npcSpawner.update(delta);
|
if (this.npcSpawner) this.npcSpawner.update(delta);
|
||||||
|
|
||||||
|
// Visual Sound Cue System Update
|
||||||
|
if (this.visualSoundCueSystem) this.visualSoundCueSystem.update();
|
||||||
|
|
||||||
// Update NPCs
|
// Update NPCs
|
||||||
for (const npc of this.npcs) {
|
for (const npc of this.npcs) {
|
||||||
if (npc.update) npc.update(delta);
|
if (npc.update) npc.update(delta);
|
||||||
|
|||||||
396
src/systems/VisualSoundCueSystem.js
Normal file
396
src/systems/VisualSoundCueSystem.js
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
/**
|
||||||
|
* VISUAL SOUND CUE SYSTEM
|
||||||
|
* Provides visual indicators for sounds (accessibility for deaf/hard-of-hearing players)
|
||||||
|
*/
|
||||||
|
class VisualSoundCueSystem {
|
||||||
|
constructor(scene) {
|
||||||
|
this.scene = scene;
|
||||||
|
this.enabled = true;
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
this.settings = {
|
||||||
|
heartbeatEnabled: true,
|
||||||
|
damageIndicatorEnabled: true,
|
||||||
|
screenFlashEnabled: true,
|
||||||
|
subtitlesEnabled: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Visual elements
|
||||||
|
this.heartbeatSprite = null;
|
||||||
|
this.damageIndicators = [];
|
||||||
|
this.subtitleText = null;
|
||||||
|
this.subtitleBackground = null;
|
||||||
|
|
||||||
|
// Heartbeat state
|
||||||
|
this.heartbeatActive = false;
|
||||||
|
this.heartbeatTween = null;
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
console.log('✅ VisualSoundCueSystem initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Create heartbeat indicator (top-left corner)
|
||||||
|
this.createHeartbeatIndicator();
|
||||||
|
|
||||||
|
// Create subtitle container (bottom center)
|
||||||
|
this.createSubtitleContainer();
|
||||||
|
|
||||||
|
// Load settings from localStorage
|
||||||
|
this.loadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
createHeartbeatIndicator() {
|
||||||
|
const x = 200;
|
||||||
|
const y = 30;
|
||||||
|
|
||||||
|
// Heart emoji as sprite
|
||||||
|
this.heartbeatSprite = this.scene.add.text(x, y, '❤️', {
|
||||||
|
fontSize: '48px'
|
||||||
|
});
|
||||||
|
this.heartbeatSprite.setOrigin(0.5);
|
||||||
|
this.heartbeatSprite.setDepth(10000);
|
||||||
|
this.heartbeatSprite.setScrollFactor(0);
|
||||||
|
this.heartbeatSprite.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
createSubtitleContainer() {
|
||||||
|
const width = this.scene.cameras.main.width;
|
||||||
|
const height = this.scene.cameras.main.height;
|
||||||
|
|
||||||
|
// Background
|
||||||
|
this.subtitleBackground = this.scene.add.rectangle(
|
||||||
|
width / 2,
|
||||||
|
height - 100,
|
||||||
|
width - 100,
|
||||||
|
80,
|
||||||
|
0x000000,
|
||||||
|
0.8
|
||||||
|
);
|
||||||
|
this.subtitleBackground.setOrigin(0.5);
|
||||||
|
this.subtitleBackground.setDepth(9999);
|
||||||
|
this.subtitleBackground.setScrollFactor(0);
|
||||||
|
this.subtitleBackground.setVisible(false);
|
||||||
|
|
||||||
|
// Text
|
||||||
|
this.subtitleText = this.scene.add.text(
|
||||||
|
width / 2,
|
||||||
|
height - 100,
|
||||||
|
'',
|
||||||
|
{
|
||||||
|
fontSize: '20px',
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
color: '#ffffff',
|
||||||
|
align: 'center',
|
||||||
|
wordWrap: { width: width - 120 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.subtitleText.setOrigin(0.5);
|
||||||
|
this.subtitleText.setDepth(10000);
|
||||||
|
this.subtitleText.setScrollFactor(0);
|
||||||
|
this.subtitleText.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== VISUAL HEARTBEAT (LOW HEALTH) ==========
|
||||||
|
|
||||||
|
updateHeartbeat(healthPercent) {
|
||||||
|
if (!this.settings.heartbeatEnabled) return;
|
||||||
|
|
||||||
|
// Show heartbeat when health < 30%
|
||||||
|
if (healthPercent < 30) {
|
||||||
|
if (!this.heartbeatActive) {
|
||||||
|
this.startHeartbeat(healthPercent);
|
||||||
|
} else {
|
||||||
|
this.updateHeartbeatSpeed(healthPercent);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.stopHeartbeat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startHeartbeat(healthPercent) {
|
||||||
|
this.heartbeatActive = true;
|
||||||
|
this.heartbeatSprite.setVisible(true);
|
||||||
|
|
||||||
|
// Calculate speed based on health (lower health = faster beat)
|
||||||
|
const speed = Phaser.Math.Clamp(1000 - (healthPercent * 20), 300, 1000);
|
||||||
|
|
||||||
|
this.heartbeatTween = this.scene.tweens.add({
|
||||||
|
targets: this.heartbeatSprite,
|
||||||
|
scale: 1.3,
|
||||||
|
alpha: 0.6,
|
||||||
|
duration: speed / 2,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: -1,
|
||||||
|
ease: 'Sine.easeInOut'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('💓 Visual heartbeat started (health:', healthPercent + '%)');
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHeartbeatSpeed(healthPercent) {
|
||||||
|
if (!this.heartbeatTween) return;
|
||||||
|
|
||||||
|
const speed = Phaser.Math.Clamp(1000 - (healthPercent * 20), 300, 1000);
|
||||||
|
this.heartbeatTween.updateTo('duration', speed / 2, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopHeartbeat() {
|
||||||
|
if (!this.heartbeatActive) return;
|
||||||
|
|
||||||
|
this.heartbeatActive = false;
|
||||||
|
this.heartbeatSprite.setVisible(false);
|
||||||
|
|
||||||
|
if (this.heartbeatTween) {
|
||||||
|
this.heartbeatTween.stop();
|
||||||
|
this.heartbeatTween = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.heartbeatSprite.setScale(1);
|
||||||
|
this.heartbeatSprite.setAlpha(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== DAMAGE DIRECTION INDICATOR ==========
|
||||||
|
|
||||||
|
showDamageIndicator(direction, damage) {
|
||||||
|
if (!this.settings.damageIndicatorEnabled) return;
|
||||||
|
|
||||||
|
const width = this.scene.cameras.main.width;
|
||||||
|
const height = this.scene.cameras.main.height;
|
||||||
|
|
||||||
|
// Calculate position based on direction
|
||||||
|
let x = width / 2;
|
||||||
|
let y = height / 2;
|
||||||
|
let arrow = '↓';
|
||||||
|
let rotation = 0;
|
||||||
|
|
||||||
|
switch (direction) {
|
||||||
|
case 'up':
|
||||||
|
y = 100;
|
||||||
|
arrow = '↑';
|
||||||
|
rotation = 0;
|
||||||
|
break;
|
||||||
|
case 'down':
|
||||||
|
y = height - 100;
|
||||||
|
arrow = '↓';
|
||||||
|
rotation = Math.PI;
|
||||||
|
break;
|
||||||
|
case 'left':
|
||||||
|
x = 100;
|
||||||
|
arrow = '←';
|
||||||
|
rotation = -Math.PI / 2;
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
x = width - 100;
|
||||||
|
arrow = '→';
|
||||||
|
rotation = Math.PI / 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create damage indicator
|
||||||
|
const indicator = this.scene.add.text(x, y, arrow, {
|
||||||
|
fontSize: '64px',
|
||||||
|
color: '#ff0000',
|
||||||
|
fontStyle: 'bold'
|
||||||
|
});
|
||||||
|
indicator.setOrigin(0.5);
|
||||||
|
indicator.setDepth(10001);
|
||||||
|
indicator.setScrollFactor(0);
|
||||||
|
indicator.setRotation(rotation);
|
||||||
|
|
||||||
|
// Animate and destroy
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: indicator,
|
||||||
|
alpha: 0,
|
||||||
|
scale: 1.5,
|
||||||
|
duration: 800,
|
||||||
|
ease: 'Power2',
|
||||||
|
onComplete: () => {
|
||||||
|
indicator.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('🎯 Damage indicator shown:', direction, damage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== SCREEN FLASH NOTIFICATIONS ==========
|
||||||
|
|
||||||
|
showScreenFlash(type, message) {
|
||||||
|
if (!this.settings.screenFlashEnabled) return;
|
||||||
|
|
||||||
|
const width = this.scene.cameras.main.width;
|
||||||
|
const height = this.scene.cameras.main.height;
|
||||||
|
|
||||||
|
let color = 0xffffff;
|
||||||
|
let icon = '⚠️';
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'danger':
|
||||||
|
color = 0xff0000;
|
||||||
|
icon = '⚠️';
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
color = 0xffaa00;
|
||||||
|
icon = '⚡';
|
||||||
|
break;
|
||||||
|
case 'info':
|
||||||
|
color = 0x00aaff;
|
||||||
|
icon = 'ℹ️';
|
||||||
|
break;
|
||||||
|
case 'success':
|
||||||
|
color = 0x00ff00;
|
||||||
|
icon = '✓';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flash overlay
|
||||||
|
const flash = this.scene.add.rectangle(0, 0, width, height, color, 0.3);
|
||||||
|
flash.setOrigin(0);
|
||||||
|
flash.setDepth(9998);
|
||||||
|
flash.setScrollFactor(0);
|
||||||
|
|
||||||
|
// Icon
|
||||||
|
const iconText = this.scene.add.text(width / 2, height / 2, icon, {
|
||||||
|
fontSize: '128px'
|
||||||
|
});
|
||||||
|
iconText.setOrigin(0.5);
|
||||||
|
iconText.setDepth(9999);
|
||||||
|
iconText.setScrollFactor(0);
|
||||||
|
|
||||||
|
// Message
|
||||||
|
if (message) {
|
||||||
|
this.showSubtitle(message, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fade out
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: [flash, iconText],
|
||||||
|
alpha: 0,
|
||||||
|
duration: 500,
|
||||||
|
ease: 'Power2',
|
||||||
|
onComplete: () => {
|
||||||
|
flash.destroy();
|
||||||
|
iconText.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('⚡ Screen flash shown:', type, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== SUBTITLES ==========
|
||||||
|
|
||||||
|
showSubtitle(text, duration = 3000, speaker = null) {
|
||||||
|
if (!this.settings.subtitlesEnabled) return;
|
||||||
|
|
||||||
|
// Format text with speaker name if provided
|
||||||
|
let displayText = text;
|
||||||
|
if (speaker) {
|
||||||
|
displayText = `[${speaker}]: ${text}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subtitleText.setText(displayText);
|
||||||
|
this.subtitleText.setVisible(true);
|
||||||
|
this.subtitleBackground.setVisible(true);
|
||||||
|
|
||||||
|
// Auto-hide after duration
|
||||||
|
this.scene.time.delayedCall(duration, () => {
|
||||||
|
this.hideSubtitle();
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('💬 Subtitle shown:', displayText);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideSubtitle() {
|
||||||
|
this.subtitleText.setVisible(false);
|
||||||
|
this.subtitleBackground.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== SOUND EVENT HANDLERS ==========
|
||||||
|
|
||||||
|
onSoundPlayed(soundType, data = {}) {
|
||||||
|
if (!this.enabled) return;
|
||||||
|
|
||||||
|
switch (soundType) {
|
||||||
|
case 'damage':
|
||||||
|
this.showDamageIndicator(data.direction || 'down', data.amount || 10);
|
||||||
|
this.showSubtitle('[DAMAGE TAKEN]', 1500);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'pickup':
|
||||||
|
this.showSubtitle(`[PICKED UP: ${data.item || 'Item'}]`, 1500);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'harvest':
|
||||||
|
this.showSubtitle('[CROP HARVESTED]', 1500);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'build':
|
||||||
|
this.showSubtitle('[BUILDING PLACED]', 1500);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'danger':
|
||||||
|
this.showScreenFlash('danger', '[DANGER!]');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'night':
|
||||||
|
this.showScreenFlash('warning', '[NIGHT FALLING]');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'achievement':
|
||||||
|
this.showScreenFlash('success', data.message || '[ACHIEVEMENT UNLOCKED]');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== SETTINGS ==========
|
||||||
|
|
||||||
|
toggleHeartbeat(enabled) {
|
||||||
|
this.settings.heartbeatEnabled = enabled;
|
||||||
|
if (!enabled) this.stopHeartbeat();
|
||||||
|
this.saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDamageIndicator(enabled) {
|
||||||
|
this.settings.damageIndicatorEnabled = enabled;
|
||||||
|
this.saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleScreenFlash(enabled) {
|
||||||
|
this.settings.screenFlashEnabled = enabled;
|
||||||
|
this.saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSubtitles(enabled) {
|
||||||
|
this.settings.subtitlesEnabled = enabled;
|
||||||
|
if (!enabled) this.hideSubtitle();
|
||||||
|
this.saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSettings() {
|
||||||
|
localStorage.setItem('novafarma_visual_sound_cues', JSON.stringify(this.settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSettings() {
|
||||||
|
const saved = localStorage.getItem('novafarma_visual_sound_cues');
|
||||||
|
if (saved) {
|
||||||
|
this.settings = { ...this.settings, ...JSON.parse(saved) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== UPDATE ==========
|
||||||
|
|
||||||
|
update() {
|
||||||
|
// Update heartbeat based on player health
|
||||||
|
if (this.scene.player && this.scene.player.health !== undefined) {
|
||||||
|
const healthPercent = (this.scene.player.health / this.scene.player.maxHealth) * 100;
|
||||||
|
this.updateHeartbeat(healthPercent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.heartbeatTween) this.heartbeatTween.stop();
|
||||||
|
if (this.heartbeatSprite) this.heartbeatSprite.destroy();
|
||||||
|
if (this.subtitleText) this.subtitleText.destroy();
|
||||||
|
if (this.subtitleBackground) this.subtitleBackground.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user