Fix SoundFx for HTML build

pull/15/head
wadeaston 2025-04-04 12:07:09 +07:00
parent 3c7928bcad
commit 9c69759176
4 changed files with 250 additions and 27 deletions

@ -122,6 +122,7 @@ shadow_opacity = 0.75
[node name="ControlsPanel" parent="CanvasLayer" instance=ExtResource("19_controls")]
[node name="SoundPanel" parent="CanvasLayer" instance=ExtResource("21_sound_panel")]
anchors_preset = 8
[node name="MissionManager" type="Node" parent="." node_paths=PackedStringArray("mission_ui", "builder")]
script = ExtResource("10_oe3re")

@ -23,10 +23,21 @@ func _ready():
# Connect the closed signal to handle when player closes the controls
controls_panel.closed.connect(_on_controls_panel_closed)
# Set up audio
setup_background_music()
setup_building_sfx()
setup_construction_sfx()
# Check for audio initialization status (important for web)
var sound_manager = get_node_or_null("/root/SoundManager")
var can_initialize_audio = true
if OS.has_feature("web") and sound_manager:
can_initialize_audio = sound_manager.audio_initialized
if not can_initialize_audio:
# For web, wait for the audio_ready signal before initializing audio
sound_manager.audio_ready.connect(_initialize_game_audio)
print("Web platform detected: Deferring audio setup until user interaction")
# Set up audio if allowed (immediate for desktop, after interaction for web)
if can_initialize_audio:
_initialize_game_audio()
# Find the builder and connect to it
var builder = get_node_or_null("/root/Main/Builder")
@ -39,6 +50,17 @@ func _ready():
# Make sure sound buses are properly configured
call_deferred("_setup_sound_buses")
# Initialize all game audio - called immediately on desktop, after user interaction on web
func _initialize_game_audio():
print("Initializing all game audio...")
# Set up all audio systems
setup_background_music()
setup_building_sfx()
setup_construction_sfx()
print("All game audio initialized successfully")
# This function is called when the controls panel is closed
func _on_controls_panel_closed():
print("Controls panel closed by player")
@ -80,10 +102,52 @@ func setup_background_music():
music_player.stream = music
music_player.volume_db = -12 # 25% volume (approx)
music_player.bus = "Music" # Use the Music bus
music_player.play()
print("Playing background music: jazz_new_orleans.mp3")
# Check if we can play audio immediately (desktop) or need to wait (web)
var can_play_now = true
if OS.has_feature("web"):
var sound_manager = get_node_or_null("/root/SoundManager")
if sound_manager:
can_play_now = sound_manager.audio_initialized
# If not initialized, connect to the ready signal
if not can_play_now:
sound_manager.audio_ready.connect(_start_background_music)
print("Background music setup complete, waiting for user interaction")
# Play immediately if allowed
if can_play_now:
_start_background_music()
else:
print("ERROR: Could not load background music")
# Start background music playing (called directly or via signal)
func _start_background_music():
if music_player and music_player.stream and not music_player.playing:
# For web builds, use a more aggressive approach to starting audio
if OS.has_feature("web"):
# Make sure the sound is playing from the beginning
music_player.stop()
music_player.seek(0.0)
# Force the music to be audible
music_player.volume_db = -12 # Original volume
music_player.bus = "Music"
# Make sure the bus isn't muted
var sound_manager = get_node_or_null("/root/SoundManager")
if sound_manager:
sound_manager._apply_music_volume()
# Play with a slight delay for better browser compatibility
get_tree().create_timer(0.1).timeout.connect(func():
music_player.play()
print("Started playing background music (web build)")
)
else:
# Standard approach for desktop builds
music_player.play()
print("Started playing background music")
# Setup building sound effects
func setup_building_sfx():
@ -117,11 +181,21 @@ func setup_construction_sfx():
# Play the building sound effect when a structure is placed
func _on_structure_placed(structure_index, position):
if building_sfx and building_sfx.stream:
# Check web audio initialized status if needed
var can_play_audio = true
if OS.has_feature("web"):
var sound_manager = get_node_or_null("/root/SoundManager")
if sound_manager:
can_play_audio = sound_manager.audio_initialized
# Only play if audio is initialized (always true on desktop, depends on user interaction for web)
if can_play_audio and building_sfx and building_sfx.stream:
if building_sfx.playing:
building_sfx.stop()
building_sfx.play()
print("Playing building placement SFX")
elif OS.has_feature("web"):
print("Structure placed but audio not yet initialized")
# Variables for construction sound looping
var construction_active = false

@ -15,6 +15,7 @@ var movement_speed: float = 2.5 # Default walking speed
var construction_sound: AudioStreamPlayer # Use regular AudioStreamPlayer instead of 3D
var loop_timer: Timer
var my_sound_id: int = 0 # Unique ID for this worker's sound
var sound_initialized: bool = false
# Signals
signal construction_started
@ -65,6 +66,7 @@ func setup_sound():
construction_sound.volume_db = -5.0 # Volume level
construction_sound.bus = "SFX" # Use the SFX bus
sound_initialized = true
print("DEBUG: Worker " + str(my_sound_id) + " sound setup completed")
else:
push_error("Could not load construction sound effect!")
@ -78,6 +80,14 @@ func setup_sound():
# Connect the timer to the loop function
loop_timer.timeout.connect(loop_construction_sound)
print("DEBUG: Timer set up and connected for worker " + str(my_sound_id))
# Check if we need to connect to the audio_ready signal (for web)
if OS.has_feature("web"):
var sound_manager = get_node_or_null("/root/SoundManager")
if sound_manager and not sound_manager.audio_initialized:
# Connect to the audio_ready signal so we can start playing when audio is ready
sound_manager.audio_ready.connect(check_and_play_sound)
print("DEBUG: Worker " + str(my_sound_id) + " connected to audio_ready signal")
func _physics_process(delta: float):
if construction_finished:
@ -97,7 +107,7 @@ func _physics_process(delta: float):
elif is_construction_active:
# Make sure we keep the construction animation looping
ensure_animation_playing()
# Make sure the construction animation keeps playing
func ensure_animation_playing():
# Check if animation isn't playing or is on the wrong animation
@ -148,21 +158,18 @@ func start_construction():
print("DEBUG: Worker " + str(my_sound_id) + " starting construction")
is_construction_active = true
# Start playing construction sound
if construction_sound and construction_sound.stream:
# Set a random pitch to create variation between workers
construction_sound.pitch_scale = randf_range(0.9, 1.1)
# Play the sound
construction_sound.play()
print("DEBUG: Starting sound playback for worker " + str(my_sound_id))
# Start the loop timer
loop_timer.start()
print("DEBUG: Started loop timer for worker " + str(my_sound_id))
else:
push_error("Worker " + str(my_sound_id) + " has no sound stream!")
print("ERROR: Worker " + str(my_sound_id) + " has no sound stream!")
# Check if we can play sound (for web platform)
var can_play_sound = true
if OS.has_feature("web"):
var sound_manager = get_node_or_null("/root/SoundManager")
if sound_manager:
can_play_sound = sound_manager.audio_initialized
# Start playing construction sound if possible
if can_play_sound and sound_initialized and construction_sound and construction_sound.stream:
play_sound()
elif OS.has_feature("web"):
print("DEBUG: Worker " + str(my_sound_id) + " waiting for audio initialization")
# Emit signal for compatibility with existing system
construction_started.emit()
@ -178,14 +185,32 @@ func start_construction():
elif animation_player.has_animation("idle"):
animation_player.play("idle")
# Helper to play construction sound
func play_sound():
if is_construction_active and construction_sound and construction_sound.stream:
# Set random pitch for variety
construction_sound.pitch_scale = randf_range(0.9, 1.1)
# Play the sound
construction_sound.play()
print("DEBUG: Playing sound for worker " + str(my_sound_id))
# Start the loop timer
loop_timer.start()
# Called when audio becomes available in web builds
func check_and_play_sound():
print("DEBUG: Audio now ready for worker " + str(my_sound_id))
if is_construction_active and not construction_finished:
play_sound()
# Loop the construction sound independently
func loop_construction_sound():
print("DEBUG: Loop timer triggered for worker " + str(my_sound_id))
if is_construction_active and construction_sound and construction_sound.stream:
# Stop the sound if it's still playing (to prevent overlap)
if construction_sound.playing:
construction_sound.stop()
# Slight random pitch variation on each loop
construction_sound.pitch_scale = randf_range(0.9, 1.1)
@ -193,7 +218,7 @@ func loop_construction_sound():
construction_sound.play()
print("DEBUG: Looping sound for worker " + str(my_sound_id))
else:
print("ERROR: Cannot loop sound - either worker not active or sound not set up")
print("DEBUG: Cannot loop sound - either worker not active or sound not set up")
func finish_construction():
print("DEBUG: Worker " + str(my_sound_id) + " finishing construction")

@ -4,6 +4,7 @@ signal music_volume_changed(new_volume)
signal sfx_volume_changed(new_volume)
signal music_muted_changed(is_muted)
signal sfx_muted_changed(is_muted)
signal audio_ready # Signal emitted when audio is initialized (important for web)
# Volume ranges from 0.0 to 1.0
var music_volume: float = 0.8
@ -21,9 +22,38 @@ var sfx_bus_index: int
const MUSIC_BUS_NAME = "Music"
const SFX_BUS_NAME = "SFX"
var audio_initialized: bool = false
func _ready():
print("DEBUG: SoundManager initializing...")
# Setup audio buses (this doesn't actually start any audio playback)
_setup_audio_buses()
# For web builds, we need to detect user interaction to initialize audio
if OS.has_feature("web"):
print("Web build detected: Waiting for user interaction to initialize audio")
# Set a flag to track initialization
audio_initialized = false
# Connect to the input events to detect user interaction
# We'll use both mouse and keyboard events to be thorough
get_viewport().connect("gui_focus_changed", _on_user_interaction)
# Setup check for mouse clicks
# Input events must be connected to the root viewport
var root = get_tree().get_root()
if root:
root.connect("gui_input", _on_input_event)
print("Connected to input events for audio initialization")
else:
# For non-web platforms, we can initialize immediately
audio_initialized = true
print("Non-web build: Audio initialized immediately")
# Setup audio buses (doesn't start audio playback)
func _setup_audio_buses():
# Initialize audio bus indices
music_bus_index = AudioServer.get_bus_index(MUSIC_BUS_NAME)
sfx_bus_index = AudioServer.get_bus_index(SFX_BUS_NAME)
@ -62,8 +92,101 @@ func _ready():
if sfx_bus_index != -1:
AudioServer.set_bus_mute(sfx_bus_index, false)
# Called when any user interaction happens in web builds
func _on_user_interaction(_arg=null):
if OS.has_feature("web") and not audio_initialized:
_initialize_web_audio()
# Handle input events for web audio initialization
func _on_input_event(event):
if OS.has_feature("web") and not audio_initialized:
if event is InputEventMouseButton or event is InputEventKey:
if event.pressed:
_initialize_web_audio()
# If this method is called from JavaScript, it will help the game to
# initialize audio properly in web builds
func init_web_audio_from_js():
if OS.has_feature("web") and not audio_initialized:
print("Audio initialization requested from JS")
_initialize_web_audio()
# Initialize audio for web builds
func _initialize_web_audio():
if audio_initialized:
return
print("User interaction detected: Initializing web audio...")
# For web browsers, we need a more direct and aggressive approach
if OS.has_feature("web"):
# 1. Explicitly unlock audio context by using JavaScript
JavaScript.eval("""
// Function to unlock Web Audio
function unlockAudio() {
// Get the AudioContext
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// Resume it (modern browsers)
if (audioCtx.state === 'suspended') {
audioCtx.resume().then(() => {
console.log('Audio context resumed successfully');
});
}
// Create and play a silent buffer (older browsers)
var buffer = audioCtx.createBuffer(1, 1, 22050);
var source = audioCtx.createBufferSource();
source.buffer = buffer;
source.connect(audioCtx.destination);
source.start(0);
console.log('Web Audio unlock attempt complete');
// Also click to unlock for iOS
document.removeEventListener('click', unlockAudio);
document.removeEventListener('touchstart', unlockAudio);
document.removeEventListener('touchend', unlockAudio);
document.removeEventListener('keydown', unlockAudio);
}
// Try to unlock now
unlockAudio();
""")
# Resume the AudioServer context
AudioServer.set_bus_mute(0, false) # Unmute master bus
# Play and immediately stop a silent sound to initialize the audio context
var silent_player = AudioStreamPlayer.new()
add_child(silent_player)
# Create a very short silent audio stream
var silent_stream = AudioStreamWAV.new()
silent_stream.format = AudioStreamWAV.FORMAT_16_BITS
silent_stream.stereo = true
silent_stream.data = PackedByteArray([0, 0, 0, 0]) # Minimal silent data
# Play and immediately stop to kickstart audio
silent_player.stream = silent_stream
silent_player.volume_db = -80.0
silent_player.play()
# Wait a moment before stopping (important for some browsers)
await get_tree().create_timer(0.1).timeout
silent_player.stop()
# Clean up
await get_tree().process_frame
silent_player.queue_free()
# Set the flag to prevent multiple initializations
audio_initialized = true
print("Web audio initialized successfully")
print("SoundManager initialized with volumes - Music: ", music_volume, ", SFX: ", sfx_volume)
# Notify any waiting game systems that audio is now available
audio_ready.emit()
# Set music volume (0.0 to 1.0)
func set_music_volume(volume: float):