feat: Implement combat and movement sound effects with looping footsteps

This commit is contained in:
2026-01-07 20:01:58 +01:00
parent 180cf3ab94
commit 5c5cc13903
3 changed files with 52 additions and 4 deletions

View File

@@ -254,6 +254,8 @@ export class GameEngine {
const result = CombatMechanics.resolveMeleeAttack(hero, monster, this);
hero.hasAttacked = true;
if (window.SOUND_MANAGER) window.SOUND_MANAGER.playSound('sword');
if (this.onCombatResult) this.onCombatResult(result);
return { success: true, result };
@@ -309,6 +311,8 @@ export class GameEngine {
const result = CombatMechanics.resolveRangedAttack(hero, monster, this);
hero.hasAttacked = true;
if (window.SOUND_MANAGER) window.SOUND_MANAGER.playSound('arrow');
if (this.onCombatResult) this.onCombatResult(result);
return { success: true, result };

View File

@@ -349,6 +349,8 @@ export class GameRenderer {
}
updateAnimations(time) {
let isAnyMoving = false;
this.entities.forEach((mesh, id) => {
const data = mesh.userData;
@@ -362,6 +364,11 @@ export class GameRenderer {
data.targetPos = new THREE.Vector3(nextStep.x, mesh.position.y, -nextStep.y);
}
// Check if this entity is contributing to movement sound
if (data.isMoving || data.pathQueue.length > 0) {
isAnyMoving = true;
}
if (data.isMoving) {
const duration = 300; // Hero movement speed (300ms per tile)
const elapsed = time - data.startTime;
@@ -457,14 +464,24 @@ export class GameRenderer {
// IF Finished Sequence (Queue empty)
if (data.pathQueue.length === 0) {
// Check if it's the player (id 'p1')
if (id === 'p1' && this.onHeroFinishedMove) {
if (data.pathQueue.length === 0 && !data.isMoving) { // Ensure strict finished state
// Check if it's the player (id 'p1') -- NOTE: ID might be hero_barbarian etc.
// Using generic callback
if (id === 'p1' && this.onHeroFinishedMove) { // Legacy check?
// Grid Coords from World Coords (X, -Z)
this.onHeroFinishedMove(mesh.position.x, -mesh.position.z);
}
}
});
// Handle Footsteps Audio Globally
if (window.SOUND_MANAGER) {
if (isAnyMoving) {
window.SOUND_MANAGER.startLoop('footsteps');
} else {
window.SOUND_MANAGER.stopLoop('footsteps');
}
}
}
renderExits(exits) {
// Cancel any pending render

View File

@@ -12,11 +12,15 @@ export class SoundManager {
'exploration': '/assets/music/ingame/Abandoned_Ruins.mp3'
},
sfx: {
'door_open': '/assets/sfx/opendoor.mp3'
'door_open': '/assets/sfx/opendoor.mp3',
'footsteps': '/assets/sfx/footsteps.mp3',
'sword': '/assets/sfx/sword1.mp3',
'arrow': '/assets/sfx/arrow.mp3'
}
};
this.initialized = false;
this.activeLoops = new Map();
}
/**
@@ -112,4 +116,27 @@ export class SoundManager {
// or log if needed
});
}
startLoop(key) {
if (this.isMuted) return;
if (this.activeLoops.has(key)) return; // Already playing
const url = this.assets.sfx[key];
if (!url) return;
const audio = new Audio(url);
audio.loop = true;
audio.volume = this.sfxVolume;
audio.play().catch(() => { });
this.activeLoops.set(key, audio);
}
stopLoop(key) {
const audio = this.activeLoops.get(key);
if (audio) {
audio.pause();
audio.currentTime = 0;
this.activeLoops.delete(key);
}
}
}