Developers Club geek daily blog

1 year, 10 months ago
It is simple to reproduce a sound in Unity. It is necessary to create the AudioSource component to attach to it the sound file in the form of AudioClip and to cause audioSource.Play () from a script. Or even to put autoreproduction on during creation of object (Play on Awake).

Difficulties begin when sounds in game become much. They everything need to be placed, registered priorities. Sounds separately, music separately. At volume control of sounds and music separately too difficulties. It is possible to regulate, of course, the volume of different channels in AudioMixer, but it does not work in WebGL. And Webplayer is considered outdated now.

And if some sound repeats several times in a row (for example the player quickly presses the button and plays a click sound), then it is good that that did not break on the middle, and new began, without disturbing previous. Moreover and at inclusion of a pause sounds of game need to be put on a pause, and sounds there is no menu. From a box such opportunity in Unity is, but is for some reason available only from a script and not all know about it.

Generally there is a wish simple and convenient SoundManager which creation I also will describe. It will not be suitable for large projects, and here for prototypes and small games quite.

Sound Manager for small games and prototypes on Unity


So that has to represent SoundManager? Well first it has to be convenient to be used. That is any "to find object on a scene", "to attach a component" and other for the user, all inside. So at once we do him by Singleton (The code is reduced to select an essence).

    private static SoundManager _instance;

    public static SoundManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = (SoundManager)FindObjectOfType(typeof(SoundManager));

                if (_instance == null)
                {
                    GameObject singleton = (GameObject)Instantiate(Resources.Load<GameObject>(PrefabPath));
                    _instance = singleton.GetComponent<SoundManager>();
                    singleton.name = "(singleton) " + typeof(SoundManager).ToString();

                    DontDestroyOnLoad(singleton);
                }
            }

            return _instance;
        }
    }


Now the manager himself will create himself on a scene so it is not necessary to add him independently (and it is not recommended). It is created on a prefab, the way to which is registered in a code so you should not move prefab. It is possible to create also by means of new GameObject () and AddComponent () if there is a wish. Besides the object is marked by means of DontDestroyOnLoad at once. It is necessary in order that music and sounds continued to play without interruption at resets of scenes.
Now it is possible to address any methods just having written SoundManager.Instance.Method (). To reduce a little more this record for all methods I added a static vrapper:

    public static void PlayMusic(string name)
    {
        Instance.PlayMusicInternal(name);
    }


So it is even possible to write even well than SoundManager.Method ().

Object it is convenient to eat, work with it. We add functionality further. The most necessary function is PlaySound:

    void PlaySoundInternal(string soundName, bool pausable)
    {
        if (string.IsNullOrEmpty(soundName)) {
            Debug.Log("Sound null or empty");
            return;
        }

        int sameCountGuard = 0;
        foreach (AudioSource audioSource in _sounds)
        {
            if (audioSource.clip.name == soundName)
                sameCountGuard++;
        }

        if (_sounds.Count > 16) {
            Debug.Log("Too much duplicates for sound: " + soundName);
            return;
        }

        StartCoroutine(PlaySoundInternalSoon(soundName, pausable));
    }

    IEnumerator PlaySoundInternalSoon(string soundName, bool pausable)
    {
        ResourceRequest request = LoadClipAsync("Sounds/" + soundName);
        while (!request.isDone)
        {
            yield return null;
        }

        AudioClip soundClip = (AudioClip)request.asset;
        if (null == soundClip)
        {
            Debug.Log("Sound not loaded: " + soundName);
        }

        GameObject sound = (GameObject)Instantiate(soundPrefab);
        sound.transform.parent = transform;

        AudioSource soundSource = sound.GetComponent<AudioSource>();
        soundSource.mute = _mutedSound;
        soundSource.volume = _volumeSound * DefaultSoundVolume;
        soundSource.clip = soundClip;
        soundSource.Play();
        soundSource.ignoreListenerPause = !pausable;

        _sounds.Add(soundSource);
    }


To start several checks of a sound. That he not empty and that such sounds disappeared too much (If where that in a cycle on an error is called). Then we load a sound from resources, we wait for loading, we create new object on a scene, we add AudioSource, we configure it and it is started. The LoadClipAsync function starts asynchronous loading of the sound file from resources by name. So the file should be put in the Resources/Sounds/Sounds folder. Creation of object happens on a prefab which is loaded from resources. So part of parameters (like a sound priority), the prefab can set from the inspector. Volume is installed also at each object separately. Unlike installation of volume AudioListener-at it allows to regulate the volume of sounds and music separately. Let's save object in the list of sounds _sounds to have an opportunity to regulate its volume and to destroy on the termination.

The pausable parameter is necessary to separate UI sounds and game sounds. The first have to be played always and be never put on a pause. The second stop during a pause and proceed when resuming game. It becomes automatically by means of a flag of soundSource.ignoreListenerPause which is for some reason unavailable from Inspector-and.

Further we need a method for adding of music in game. In general the code is similar to adding of a sound, but another is used prefab (with a durgy priority and the loop setup).

    void PlayMusicInternal(string musicName)
    {
        if (string.IsNullOrEmpty(musicName)) {
            Debug.Log("Music empty or null");
            return;
        }

        if (_currentMusicName == musicName) {
            Debug.Log("Music already playing: " + musicName);
            return;
        }

        StopMusicInternal();

        _currentMusicName = musicName;

        AudioClip musicClip = LoadClip("Music/" + musicName);

        GameObject music = (GameObject)Instantiate(musicPrefab);
        if (null == music) {
            Debug.Log("Music not found: " + musicName);
        }
        music.transform.parent = transform;

        AudioSource musicSource = music.GetComponent<AudioSource>();
        musicSource.mute = _mutedMusic;
        musicSource.ignoreListenerPause = true;
        musicSource.clip = musicClip;
        musicSource.Play();

        musicSource.volume = 0;
        StartFadeMusic(musicSource, MusicFadeTime, _volumeMusic * DefaultMusicVolume, false);

        _currentMusicSource = musicSource;
    }


In the majority of small projects one track which is lost at present so start of new music stops the previous tracks automatically so on each scene it is enough to cause only SoundManager.PlayMusic ("MusicForCurrentScene") suffices; Besides during creation and a stop of music smooth increase of volume and smooth fading is added. It allows to make transition smooth and does not beat hearing. Smooth change of volume can be done Tween-ohm, but it is possible also handles that there were less dependences.

Further we need an opportunity to put a pause. As whether at all sounds setup is put already down they are put on a pause at a pause AudioListener-and, methods turn out very simple.

    public static void Pause()
    {
        AudioListener.pause = true;
    }

    public static void UnPause()
    {
        AudioListener.pause = false;
    }


Or it is possible to configure automatic inclusion of a pause.

    void Update()
    {
        if (AutoPause)
        {
            bool curPause = Time.timeScale < 0.1f;
            if (curPause != AudioListener.pause)
            {
                AudioListener.pause = curPause;
            }
        }
    }


Further we will need methods of installation and receipt of volume.

    void SetSoundVolumeInternal(float volume)
    {
        _volumeSound = volume;
        SaveSettings();
        ApplySoundVolume();
    }

    float GetSoundVolumeInternal()
    {
        return _volumeSound;
    }

    void SaveSettings()
    {
        PlayerPrefs.SetFloat("SM_SoundVolume", _volumeSound);
    }

    void LoadSettings()
    {
        _volumeSound = PlayerPrefs.GetFloat("SM_SoundVolume", 1);

        ApplySoundVolume();
    }

    void ApplySoundVolume()
    {
        foreach (AudioSource sound in _sounds)
        {
            sound.volume = _volumeSound * DefaultSoundVolume;
        }
    }


Here everything is simple. We save and read settings by means of PlayerPrefs, at change we are run on sounds and we apply new volume. It is similarly possible to make the mute setup and all most for music too.

There now and all. SoundManager which are easy for using is ready. As we took out templates for sounds and music in prefaba, it is easily possible to connect output to them from AudioMixer-and. Besides there is still a small class simplifying challenges of the necessary methods from animations, processors of buttons and pr that it was not necessary to write a script because of one line.

Sound Manager for small games and prototypes on Unity

Pluses of the received manager:
+ Usability
+ Pure code and objects of a scene. It is not necessary to hang up sound components anywhere, to look for and cause them from a code
+ Music which is not interrupted when loading a scene and changes smoothly
+ Geympleynye and UI sounds
+ Support of a pause
+ Support of AudioMixer
+ Work on all platforms, including not supporting AudioMixer (for example WebGL)
+ Support of a voice of the story-teller (in article it is not mentioned, but in a complete code it is implemented)

Restriction of the current implementation (Not yet):
— There is no position 3d sound yet
— Changes pitch-and a sound that many multiple repetition of identical sounds did not become boring
— Loading of a sound when using can lead to logs (Imperceptibly on small projects and small sounds)
— There is no volume control of separately taken sound
— There are no looped sounds, like an ambiyent

It is possible to look at a complete code of the manager on my GitHub-e:
https://github.com/Gasparfx/SoundManager

Our project the using this manager on GreenLight:
http://steamcommunity.com/sharedfiles/filedetails/?id=577337491

This article is a translation of the original post at habrahabr.ru/post/274529/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus