Building a real-time strategy game in Unity 4.1 using C# scripting

<
>

Part 20: Audio

The goal for this time is to make our game a little more interesting. We will do this by adding in some sound effects to liven up the atmosphere of our game.

A quick note before we get into things. All of the sounds I am using for this were taken from www.soundgator.com. They are all demo sounds that are merely intended to highlight the different things that we are doing in our game. For an actual game I would recommend working with a sound designer of some sort to make sure that a) sounds in your game complement both each other and your theme and b) you have appropriate levels between the different sounds you use so that you do not have to mess around in Unity too much.

The other thing I want to do in this part is to fix some issues we have introduced when it comes to launching our game. At the moment we need to launch the game for the Main Menu before we can do anything. This is exactly how we want things to work when we release our game to other people - it is the only sensible entry point for a Player. However, if we are testing things it would be nice to be able to launch Unity from within one of our Map scenes. This will save us from having to do lots of repetitive Menu navigation. And the more time we can save the more efficient we can be at actually building a game. So we will fix the issues that are preventing us from doing this and then we will turn our attention to getting some sound effects working in our project.

Bugfixes

The main reason that we are not able to start a game from within a Map scene is that we are now expecting the Player to have decided who they are for the current session. Part of this process involves selecting an Avatar, which we are then displaying in the lower right corner of the screen while they are playing on a given Map. Unfortunately, when we launch from within a Map scene none of this process is completed, which causes some errors to be thrown in Unity. It also means that things like saving / loading will not work either, since we do not know who the Player is.

We will start our bugfix by making sure that a Player is selected when our level loads, assuming that there is not a Player already selected that is. The loading of a level is being handled by LevelLoader.cs. When we launch from inside a Map scene the Awake() method will be called, so we need to add our changes there. Add the following code

				if(initialised) {
					SelectPlayerMenu menu = GameObject.FindObjectOfType(typeof(SelectPlayerMenu)) as SelectPlayerMenu;
					if(!menu) {
						//we have started from inside a map, rather than the main menu
						//this happens if we launch Unity from inside a map file for testing
						Player[] players = GameObject.FindObjectsOfType(typeof(Player)) as Player[];
						foreach(Player player in players) {
							if(player.human) {
								PlayerManager.SelectPlayer(player.username, 0);
							}
						}
					}
				}

to the end of that method. It is only possible for one LevelLoader to be initialised at any given time, so we know that this code will only be run once. Awake() is only called once on the object, meaning that this code will not be called again when we go from the MainMenu into a scene. Finally, we also know that the Select Player Menu is used to select who the Player is. If this Menu does not exist in the current scene then we know that we need to select a Player, since one has not already been selected. In this case the code simply finds the human Player in the scene and selects them as the current Player.

Before this code will run, of course, we need to add a LevelLoader to our Map scene. Open the scene and create a new EmptyObject called LevelLoader. Attach LevelLoader.cs to this EmptyObject and save the scene.

This fixes one issue, but it does not solve the error messages being constantly thrown by our HUD when it attempts to draw the Avatar selected for our Player. This is because PlayerManager.SetAvatarTextures() is currently being called from inside our Select Player Menu, and this does not exist in our scene when we launch directly from a Map. What we will do is shift the list of Avatars away from the Menu (which is really too specific a place to have them) and add them to GameObjectList instead. Add

				public Texture2D[] avatars;

to the top of GameObjectList.cs. Then add

				PlayerManager.Load();
				PlayerManager.SetAvatarTextures(avatars);

into Awake() inside the if(!created) block. Finally add the new method

				public Texture2D[] GetAvatars() {
					return avatars;
				}

so that we have a way to access the Avatars in the GameObjectList. We want to be able to access these Avatars from everywhere, so add

				public static Texture2D[] GetAvatars() {
					return gameObjectList.GetAvatars();
				}

to ResourceManager.cs so that we can easily do so. To keep changes to a minimum we should actually create a GameObjectList prefab, so do that now by dragging the GameObjectList in the MainMenu scene into the Resources folder. We need to make sure that the list of Avatars in our GameObjectList is not empty, so add the Avatars you had in the Select Player Menu to the Avatars list for the GameObjectList prefab. Now load up the Map scene, delete the existing GameObjectList, and then add the GameObjectList prefab to the scene. Now whenever we make a change to the GameObjectList prefab we know that the changes will be made to any GameObjectList that exists in a scene somewhere. This will be useful when we come to play test a new Map, since we know that to get access to all of our GameObjects we just need to add a copy of the GameObjectList prefab to the scene.

Now that we have changed how our Avatars are being handled, we need to update SelectPlayerMenu.cs to reflect this. Start by changing

				public Texture2D[] avatars;

to

				private Texture2D[] avatars;

so that we can no longer edit it from within Unity. Then update Start() so that it has the following code in it.

				void Start () {
					avatars = ResourceManager.GetAvatars();
					if(avatars.Length > 0) avatarIndex = 0;
					SelectionList.LoadEntries(PlayerManager.GetPlayerNames());
				}

This makes sure that we load the Avatars from our GameObjectList, rather than setting them based on what had been added in Unity.

The final thing we want to do is to make sure that PlayerManager.cs does not throw exceptions if no Avatars have been set. Add

				if(avatars == null) return null;

to the start of GetPlayerAvatar() to make sure this does not happen.

You should now be able to run your game from within your Map scene without any errors being thrown. You should also see the first Avatar in your list being shown in the lower left corner of the screen, next to the default name you set for the human Player in the scene.

Audio Element

With that out of the way it is time to turn our attention to get some sounds playing in our game. We will begin by defining a place to store all of our Audio related things. Inside the Assets folder create a new folder called Audio. Inside this folder create a new folder called Sounds. Any sound effects that we are going to add to our project should go inside this folder. Finally, inside the Audio folder create a new C# script called AudioElement.cs with the following code in it.

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

				public class AudioElement {
					
					private GameObject element;
					private Dictionary< AudioClip, GameObject > soundObjects = new Dictionary< AudioClip, GameObject >();
					
					public AudioElement(List< AudioClip > sounds, List< float > volumes, string id, Transform parentTransform) {
						if(sounds == null || sounds.Count == 0 || volumes == null || volumes.Count == 0 || sounds.Count != volumes.Count) return;
						element = new GameObject("AudioElement_" + id);
						if(parentTransform) element.transform.parent = parentTransform;
						else {
							//attach it to the game object list (since we know there should be one present)
							//do so to keep the inspector cleaner - this saves making a sounds object
							GameObjectList list = MonoBehaviour.FindObjectOfType(typeof(GameObjectList)) as GameObjectList;
							if(list) element.transform.parent = list.transform;
						}
						Add (sounds, volumes);
					}
					
					public void Add(List< AudioClip > sounds, List< float > volumes) {
						for(int i = 0; i < sounds.Count; i++) {
							AudioClip sound = sounds[i];
							if(!sound) continue;
							GameObject temp = new GameObject(sound.name);
							temp.AddComponent(typeof(AudioSource));
							temp.audio.clip = sound;
							temp.audio.volume = volumes[i];
							temp.transform.parent = element.transform;
							soundObjects.Add(sound, temp);
						}
					}
					
					public void Play(AudioClip sound) {
						GameObject temp;
						if(soundObjects.TryGetValue(sound, out temp)) {
							if(!temp.audio.isPlaying) temp.audio.Play();
						}
					}
					
					public void Pause(AudioClip sound) {
						GameObject temp;
						if(soundObjects.TryGetValue(sound, out temp)) {
							temp.audio.Pause();
						}
					}
					
					public void Stop(AudioClip sound) {
						GameObject temp;
						if(soundObjects.TryGetValue(sound, out temp)) {
							temp.audio.Stop();
						}
					}
					
					public bool IsPlaying(AudioClip sound) {
						GameObject temp;
						if(soundObjects.TryGetValue(sound, out temp)) {
							return temp.audio.isPlaying;
						}
						return false;
					}
				}

This class will be acting as an audio manager for any class that we add it to. There are a number of reasons why we are doing it this way. Unity only allows a single AudioClip to be added to a GameObject. This is problematic, since there are several of our WorldObjects that are going to require more than one sound. Initially I tried to add a child object for each sound that I wanted to play. However, for some reason unknown to me, Unity would refuse to actually play the sound clip when requested. And so I played around with this approach instead, as an alternative to make sure I could play the sounds that I wanted, when I wanted. And it turns out that this design actually works really nicely, and 3d sounds work really well with no extra effort on our part too.

The basic idea is that when our AudioElement is created we create a new GameObject. Then, for each sound clip passed in we create a GameObject with that sound clip attached. This is needed for us to be able to play the sound clips and mess around with things like volume levels. These GameObjects are then attached to the main GameObject we created for the AudioElement. The main GameObject is then attached to the parent transform passed into the constructor. This means it will move around with the parent object (e.g. this could be attached to a Unit), making 3d sound work nicely for us. If no transform is passed in we want to attach the GameObject to the GameObjectList in the scene, to make sure that our inspector does not become unnecessarily cluttered.

This works nicely for structure within our scene. On top of this, though, we want an easy way to be able to access a particular sound object so that we can start / stop it. We could find the appropriate child of the main GameObject we created, but this will add extra overhead to finding the sound clip - overhead that will quickly add up and could have a moderate performance impact on our game. The smarter option we are going to use is to create a Dictionary which maps each sound clip to the GameObject that was created for it. Then, when we call Play(soundClip), Stop(soundClip), etc. we can quickly retrieve the appropriate GameObject from the Dictionary and tell it to perform the appropriate action. This adds a bit of extra memory overhead to our game, but the I believe the performance increase will be worth it in the long run. (And before anyone asks, no, I have not run any performance tests on this. But it should be fairly obvious that a retrieval from a Dictionary will be much faster than getting child objects and iterating through them to find the one that we want)

So we have defined a way to play one or more audio clips from a particular object in our game. Now it is time to make use of that to actually make some noise. The first thing we will add sound to is button clicks in all of our menus. Unfortunately the design of our menu code is actually not as good as it could be, so this will take a little more work than it should to get going. However, I am not prepared to rewrite all of the Menu code, so we will just have to live with it. Feel free in your own projects to tidy things up though. Do not let my poor design in some places hold you back from making improvements. After all, this is a tutorial and not production code.

Let's start with Menu.cs. Begin by adding the variables

				public AudioClip clickSound;
				public float clickVolume = 1.0f;

				private AudioElement audioElement;

to the top of the file. Then add

				if(clickVolume < 0.0f) clickVolume = 0.0f;
				if(clickVolume > 1.0f) clickVolume = 1.0f;
				List< AudioClip > sounds = new List< AudioClip >();
				List< float> volumes = new List< float >();
				sounds.Add(clickSound);
				volumes.Add (clickVolume);
				audioElement = new AudioElement(sounds, volumes, "Menu", null);

to the end of Start(). This makes sure that the specified desired volume for the click is constrained to the range of 0.0f - 1.0f and then creates an AudioElement for the Menu. Since this is a Menu we do not care about where in our world the AudioElement sits, so we define no parent transform. This means that the AudioElement for the Menu will be added as a child of GameObjectList. We also need to add

				using System.Collections.Generic;

to the very top of the file to allow us to access List< > in our code.

The final change we want to make here is to add

				if(audioElement != null) audioElement.Play(clickSound);

into HandleButton() to make sure that each time we handle a button we also play the click sound.

There are currently two Menus (out of 5 ...) in our game which are actually child classes of Menu.cs: MainMenu.cs and PauseMenu.cs. In both of these we need to add

				base.HandleButton(text);

to the top of HandleButton() to make sure that the default behaviour for this method runs - in this case the playing of the button click sound.

We also need to attach a sound clip to these Menus before a sound will actually play. I have used the sound button_click for the button clicks in my project. To attach this to the Pause Menu expand the Player prefab in the Player folder, click on the HUD element, the add the button click sound to the appropriate field for the Pause Menu in the inspector. To attach it to the Main Menu open the MainMenu scene, click on the camera, and add the button click sound to the appropriate field for the Main Menu. If you run your game now you should notice click sounds playing in the Main Menu and the Pause Menu when you click on a button, but not in any other Menus just yet.

For the SelectPlayerMenu.cs we want to do almost the same thing. Add

				using System.Collections.Generic;

to the top of the file, the variables

				public AudioClip clickSound;
				public float clickVolume = 1.0f;

				private AudioElement audioElement;

to the top of the class, and

				if(clickVolume < 0.0f) clickVolume = 0.0f;
				if(clickVolume > 1.0f) clickVolume = 1.0f;
				List< AudioClip > sounds = new List< AudioClip >();
				List< float > volumes = new List< float >();
				sounds.Add(clickSound);
				volumes.Add (clickVolume);
				audioElement = new AudioElement(sounds, volumes, "SelectPlayerMenu", null);

to the end of Start(). The difference now is in determining when to play the click sound. Add the new method

				private void PlayClick() {
					if(audioElement != null) audioElement.Play(clickSound);
				}

so that we can play the click sound whenever we want to. Then add the call

				PlayClick();

inside the following checks in OnGUI():

Finally we need to add the sound clip to the Select Player Menu in the same way we added it to the Main Menu.

Again we follow the same process for LoadMenu.cs. Add

				using System.Collections.Generic;

to the top of the file, the variables

				public AudioClip clickSound;
				public float clickVolume = 1.0f;

				private AudioElement audioElement;

to the top of the class, and

				if(clickVolume < 0.0f) clickVolume = 0.0f;
				if(clickVolume > 1.0f) clickVolume = 1.0f;
				List< AudioClip > sounds = new List< AudioClip >();
				List< float > volumes = new List< float >();
				sounds.Add(clickSound);
				volumes.Add (clickVolume);
				audioElement = new AudioElement(sounds, volumes, "LoadMenu", null);

to the end of Start(). Once again create the method

				private void PlayClick() {
					if(audioElement != null) audioElement.Play(clickSound);
				}

and add the call

				PlayClick();

inside the following checks in OnGUI():

Since the Load Menu can be accessed from both the Main Menu and the Pause Menu we need to add the sound clip to both of these places. For the Main Menu access we do this in the same way that we just added the sound for the Select Player Menu. Then for the Pause Menu access we do this in the same way that we added the sound clip for the Pause Menu itself (through the Player prefab).

Once more we follow the same process for SaveMenu.cs. Add

				using System.Collections.Generic;

to the top of the file, the variables

				public AudioClip clickSound;
				public float clickVolume = 1.0f;

				private AudioElement audioElement;

to the top of the class, and

				if(clickVolume < 0.0f) clickVolume = 0.0f;
				if(clickVolume > 1.0f) clickVolume = 1.0f;
				List< AudioClip > sounds = new List< AudioClip >();
				List< float > volumes = new List< float >();
				sounds.Add(clickSound);
				volumes.Add (clickVolume);
				audioElement = new AudioElement(sounds, volumes, "SaveMenu", null);

to the end of Start(). Once again create the method

				private void PlayClick() {
					if(audioElement != null) audioElement.Play(clickSound);
				}

and add the call

				PlayClick();

inside the following check in OnGUI():

and the following checks in DrawMenu():

The Save Menu can only be accessed through the Pause Menu, so we need to attach the sound clip to it in the same way that we added the sound clip for the Pause Menu itself (through the Player prefab).

Another place where we want to add our button click sound is in our Confirmation Dialog, which is brought up when we go to save over a game from the Save Menu. Add

				private AudioClip clickSound;
				private AudioElement audioElement;

to the top of ConfirmationDialog.cs and update StartConfirmation() to be the following.

				public void StartConfirmation(AudioClip clickSound, AudioElement audioElement) {
					this.clickSound = clickSound;
					this.audioElement = audioElement;
					confirming = true;
					clickYes = false;
					clickNo = false;
				}

Then add the method

				private void PlayClick() {
					if(audioElement != null) audioElement.Play(clickSound);
				}

and add the call

				PlayClick();

inside the following checks in Dialog():

Once these changes have been made you will need to update the call

				confirmDialog.StartConfirmation();

in StartSave() inside SaveMenu.cs to be

				confirmDialog.StartConfirmation(clickSound, audioElement);

so that the appropriate sound clip and AudioElement are sent to the confirmation dialog.

The final place that we want to add our button click sound to is the buttons in our HUD. Add the following variables

				public AudioClip clickSound;
				public float clickVolume = 1.0f;

				private AudioElement audioElement;

to the top of HUD.cs, then add

				List< AudioClip > sounds = new List< AudioClip >();
				List< float > volumes = new List< float >();
				sounds.Add(clickSound);
				volumes.Add (clickVolume);
				audioElement = new AudioElement(sounds, volumes, "HUD", null);

to the end of Start(). Create the method

				private void PlayClick() {
					if(audioElement != null) audioElement.Play(clickSound);
				}

and call it from the following places:

Finally, in order for the click sound to play we need to add it to our HUD. We will do this in the same way that we did for our Pause Menu and our Save Menu (through the Player prefab).

If you run your game now then you should notice the button click playing whenver you click a "button" - whether in one of your Menus or inside the HUD.

In Game Audio

Now that we have button clicks working we can turn our attention to sounds inside our game. These are sounds that we want to play when various things happen to our WorldObjects. Since many of these things will be common to all of our WorldObjects (e.g. selection) we will begin by adding some extra functionality to WorldObject.cs. First add the following variable to the top of the class.

				public AudioClip attackSound, selectSound, useWeaponSound;
				public float attackVolume = 1.0f, selectVolume = 1.0f, useWeaponVolume = 1.0f;

				protected AudioElement audioElement;

We want the ability for a child class to access the AudioElement, which is why it is protected rather than private. It is also possible that a child class will want to have other sounds (as we will see shortly), so we want an easy place for it to be able to initialise those. For this reason add the new method

				protected virtual void InitialiseAudio() {
					List< AudioClip > sounds = new List< AudioClip >();
					List< float > volumes = new List< float >();
					if(attackVolume < 0.0f) attackVolume = 0.0f;
					if(attackVolume > 1.0f) attackVolume = 1.0f;
					sounds.Add(attackSound);
					volumes.Add(attackVolume);
					if(selectVolume < 0.0f) selectVolume = 0.0f;
					if(selectVolume > 1.0f) selectVolume = 1.0f;
					sounds.Add(selectSound);
					volumes.Add(selectVolume);
					if(useWeaponVolume < 0.0f) useWeaponVolume = 0.0f;
					if(useWeaponVolume > 1.0f) useWeaponVolume = 1.0f;
					sounds.Add(useWeaponSound);
					volumes.Add(useWeaponVolume);
					audioElement = new AudioElement(sounds, volumes, objectName + ObjectId, this.transform);
				}

and call it using

				InitialiseAudio();

at the end of Start(). With that in place we want to call each of these sounds at the appropriate times. We can do this by adding

				if(audioElement != null) audioElement.Play(selectSound);

inside the check if(selected) in SetSelection(), then adding

				if(audioElement != null) audioElement.Play(attackSound);

to the top of BeginAttack(), and finally adding

				if(audioElement != null) audioElement.Play(useWeaponSound);

to the top of UseWeapon(). This logic means that if we have the sounds attached to a WorldObject it will play them at the correct times. We will leave testing this until we have modified some child classes, since they introduce some extra sounds to be attached. It makes more sense to add all of the sounds we need to each WorldObject once we are done, rather than in bits here and there as we go.

With our basic WorldObject sorted we will now update our Building to include sounds. The main sound we want to include is for when a new Unit is finished being constructed. Add the variables

				public AudioClip finishedJobSound;
				public float finishedJobVolume = 1.0f;

to the top of Building.cs and provide following definition for InitialiseAudio().

				protected override void InitialiseAudio () {
					base.InitialiseAudio ();
					if(finishedJobVolume < 0.0f) finishedJobVolume = 0.0f;
					if(finishedJobVolume > 1.0f) finishedJobVolume = 1.0f;
					List< AudioClip > sounds = new List< AudioClip >();
					List< float > volumes = new List< float >();
					sounds.Add(finishedJobSound);
					volumes.Add (finishedJobVolume);
					audioElement.Add(sounds, volumes);
				}

This adds the desired sound to the AudioElement that already exists for the Building. We can now get that sound to play by adding

				if(audioElement != null) audioElement.Play(finishedJobSound);

inside the check if(player) in ProcessBuildQueue(). A similar thing can then be done to Unit.cs by adding

				using System.Collections.Generic;

to the top of the file, adding the variables

				public AudioClip driveSound, moveSound;
				public float driveVolume = 0.5f, moveVolume = 1.0f;

to the top of the class and providing a definition for InitialiseAudio().

				protected override void InitialiseAudio () {
					base.InitialiseAudio ();
					List< AudioClip > sounds = new List< AudioClip >();
					List< float > volumes = new List< float >();
					if(driveVolume < 0.0f) driveVolume = 0.0f;
					if(driveVolume > 1.0f) driveVolume = 1.0f;
					volumes.Add(driveVolume);
					sounds.Add(driveSound);
					if(moveVolume < 0.0f) moveVolume = 0.0f;
					if(moveVolume > 1.0f) moveVolume = 1.0f; 
					sounds.Add(moveSound);
					volumes.Add(moveVolume);
					audioElement.Add(sounds, volumes);
				}

We can then call the sounds by adding

				if(audioElement != null) audioElement.Play (moveSound);

to the top of StartMove(Vector3 destination) (make sure to add it to the correct method, since there are two versions of this which take different parameters), adding

				if(audioElement != null) audioElement.Play(driveSound);

inside the check if(transform.rotation == targetRotation || transform.rotation == inverseTargetRotation) in TurnToTarget(), and adding

				if(audioElement != null) audioElement.Stop(driveSound);

to MaveMove() inside the check if(transform.position == destination) to make sure that the drive sound is stopped. Similarly, for Worker.cs add

				using System.Collections.Generic;

to the top of the file, add the variables

				public AudioClip finishedJobSound;
				public float finishedJobVolume = 1.0f;

to the top of the class and the definition of InitialiseAudio().

				protected override void InitialiseAudio () {
					base.InitialiseAudio ();
					if(finishedJobVolume < 0.0f) finishedJobVolume = 0.0f;
					if(finishedJobVolume > 1.0f) finishedJobVolume = 1.0f;
					List< AudioClip > sounds = new List< AudioClip >();
					List< float > volumes = new List< float >();
					sounds.Add(finishedJobSound);
					volumes.Add (finishedJobVolume);
					audioElement.Add(sounds, volumes);
				}

Then add the call

				if(audioElement != null) audioElement.Play(finishedJobSound);

to Update() inside the check if(!currentProject.UnderConstruction()). Finally, for Harvester.cs add

				using System.Collections.Generic;

to the top of the file, add the variables

				public AudioClip emptyHarvestSound, harvestSound, startHarvestSound;
				public float emptyHarvestVolume = 0.5f, harvestVolume = 0.5f, startHarvestVolume = 1.0f;

to the top of the class and provide the definition of InitialiseAudio().

				protected override void InitialiseAudio () {
					base.InitialiseAudio ();
					List< AudioClip > sounds = new List< AudioClip >();
					List< float > volumes = new List< float >();
					if(emptyHarvestVolume < 0.0f) emptyHarvestVolume = 0.0f;
					if(emptyHarvestVolume > 1.0f) emptyHarvestVolume = 1.0f;
					sounds.Add(emptyHarvestSound);
					volumes.Add(emptyHarvestVolume);
					if(harvestVolume < 0.0f) harvestVolume = 0.0f;
					if(harvestVolume > 1.0f) harvestVolume = 1.0f;
					sounds.Add(harvestSound);
					volumes.Add (harvestVolume);
					if(startHarvestVolume < 0.0f) startHarvestVolume = 0.0f;
					if(startHarvestVolume > 1.0f) startHarvestVolume = 1.0f;
					sounds.Add(startHarvestSound);
					volumes.Add(startHarvestVolume);
					audioElement.Add(sounds, volumes);
				}

To play these sounds add

				if(audioElement != null) audioElement.Play(startHarvestSound);

to the start of StartHarvest(), add

				if(audioElement != null) audioElement.Play(harvestSound);

to the start of Collect(), and add

				if(audioElement != null) audioElement.Play(emptyHarvestSound);

to the start of Deposit().

That wraps up all of the sounds that we want to use for the various types of WorldObject (for now, and feel free to add in whatever other sounds you like as well). Of course, before we can hear anything we need to add sounds to specific WorldObjects. I have used the following sounds for my project:

Once you have the sounds in Unity we need to attach them to the appropriate WorldObjects. To make sure the changes are applied everywhere we are going to add them to the prefab objects we have created. For each of the following prefabs assign the appropriate sound to the relevant field (the names should all make sense).

With all of the sounds attached you should now be able to run you game and hear things happening for each of the events we have defined. Now, I am not sure of performance issues once you get dozens of WorldObjects on screen all making noise. And I can see that things could get overwhelming if too many sounds are playing at once. But those are issues that can be sorted out if / when they arise. Until that point, have fun annoying your friends with a noisy game.

Another thing to point out is that it is very easy to customise the sounds that each WorldObject makes. Since we are adding sound clips to prefabs, you could easily make your Tank sound different to the Worker when it is driving (to simulate different types of engines). The code provides the way for the appropriate sounds to be played at the right time. It is the specific sound clips that you add to a prefab which define how things actually sound.

And that brings to a close for this time. All of the code for this part can be found on github under the commit for Part 20. Until next time, enjoy the ability to make lots of noise.

<
>