Skip to content

Sound System

Overview

SoundManager handles all audio in the game - music, sound effects, and user preferences.

What's Included

Sound Effects

  • 🎵 Shape grab
  • 🎯 Shape place
  • ✨ Line clear
  • 🔥 Combo sounds
  • ❌ Invalid placement
  • 🎊 Game over

Music

  • 🎼 Background music (loop)
  • 🎶 Menu music (optional)

SoundManager Component

Inspector Settings

┌─ SoundManager ──────────────────┐
│ Music Volume: 0.7                │
│ SFX Volume: 1.0                  │
│                                  │
│ Background Music: [AudioClip]    │
│                                  │
│ Sound Effects:                   │
│   Grab: [AudioClip]             │
│   Place: [AudioClip]            │
│   Clear: [AudioClip]            │
│   Combo: [AudioClip]            │
│   Invalid: [AudioClip]          │
│   GameOver: [AudioClip]         │
└──────────────────────────────────┘

How to Use

Playing Sounds

// From any script:
SoundManager.Instance.PlayGrab();
SoundManager.Instance.PlayPlace();
SoundManager.Instance.PlayClear();
SoundManager.Instance.PlayCombo();
SoundManager.Instance.PlayInvalid();
SoundManager.Instance.PlayGameOver();

Controlling Volume

// Music volume (0.0 - 1.0)
SoundManager.Instance.SetMusicVolume(0.5f);

// SFX volume (0.0 - 1.0)
SoundManager.Instance.SetSFXVolume(1.0f);

Muting

// Mute/unmute music
SoundManager.Instance.MuteMusic(true);

// Mute/unmute SFX
SoundManager.Instance.MuteSFX(true);

Audio Sources

SoundManager uses multiple AudioSources:

SoundManager GameObject
    ├── MusicSource (AudioSource)
    │   └── Loops: Yes
    │   └── Volume: musicVolume
    └── SFXSource (AudioSource)
        └── Loops: No
        └── Volume: sfxVolume

Singleton Pattern

public static SoundManager Instance { get; private set; }

void Awake()
{
    if (Instance != null && Instance != this)
    {
        Destroy(gameObject);
        return;
    }

    Instance = this;
    DontDestroyOnLoad(gameObject);
}

One instance, persists across scenes.

Integration Examples

Shape Dragging

// In NewDraggableShape.cs
public void OnBeginDrag(PointerEventData data)
{
    SoundManager.Instance.PlayGrab();
    // ... dragging logic
}

public void OnEndDrag(PointerEventData data)
{
    if (validPlacement)
    {
        SoundManager.Instance.PlayPlace();
    }
    else
    {
        SoundManager.Instance.PlayInvalid();
    }
}

Line Clearing

// In NewManager.cs
void OnLinesCleared(int count)
{
    if (count > 0)
    {
        SoundManager.Instance.PlayClear();
    }
}

Combo System

// In NewScoreManager.cs
void OnComboIncreased(int combo)
{
    if (combo > 1)
    {
        SoundManager.Instance.PlayCombo();
    }
}

Settings UI

Creating Volume Sliders

public class AudioSettings : MonoBehaviour
{
    [SerializeField] private Slider musicSlider;
    [SerializeField] private Slider sfxSlider;

    void Start()
    {
        // Load saved volumes
        musicSlider.value = PlayerPrefs.GetFloat("MusicVolume", 0.7f);
        sfxSlider.value = PlayerPrefs.GetFloat("SFXVolume", 1.0f);

        // Apply saved settings
        SoundManager.Instance.SetMusicVolume(musicSlider.value);
        SoundManager.Instance.SetSFXVolume(sfxSlider.value);

        // Listen for changes
        musicSlider.onValueChanged.AddListener(OnMusicVolumeChanged);
        sfxSlider.onValueChanged.AddListener(OnSFXVolumeChanged);
    }

    void OnMusicVolumeChanged(float value)
    {
        SoundManager.Instance.SetMusicVolume(value);
        PlayerPrefs.SetFloat("MusicVolume", value);
    }

    void OnSFXVolumeChanged(float value)
    {
        SoundManager.Instance.SetSFXVolume(value);
        PlayerPrefs.SetFloat("SFXVolume", value);
    }
}

Mute Buttons

public class MuteButton : MonoBehaviour
{
    [SerializeField] private Image icon;
    [SerializeField] private Sprite mutedIcon;
    [SerializeField] private Sprite unmutedIcon;

    private bool isMuted;

    public void ToggleMute()
    {
        isMuted = !isMuted;
        SoundManager.Instance.MuteSFX(isMuted);
        icon.sprite = isMuted ? mutedIcon : unmutedIcon;
    }
}

Audio Clip Recommendations

File Formats

  • WAV - High quality, larger size
  • OGG - Good quality, smaller size (recommended)
  • MP3 - Not recommended (licensing issues)

Clip Lengths

  • SFX: 0.1 - 0.5 seconds (short!)
  • Music: 1-3 minutes (loops)

Sample Rates

  • SFX: 22050 Hz (sufficient)
  • Music: 44100 Hz (better quality)

Advanced Features

Pitch Variation

Make sounds less repetitive:

public void PlayPlace()
{
    sfxSource.pitch = Random.Range(0.95f, 1.05f);
    sfxSource.PlayOneShot(placeClip);
    sfxSource.pitch = 1f;  // Reset
}

Sound Pools

For frequently played sounds:

private AudioSource[] sfxPool;

void Awake()
{
    sfxPool = new AudioSource[5];
    for (int i = 0; i < 5; i++)
    {
        GameObject obj = new GameObject($"SFX_{i}");
        obj.transform.parent = transform;
        sfxPool[i] = obj.AddComponent<AudioSource>();
    }
}

public void PlaySFX(AudioClip clip)
{
    AudioSource available = Array.Find(sfxPool, s => !s.isPlaying);
    if (available != null)
    {
        available.PlayOneShot(clip);
    }
}

Fade In/Out

public void FadeOutMusic(float duration)
{
    StartCoroutine(FadeOutCoroutine(duration));
}

IEnumerator FadeOutCoroutine(float duration)
{
    float startVolume = musicSource.volume;
    float elapsed = 0f;

    while (elapsed < duration)
    {
        elapsed += Time.deltaTime;
        musicSource.volume = Mathf.Lerp(startVolume, 0f, elapsed / duration);
        yield return null;
    }

    musicSource.Stop();
    musicSource.volume = startVolume;
}

Distance-Based Volume

If you add 3D audio later:

public void PlaySFXAt(Vector3 position, AudioClip clip)
{
    AudioSource.PlayClipAtPoint(clip, position, sfxVolume);
}

Audio Mixer (Optional Enhancement)

For more control, add an Audio Mixer:

1. Create Audio Mixer Asset
2. Add groups: Master, Music, SFX
3. Assign AudioSources to groups
4. Control with code:

audioMixer.SetFloat("MusicVolume", Mathf.Log10(volume) * 20);

Mobile Considerations

Audio Settings

// On mobile, lower music volume by default
#if UNITY_ANDROID || UNITY_IOS
    float defaultMusicVolume = 0.5f;
#else
    float defaultMusicVolume = 0.7f;
#endif

Background Audio

void OnApplicationPause(bool paused)
{
    if (paused)
    {
        musicSource.Pause();
    }
    else
    {
        musicSource.UnPause();
    }
}

Performance Tips

DO:

✅ Use compressed audio (OGG) ✅ Load clips in Inspector (faster) ✅ Use AudioSource pooling ✅ Set appropriate quality settings

DON'T:

❌ Load clips at runtime ❌ Use very high sample rates ❌ Play too many sounds at once ❌ Forget to stop unused sources

Debugging

void Update()
{
    if (Input.GetKeyDown(KeyCode.M))
    {
        Debug.Log($"Music: {musicSource.isPlaying}, " +
                  $"Volume: {musicSource.volume}");
    }
}

Common Issues

Sound not playing

  • ✅ Check clip is assigned
  • ✅ Check volume > 0
  • ✅ Check AudioSource exists
  • ✅ Check not muted

Sound cutting off

  • ✅ Use PlayOneShot for overlapping
  • ✅ Use audio source pool
  • ✅ Don't reuse same source too fast

Music not looping

  • ✅ Check "Loop" checkbox on AudioSource
  • ✅ Verify clip is assigned

What's Next?


Audio Tip

Good audio = 50% of game feel! Don't skip sound effects. Even simple blips make huge difference!