Game/MansionGame.Music.cs

Client-side music controller for the MansionGame. It chooses a music track for the current level, crossfades between tracks when the level changes, starts/stops playback, and applies a smooth volume lerp to a configured target volume.

Native Interop
using Sandbox;

namespace BrickJam;

public sealed partial class MansionGame
{
	/// <summary>How fast the music fades in/out (per second). Legacy faded on the server tick.</summary>
	public float MusicVolumeChangeRate => 0.5f;

	/// <summary>Target music volume - background level, the tracks are mastered loud.</summary>
	public float MusicVolume => 0.15f;

	private SoundHandle musicHandle;
	private LevelType musicLevel = LevelType.None;
	private float musicVolume;

	/// <summary>
	/// Client-side music orchestration (every client, host included - music is local audio). Scene-System
	/// port of the legacy host-side <c>ProcessMusic</c>: drive the track from the replicated
	/// <see cref="CurrentLevelType"/> and crossfade when the level changes.
	/// </summary>
	protected override void OnUpdate()
	{
		var track = Level.GetMusic( CurrentLevelType );

		if ( CurrentLevelType != musicLevel )
		{
			// Level changed: fade the old track out, then swap once it's silent.
			musicVolume = musicVolume.LerpTo( 0f, MusicVolumeChangeRate * Time.Delta );

			if ( musicVolume <= 0.01f )
			{
				musicHandle?.Stop();
				musicHandle = null;
				musicLevel = CurrentLevelType;
				musicVolume = 0f;
			}

			ApplyMusicVolume();
			return;
		}

		// Same level: keep the track playing (restart if the asset isn't looped) and fade toward target.
		if ( !string.IsNullOrEmpty( track ) )
		{
			if ( musicHandle is null || musicHandle.IsStopped )
			{
				musicHandle = Sound.Play( track );
				if ( musicHandle is not null )
					musicHandle.Volume = musicVolume; // start at the current (faded) level, not full blast
			}

			musicVolume = musicVolume.LerpTo( MusicVolume, MusicVolumeChangeRate * Time.Delta );
		}
		else
		{
			musicVolume = musicVolume.LerpTo( 0f, MusicVolumeChangeRate * Time.Delta );
			if ( musicVolume <= 0.01f && musicHandle is not null )
			{
				musicHandle.Stop();
				musicHandle = null;
			}
		}

		ApplyMusicVolume();
	}

	private void ApplyMusicVolume()
	{
		if ( musicHandle is not null && !musicHandle.IsStopped )
			musicHandle.Volume = musicVolume;
	}
}