Files
novafarma/docs/guides/HEARING_ACCESSIBILITY_PLAN.md
2025-12-12 13:48:49 +01:00

486 lines
12 KiB
Markdown

# 🔊 HEARING ACCESSIBILITY & CONTROLS - IMPLEMENTATION PLAN
**Datum:** 12. December 2025
**Prioriteta:** HIGH
**Estimated Time:** 3-4 ure
---
## 🎯 **CILJI:**
Implementirati celoten accessibility sistem za gluhe in remappable controls:
- Smart Subtitles
- Visual Sound Cues
- Subtitle System
- Remappable Controls
---
## 📋 **FAZA 1: SUBTITLE SYSTEM** (45 min)
### **1.1 Subtitle Manager**
**Datoteka:** `src/systems/SubtitleSystem.js` (nova)
```javascript
class SubtitleSystem {
constructor(scene) {
this.scene = scene;
this.enabled = true; // Always on by default
this.size = 'medium'; // small, medium, large, very_large
this.backgroundOpacity = 0.8;
this.currentSubtitle = null;
this.subtitleQueue = [];
this.createSubtitleUI();
console.log('📝 SubtitleSystem: Initialized');
}
createSubtitleUI() {
const uiScene = this.scene.scene.get('UIScene');
if (!uiScene) return;
// Subtitle container (bottom center)
const width = this.scene.cameras.main.width;
const height = this.scene.cameras.main.height;
this.subtitleBg = uiScene.add.graphics();
this.subtitleBg.setScrollFactor(0);
this.subtitleBg.setDepth(9000);
this.subtitleText = uiScene.add.text(
width / 2,
height - 100,
'',
this.getTextStyle()
);
this.subtitleText.setOrigin(0.5);
this.subtitleText.setScrollFactor(0);
this.subtitleText.setDepth(9001);
this.subtitleText.setVisible(false);
}
getTextStyle() {
const sizes = {
small: '16px',
medium: '20px',
large: '24px',
very_large: '32px'
};
return {
fontSize: sizes[this.size],
fontFamily: 'Arial',
color: '#ffffff',
stroke: '#000000',
strokeThickness: 4,
align: 'center',
wordWrap: { width: 800 }
};
}
showSubtitle(text, speaker = null, direction = null, duration = 3000) {
if (!this.enabled) return;
// Format subtitle
let subtitle = '';
// Speaker name (colored)
if (speaker) {
const color = this.getSpeakerColor(speaker);
subtitle += `[${speaker}]: `;
}
// Directional arrows
if (direction) {
subtitle = `${direction} ${subtitle}`;
}
subtitle += text;
// Show subtitle
this.subtitleText.setText(subtitle);
this.subtitleText.setStyle(this.getTextStyle());
this.subtitleText.setVisible(true);
// Background box
const bounds = this.subtitleText.getBounds();
this.subtitleBg.clear();
this.subtitleBg.fillStyle(0x000000, this.backgroundOpacity);
this.subtitleBg.fillRoundedRect(
bounds.x - 10,
bounds.y - 5,
bounds.width + 20,
bounds.height + 10,
8
);
// Auto-hide after duration
this.scene.time.delayedCall(duration, () => {
this.hideSubtitle();
});
console.log(`📝 Subtitle: ${subtitle}`);
}
hideSubtitle() {
this.subtitleText.setVisible(false);
this.subtitleBg.clear();
}
getSpeakerColor(speaker) {
const colors = {
'Player': '#00ff00',
'NPC': '#ffff00',
'System': '#ff00ff'
};
return colors[speaker] || '#ffffff';
}
// Sound effect captions
showSoundEffect(effect, direction = null) {
const captions = {
'dig': '[DIGGING SOUND]',
'plant': '[PLANTING SOUND]',
'harvest': '[HARVESTING SOUND]',
'build': '[BUILDING SOUND]',
'ui_click': '[CLICK]',
'footstep': '[FOOTSTEPS]',
'damage': '[DAMAGE]',
'death': '[DEATH SOUND]'
};
const caption = captions[effect] || `[${effect.toUpperCase()}]`;
this.showSubtitle(caption, null, direction, 1500);
}
}
```
---
## 📋 **FAZA 2: VISUAL SOUND CUES** (60 min)
### **2.1 Visual Heartbeat (Low Health)**
```javascript
class VisualSoundCues {
constructor(scene) {
this.scene = scene;
this.heartbeatActive = false;
this.createVisualCues();
}
createVisualCues() {
const uiScene = this.scene.scene.get('UIScene');
if (!uiScene) return;
// Heartbeat overlay (red pulse)
this.heartbeatOverlay = uiScene.add.graphics();
this.heartbeatOverlay.setScrollFactor(0);
this.heartbeatOverlay.setDepth(8999);
this.heartbeatOverlay.setAlpha(0);
}
showHeartbeat() {
if (this.heartbeatActive) return;
this.heartbeatActive = true;
const width = this.scene.cameras.main.width;
const height = this.scene.cameras.main.height;
// Pulse effect
this.scene.tweens.add({
targets: this.heartbeatOverlay,
alpha: { from: 0, to: 0.3 },
duration: 500,
yoyo: true,
repeat: -1,
onUpdate: () => {
this.heartbeatOverlay.clear();
this.heartbeatOverlay.fillStyle(0xff0000, this.heartbeatOverlay.alpha);
this.heartbeatOverlay.fillRect(0, 0, width, height);
}
});
}
hideHeartbeat() {
this.heartbeatActive = false;
this.scene.tweens.killTweensOf(this.heartbeatOverlay);
this.heartbeatOverlay.setAlpha(0);
this.heartbeatOverlay.clear();
}
}
```
### **2.2 Damage Direction Indicator**
```javascript
showDamageDirection(angle) {
const uiScene = this.scene.scene.get('UIScene');
if (!uiScene) return;
const centerX = this.scene.cameras.main.centerX;
const centerY = this.scene.cameras.main.centerY;
// Arrow pointing to damage source
const arrow = uiScene.add.text(
centerX + Math.cos(angle) * 100,
centerY + Math.sin(angle) * 100,
'⚠️',
{
fontSize: '32px'
}
);
arrow.setOrigin(0.5);
arrow.setScrollFactor(0);
arrow.setDepth(9000);
// Fade out
uiScene.tweens.add({
targets: arrow,
alpha: 0,
duration: 1000,
onComplete: () => arrow.destroy()
});
}
```
### **2.3 Screen Flash Notifications**
```javascript
showNotification(type) {
const colors = {
'danger': 0xff0000,
'warning': 0xffff00,
'success': 0x00ff00,
'info': 0x00ffff
};
const color = colors[type] || 0xffffff;
// Flash screen border
this.scene.cameras.main.flash(200,
(color >> 16) & 0xff,
(color >> 8) & 0xff,
color & 0xff
);
}
```
---
## 📋 **FAZA 3: REMAPPABLE CONTROLS** (90 min)
### **3.1 Control Mapping System**
**Datoteka:** `src/systems/ControlsSystem.js` (nova)
```javascript
class ControlsSystem {
constructor(scene) {
this.scene = scene;
// Default key mappings
this.keyMappings = {
'move_up': 'W',
'move_down': 'S',
'move_left': 'A',
'move_right': 'D',
'interact': 'SPACE',
'build': 'B',
'craft': 'C',
'inventory': 'I',
'map': 'M',
'pause': 'ESC',
'save': 'F5',
'load': 'F9',
'rotate': 'R',
'confirm': 'E',
'cancel': 'ESC'
};
// Control profiles
this.profiles = {
'default': { ...this.keyMappings },
'one_handed_left': {
'move_up': 'W',
'move_down': 'S',
'move_left': 'A',
'move_right': 'D',
'interact': 'Q',
'build': 'E',
'craft': 'R',
'inventory': 'F',
'map': 'TAB',
'pause': 'ESC'
},
'one_handed_right': {
'move_up': 'UP',
'move_down': 'DOWN',
'move_left': 'LEFT',
'move_right': 'RIGHT',
'interact': 'ENTER',
'build': 'NUMPAD_1',
'craft': 'NUMPAD_2',
'inventory': 'NUMPAD_3',
'map': 'NUMPAD_0',
'pause': 'ESC'
}
};
this.currentProfile = 'default';
this.loadMappings();
this.applyMappings();
console.log('🎮 ControlsSystem: Initialized');
}
remapKey(action, newKey) {
this.keyMappings[action] = newKey;
this.saveMappings();
this.applyMappings();
console.log(`🎮 Remapped ${action} to ${newKey}`);
}
loadProfile(profileName) {
if (!this.profiles[profileName]) return;
this.currentProfile = profileName;
this.keyMappings = { ...this.profiles[profileName] };
this.applyMappings();
console.log(`🎮 Loaded profile: ${profileName}`);
}
saveMappings() {
localStorage.setItem('novafarma_controls', JSON.stringify(this.keyMappings));
}
loadMappings() {
const saved = localStorage.getItem('novafarma_controls');
if (saved) {
this.keyMappings = JSON.parse(saved);
}
}
applyMappings() {
// Re-bind all keys based on current mappings
// This would require refactoring existing key bindings
console.log('🎮 Controls applied');
}
}
```
### **3.2 Controls Settings UI**
```javascript
createControlsMenu() {
const menu = this.scene.add.container(
this.scene.cameras.main.centerX,
this.scene.cameras.main.centerY
);
// Title
const title = this.scene.add.text(0, -250, '🎮 CONTROLS', {
fontSize: '32px',
fontStyle: 'bold'
}).setOrigin(0.5);
// Profile selector
const profileLabel = this.scene.add.text(-200, -200, 'Profile:', {
fontSize: '18px'
});
const profileDropdown = this.createDropdown(
0, -200,
['Default', 'One-Handed Left', 'One-Handed Right'],
(value) => this.loadProfile(value.toLowerCase().replace(' ', '_'))
);
// Key mappings list
let yOffset = -150;
Object.entries(this.keyMappings).forEach(([action, key]) => {
const actionLabel = this.scene.add.text(-200, yOffset, action, {
fontSize: '16px'
});
const keyButton = this.scene.add.text(100, yOffset, key, {
fontSize: '16px',
backgroundColor: '#333333',
padding: { x: 10, y: 5 }
});
keyButton.setInteractive({ useHandCursor: true });
keyButton.on('pointerdown', () => {
this.startKeyRemap(action, keyButton);
});
menu.add([actionLabel, keyButton]);
yOffset += 30;
});
menu.add(title);
return menu;
}
startKeyRemap(action, button) {
button.setText('Press key...');
// Listen for next key press
const listener = (event) => {
this.remapKey(action, event.key.toUpperCase());
button.setText(event.key.toUpperCase());
this.scene.input.keyboard.off('keydown', listener);
};
this.scene.input.keyboard.on('keydown', listener);
}
```
---
## 📝 **IMPLEMENTATION STEPS:**
1. **Ustvari SubtitleSystem.js** (45 min)
2. **Ustvari VisualSoundCues.js** (60 min)
3. **Ustvari ControlsSystem.js** (90 min)
4. **Integracija v GameScene** (30 min)
5. **Settings Menu UI** (45 min)
6. **Testing** (30 min)
**Total:** 5 ur
---
## 🔧 **DATOTEKE:**
**Nove:**
- `src/systems/SubtitleSystem.js` (~300 vrstic)
- `src/systems/VisualSoundCues.js` (~200 vrstic)
- `src/systems/ControlsSystem.js` (~400 vrstic)
**Posodobljene:**
- `src/scenes/GameScene.js` - Initialize systems
- `src/scenes/UIScene.js` - Settings menu
- `index.html` - Dodaj nove skripte
---
## 🎯 **PRIORITETA:**
**HIGH** - Accessibility za gluhe je ključen za:
- Večjo dostopnost
- Širše občinstvo
- Boljšo uporabniško izkušnjo
- Compliance s standardi
---
**Status:****PLAN PRIPRAVLJEN - ČAKA NA IMPLEMENTACIJO**
**Priporočam:** Implementacija v naslednji seji (jutri)
**Razlog:** Seja že traja 2h 28min, ta funkcionalnost zahteva 5 ur dela.
**Seja bi trajala 7+ ur** - preveč za en dan.
Želite začeti zdaj ali pustim za jutri? 🎮