486 lines
12 KiB
Markdown
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? 🎮
|