Unity3D Tips #1 – Realistic Repeating SFX

This is the first Unity post in a series where we’ll share some of our experience and methods for working with the Unity3D game engine.

This is going to be a post about sound design and programming, by David Larsen and Mathias Soeholm. We will show you how we handle audio inside Unity, and make sure that repetitive sounds, sound good. Let’s talk about the latter first.

_____

David Larsen

When we use sound effects in Unity3D, we often use a principle called Round Robin, or anti-repetition. Basically, Round Robin is when you use multiple variations of the same sound effect, so when the sound effect is triggered, one of these variations is played at random. The purpose of this is to get rid of the so-called “machinegun-effect” – when a single audio file (for example footsteps, or hit impacts) is repeated in quick succession, it sounds very unnatural and unrealistic.

Here’s an example of a series of footsteps using the same audio file:

Listen to Title of audio file

Hardly sounds convincing, huh? It’s very obvious that it’s just the same sound effect played over and over again. Here’s how you fix it, using the Round Robin principle:
Let’s say you have a sound effect called “Explosion”. Now imagine you have 3 or more variations of that same explosion, called Exp_1.mp3, Exp_2.mp3 and Exp_3.mp3.
When the “Explosion” sound effect is triggered, have a script randomly select one of these sounds. First time it’s triggered, it might use Exp_2.mp3. Next Exp_1.mp3, then Exp_3.mp3, and then perhaps Exp_1.mp3 again. This gets rid of the machinegun effect, and it makes the sound effect feel much more dynamic and realistic.

Here is a set of footsteps that are triggered the same way as with the previous example, but now with a script that is randomly choosing between a selection of 5 variations of the footsteps. The difference should be very clear:

Listen to Title of audio file

At a minimum, you want 3 sound variations for this to work, but the more the better. If I can get away with it, I do 4-6 variations of each sound.
Something you want to be aware of, is that true randomization won’t necessarily work better. You don’t want to play back the same sound variation twice. Our solution was to exclude the audio files that had already been played from the selection. So if Exp_1.mp3 had just been used, only Exp_2.mp3 and Exp_3.mp3 would be available.

The biggest downside to using round Robin is of course that you need to come up with a lot of variations of each sound. While it’s easy enough to get a lot of sounds for footsteps and such, you might not be able to find multiple sounds for effects like explosions, impacts, etc. But remember, even very subtle things like a tiny pitchshift, a bit of EQ’ing, and maybe timestretching will get you very far – even if you don’t have the opportunity to get multiple recordings or design variations of a specific sound.

Mathias Soeholm, our gameplay programmer, made a really useful script for this. It’s called AudioHandler, and it manages all the audio in our games. It’s responsible for playing music and sound effects, and it has a Round Robin engine build right in.
Feel free to use this however you want. Below is the entire script, and documentation on how to use it, written by Mathias.

_______

Mathias Soeholm

The AudioHandler script, utilizes one of multiple audio sources in the scene, and selects one of them at random. I use multiple audio sources, because a single audio source can only play one sound at a time. This is a great system if you are using 2D sounds and you’re not dependent on where your audio sources are located in the scene.

AudioHandler.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class AudioCollection
{
    public int repeatInterval;
    public AudioClip[] audioClips;

    [HideInInspector]
    public List<int> recentlyPlayedSounds = new List<int>();
}

public class AudioHandler : MonoBehaviour
{
    enum MusicState
    {
        FullVolume,
        FadingIn,
        FadingOut,
        Muted
    }

    private const float FadeFactor = 0.2f;

    // Audio clips - Example variables
    public AudioClip deliverAtSafeZoneSound;
    public AudioCollection joinShoalSounds;

    private static MusicState musicState = MusicState.Muted;
    private static int nextAudioSource;
    private static List<AudioSource> audioSources = new List<AudioSource>();
    private static AudioCollection collectionToPlay;

    // Static reference to singleton object
    public static AudioHandler instance;

    // Methods
    void Start()
    {
        audioSources.Clear();
        instance = gameObject.GetComponent<AudioHandler>();
        
        // Trigger FadeIn
        musicState = MusicState.FadingIn;

        // Get references to all audio sources in the scene
        foreach (var audioSource in GameObject.FindGameObjectsWithTag("AudioSourceObject"))
        {
            audioSources.Add(audioSource.GetComponent<AudioSource>());
        }
    }

    void Update()
    {
        HandleMusicFade();
    }

    void HandleMusicFade()
    {
        switch (musicState)
        {
            case MusicState.FadingIn:
                audio.volume = Mathf.Min(audio.volume + FadeFactor*Time.deltaTime, 1);
                if (audio.volume >= 1)
                    musicState = MusicState.FullVolume;
                break;
            case MusicState.FadingOut:
                audio.volume = Mathf.Max(audio.volume - FadeFactor*Time.deltaTime, 0);
                if (audio.volume <= 0)
                    musicState = MusicState.Muted;
                break;
        }
    }

    public void PlayCipFromCollection(ref AudioCollection collection)
    {
        collectionToPlay = collection;

        StartCoroutine("PlayCollectionClip");
    }

    IEnumerator PlayCollectionClip()
    {
        yield return 0;

        int clipToPlay;

        // The use of coroutines enables us to use a while loop
        do
        {
            clipToPlay = Random.Range(0, collectionToPlay.audioClips.Length);

        } while (collectionToPlay.recentlyPlayedSounds.Contains(clipToPlay));

        collectionToPlay.recentlyPlayedSounds.Add(clipToPlay);

        if (collectionToPlay.recentlyPlayedSounds.Count > collectionToPlay.repeatInterval)
            collectionToPlay.recentlyPlayedSounds.RemoveAt(0);

        PlaySound(collectionToPlay.audioClips[clipToPlay]);
    }

    public static void PlaySound(AudioClip clip)
    {
        // Get index for the next audio source to use
        nextAudioSource = (nextAudioSource + 1) % audioSources.Count;

        if (audioSources[nextAudioSource].isPlaying) return;

        // Assign the clip to the selected audio source
        audioSources[nextAudioSource].clip = clip;

        // Play the clip with the selected audio source
        audioSources[nextAudioSource].PlayOneShot(clip);
    }
}

The picture below shows how the audio sources are set up on individual gameobjects, all with the tag “AudioSourceObject”, which is important for the script to be able to find and utilize the audio sources.

Sounds are added in the inspector, either as a single sound or a collection. The AudioHandler script is applied on a gameobject, which also contains an audio source for the music.

I’ve set up an example of each in the script:

// Audio clips - Example variables
public AudioClip deliverAtSafeZoneSound;
public AudioCollection joinShoalSounds;

Here’s how it looks in the inspector:

Each sound collection contains a variable called ‘repeatInterval’. This is how many other sounds, which must be played before repeating a previously played sound. The repeat interval must always be below the amount of sounds in a collection, otherwise you’ll end up in an infinite while loop, and that’s bad!

To play a sound from a collection, from other scripts, you use the static instance of the AudioHandler class, which references the AudioHandler component on the gameobject. Here’s an example:

// Play sound
AudioHandler.instance.PlayCipFromCollection(ref AudioHandler.instance.joinShoalSounds);

The ‘ref’ keyword is used to pass by reference and not as a copy.

To play a single sound that is not part of a collection, you simply call the static PlaySound() method. Here’s an example:

// Play sound
AudioHandler.PlaySound(AudioHandler.instance.deliverAtSafeZoneSound);

If you have any trouble, I’ll be glad to help you out just leave a comment below. And if you make any improvements, please share. :-)

/Mathias


Comments

Lucifers Maze

We spent the holiday at the Copenhagen Games e-sport event. During the event, we worked together with our neighbor, Thermal Games, developing a small game and giving the attendees some insight to the process of developing a game.
The event lasted four days, and during that time we managed to create a small tower defense game. We only decided the genre, letting the visitors who stopped by our booth determine everything else, from protagonists and antagonists, to what kind of abilities the towers should have.
It was a great experiment, and the gamers and visitors who came with suggestions were very creative. We gave out five CS:GO beta keys as rewards to the best ideas for towers, and three Diablo III beta keys for the best name for the game: Lucifer’s Maze.

Working with Thermal Games and the people at Copenhagen Games was really fun and a great experience.

Check out Lucifers Maze here. While you play, keep in mind that the point with the event was not to make a finished game, but to give people an idea of the process. As a result, the game is fairly unfinished and buggy, but we hope you enjoy it none the less. Tell us what you think in the comments below.

 


Comments

Copenhagen Games & Cado pricedrop!

First of all, Cado HD is now permanently 66% off! That’s 100% game for only 33% of the price!
Grab Cado HD for $1 here

Tomorrow it is time for Copenhagen Games, the biggest eSport event in Denmark. Professional gamers are pitted against in other in a virtual bloodbath featuring competitive games like Starcraft II and Counter Strike. So what does this have to do with casual iOS games? Well, we’re going to be there, all four days of the event, developing a game!

We have teamed up with our neighbor, Thermal Games, and we are going to have a booth, smack in the middle of the event. During that time we will make a small game, collaborating with Thermal Games. But it doesn’t stop there; we will base the entire game on input from the gamers at the event, so if you have always wanted to help make a game, here’s your chance.

With us, we are bringing 100 promo codes for Cado and Cado HD. We will be giving these away to everybody we see during the event. If you are attending, leave a comment below, and we’ll make sure to save one for you!

Copenhagen Games starts Wednesday and will last four days until Saturday night, and we will be there the entire time. So if you are going, be sure to stop by to get a free promo code for Cado, and command us when we make a game.

Check out Copenhagen Games on Facebook


Comments

Happy New Year!

First of all, Happy New Year everyone. 2011 have been an exciting year; we released our first game, and set our first prints in the game industry. The year has been the beginning of a journey, which we’re continuing with increasing speed and enthusiasm. Because as excting as 2011 has been for us, 2012 is looking even more promising, as we recently a subsidy for our next game project from DFI (The Danish Film Institute). DFI is a government agency that has support programmes for film and game projects, and our project was among the ones to recieve the subsidy:

Minnow (Working title)
An invasion is threatining your home in the coral reef. From the abyss far beneath the surface, deep sea creatures are attacking the reef, and as the biggest fish in the coral reef, it’s up to you to fend off the invading forces. Gather the frightened minnows of the coral reef, and assemble a shoal big and strong enough to take on the horrible enemies. Explore the depths of the ocean, and fend of enemies, while you collect their valuable luminous orbs. Find rare coral fish, who with their unique ablities will help you in your fight. Alone you can never hope to prevail, but if all the coral fish stick together, you might just be able to defeat the invading forces, and once again bring peace to the coral reef.

The game is a 2.5D action/adventure game for iPhone, iPad and iPod Touch, and it will utilize the touch screen to the fullest, giving you complete control of your shoal with the tip of your finger.

As always, we will keep you updated with progress here, on Facebook and on Twitter.

 

But that’s not all!
We’re also just about to release a small free game for your enjoyment. Stay tuned.

 


Comments

Medic!

First blog entry since Cado was released – a lot of stuff has been going on.

We recently released a 20 level bonus pack for Cado, and while we worked on that, we were also working on a commission by the Center for Medicinal Education at Aarhus University, which we finished this week. We were tasked with making a game that could be used in the classroom, specifically for spicing up the boring (not our own words) classes about medicinal laws and ethics.

The game is called ‘Medic!’, and is set in the middle of a city that has been hit by an earthquake. You control the only ambulance that is left, as you race around the city, trying to save as many people as you can. Regularly you will be faced by tough dilemmas, where you have to choose who to save.
Unfortunately, the game not yet available to the public. But don’t worry, we have something bigger coming up, expect a post about that soon.

In the meantime,  here are some shiny screenshots of ‘Medic!’:


Comments

Page 1 of 3123