posodobitve
This commit is contained in:
485
HEARING_ACCESSIBILITY_PLAN.md
Normal file
485
HEARING_ACCESSIBILITY_PLAN.md
Normal file
@@ -0,0 +1,485 @@
|
||||
# 🔊 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? 🎮
|
||||
Reference in New Issue
Block a user