levelit_soundcomparison_post

Sound of Crashing Tower in “Level It!” with Unity3d

My mobile game Level It! has towers built out of a few hundred small wooden blocks. When hit the tower falls and a crash sound plays. In this post I describe how I programmed the crash sound in Unity3d.

 

First version of the crash sound: no sound dynamics and too slow

Every time a collision is detected the sound of a single crash sound is played. By a single crash sound I mean the sound of one block hitting another block – one single “click”-sound.

There is a Unity tutorial that explains how to do this.

Unfortunately in the Level It! case – when a tower of 100s of blocks falls there are tousands of little crashes happening in a short period of time – it didn’t sound realistic at all. I tried to tweak it in several ways but with no real improvement.
 

Improved version of the crash sound with long recorded crash sound

The basic idea is to play a longer recorded crash sound while tower blocks are colliding and stop playing when no collision happens for a while. I found two good crash sounds on Soundcloud.

The idea for the code is to start playing the long crash sound if several collisions happened in a short time. While playing every new collision increases the sound volume up to the maximum volume. If no collision happens the volume is decreased regularly until the crash sound is completely stopped.

The following code is the entry point. The block is a prefab so every block contains this. (In Unity editor use „Add Component“ then choose „script“ and add it). So if a collision is detected and the other object is also a block then the SoundManager is called.
 

BlockCollision Class
public class BlockCollision : MonoBehaviour
{
    void OnCollisionEnter(Collision coll)
    {
        if (coll.gameObject.tag == "bullet")
        {
            return;
        }

        float speed = coll.relativeVelocity.magnitude;
        SoundManager.instance.PlayCrashSound(Definitions.CrashsoundId, speed);
    }
}

 

SoundManager Class

The SoundManager is a singleton so it can be accessed by the colliding blocks easily.

Here’s the setup of the SoundManager Class:

public class SoundManager : MonoBehaviour
{
    private static SoundManager _instance;

    private static AudioSource[] _audioSource;
    private static AudioSource _audioSourceGeneral;
    private static AudioSource _audioSourceLongCrash;
    private static AudioSource _audioSourceSmallCrash;

    private static Stopwatch _stopwatch;
    private static long lastTimeSound;

    private static int _longCrashVolume;
    private static bool playingLongCrash = false;
    private static bool _longCrashLowererCalled = false;
    private static Coroutine _coroutineLongCrashLowerer;

    [...]

    void Start()
    {
        _audioSource = GetComponents<AudioSource>();
        _audioSourceGeneral = _audioSource[0];  // empty
        _audioSourceLongCrash = _audioSource[1];
        _audioSourceSmallCrash = _audioSource[2];

        _stopwatch = new Stopwatch();
        _stopwatch.Start();
        lastTimeSound = _stopwatch.ElapsedMilliseconds;
        _longCrashVolume = 0;
    }

    [...]
}

 

Unity Editor

In the Unity editor the sound files are added as Audio Sources.
soundmanager_levelit
 

SoundManager Methods

If no collisions is detected for a while (80 ms) then a single crash sound is played (one single click-sound).

If several (5) collisions are detected in a shorter interval than 80 ms then the long crash sound is starting to play. The volume is increased with every new collision (up to a maximum) and decreased every 100ms. When the volume reaches 0 the sound is stopped.

The following code shows this mechanism. It is the main part of the SoundManager:

public void PlayCrashSound(int soundId, float speed)
{
    if (!_soundOn || speed < minSpeed) { return; } long currentTime = _stopwatch.ElapsedMilliseconds; if (currentTime - lastTimeSound > 80) // single blocks crashes
    {
        PlaySingleCrash(speed);
        lastTimeSound = currentTime;
    }
    else
    {
        if (!playingLongCrash && _longCrashVolume > 5) // lots of blocks crash in short time
        {
            StartPlayingLongCrash();
            StartLongCrashFader();
        }

        lastTimeSound = currentTime;
        IncreaseLongCrashVolume();
    }

    UpdateLongCrashVolume();
}

void StartLongCrashFader()
{
    _longCrashLowererCalled = true;
    _coroutineLongCrashLowerer = StartCoroutine(LowerLongCrashVolume());
}

void StopLongCrashLowerer()
{
    if (_coroutineLongCrashLowerer != null)
    {
        StopCoroutine(_coroutineLongCrashLowerer);
        _longCrashLowererCalled = false;
    }
}

IEnumerator LowerLongCrashVolume()
{
    yield return new WaitForSeconds(0.1f);
    DecreaseLongCrashVolume();
    StartLongCrashFader();
}

private void IncreaseLongCrashVolume()
{
    if (_longCrashVolume < 10) { _longCrashVolume += 1; UpdateLongCrashVolume(); } } private void DecreaseLongCrashVolume(int number = 2) { if (_longCrashVolume > 0)
    {
        _longCrashVolume -= number;
        UpdateLongCrashVolume();
    }
}

private void StartPlayingLongCrash()
{
    playingLongCrash = true;
    UpdateLongCrashVolume();
    if (_towerCrashType == TowerCrashType.BigAndMediumTower)
    {
        _audioSourceLongCrash.Play();
    }
    else // if (_towerCrashType == TowerCrashType.SmallTower)
    {
        _audioSourceSmallCrash.Play();
    }
}

private void StopPlayingLongCrash()
{
    StopLongCrashLowerer();
    playingLongCrash = false;
    if (_towerCrashType == TowerCrashType.BigAndMediumTower)
    {
        _audioSourceLongCrash.Stop();
    }
    else
    {
        _audioSourceSmallCrash.Stop();
    }
}

private void UpdateLongCrashVolume()
{
    if (_longCrashVolume <= 0 && playingLongCrash)
    {
        StopPlayingLongCrash();
    }

    _audioSourceLongCrash.volume = _longCrashVolume*volumeLongCrashBigTower;
    _audioSourceSmallCrash.volume = _longCrashVolume*volumeLongCrashSmallTower;
}

 

Comparison of the two versions

Check out the difference of the 2 versions in the video below.

Leave a Reply

Your email address will not be published. Required fields are marked *