Audio can make a huge impact on the immersion you feel with a game. In some cases the right music and sound fx can get you excited even when there is nothing visual or interactive to influence you in the same way. Since a goal of this project is to make it feel very complete, we will need to add this important feature.
Music Controller
Much like I had already configured prefabs for the screens, board, etc, I also have already configured a “Music” prefab in the project. This has a hierarchy of GameObjects, each with its own AudioSource component. Technically it isn’t necessary to do this, because you can have multiple AudioSource components on a single GameObject. As another alternative, you could simply use a single AudioSource component for your music player and link directly to the AudioClip resources instead. In my opinion, the setup I used is better for organization because I can give each object its own name which doesn’t need to match the filename of the clip itself. In addition it is more flexible in case I eventually want to add additional features such as cross-fading or unique modifications per source.
The MusicController script already has a variety of fields that either hold a specifically named AudioSource component, or a collection (List) of AudioSource components. This allows me to use different music tracks for different levels of game play. For example, in this game the more badges you earn the closer you are to winning, and so the more intense I want the music to be.
Feature-wise, I don’t need very much from this script. All I need is to be able to keep track of the currently playing music so I can stop it when I am ready to play something else. A more complex system may also handle cross fading (I have an example of this here), or allowing the volume or ability to play music at all be driven by user settings. Hopefully by now you know enough to add your own screen, tie it into the flow, and connect it with systems as necessary, but if not you can always post questions/comments here or in my forum.
Let’s go ahead and look at some new code:
void SetSource (AudioSource source, bool forceReset = false) { if (source == currentMusic) return; if (currentMusic != null) currentMusic.Pause(); currentMusic = source; if (forceReset) currentMusic.time = 0; currentMusic.Play(); }
This method handles the primary feature of our music controller. It isn’t marked as public because I am going to expose a few convenience methods that invoke it instead. It accepts as parameters an “AudioSource” that we want to start playing, and a “bool” that indicate whether or not we want to play from the beginning or from wherever it last left off. The second parameter is optional and if you don’t include it, it will default to false.
At the beginning of the method we first check to see whether or not the new target is the same as the current music. If so, then we “return” early – no action needs to be taken. You might encounter this when changing turns between players of the same badge level. Taking no action is better than needing to pause it just to play it again.
When we are assigning a new music track, our next check is to see whether or not there had already been any other music playing. If music had been playing we need to pause the other music.
Next we update our “currentMusic” reference to point to the source we passed along as a parameter in the method. If we also passed along a “true” value for “forceReset” then we will rewind the clip to the beginning by setting its “.time” field to “0”. Finally, we tell the new audio source to “Play”.
public void PlayIntro () { SetSource(introMusic); }
The PlayIntro method is our first conevenience method to wrap the SetSource method. Notice that it does not use the second parameter so it will not cause the music to always play from the beginning. I only had one clip to use as an intro, so I simply pass the source which held that clip as my parameter.
public void PlayJourney (int level) { level = Mathf.Clamp(level, 0, journeyMusic.Count - 1); SetSource(journeyMusic[level]); }
I had three different music tracks for a player’s journey music – this is the music that plays while you see a pawn moving from tile to tile along the game board. The “level” parameter that you pass is an index for which of the clips to play and it is determined by the number of badges the current player has earned. As a safeguard I clamped the value so it couldnt go out of bounds of the array, even though that wont happen in this project. Note that even though you can earn four badges, I didnt need a fourth journey music because the game will end at that point and I have a separate game over music clip.
public void PlayBattle (int level) { level = Mathf.Clamp(level, 0, battleMusic.Count - 1); SetSource(battleMusic[level], true); } public void PlayVictory (int level) { level = Mathf.Clamp (level, 0, victoryMusic.Count - 1); SetSource (victoryMusic [level], true); }
The battle and victory music also take a “level” parameter, but in this case it is not related to the number of badges a player has earned – it actually relates to the kind of battle such as a gym battle or random encounter battle. Note that I actually use the second parameter in the call to SetSource, because I do want the music to start from the beginning when it plays.
public void PlayGameOver () { SetSource(endMusic, true); }
Our final convenience method to play the single game over music clip from the beginning – nothing new here.
Flow Control
Now that we have a script which can handle playing music, we need to tie it in to the various states of our flow controller so that it plays the right music at the right time. Many of the states already include placeholder comments indicating where to add the code.
Intro Music
The intro music will play on the menus at the very beginning of our game. We will use the “OnEnter…” method of IntroState to trigger this:
musicController.PlayIntro ();
Journey Music
The journey music plays during a player’s turn, such as while managing the Pokemon team, rolling, and moving the pawn on the board. Each player can have a different number of badges, and the badges determine which journey music to play, so at the start of each turn we will need to make sure we play the correct music. The state which we will use for this is the GetReadyState, and like before we can add this to the “OnEnter…” method.
musicController.PlayJourney (game.CurrentPlayer.badges.Count);
During a journey, a player may encounter battles, and the battles will have their own music, so whenever a battle has ended, such as with the GymCompleteState and EncounterCompleteState states, then we will also want to make sure the proper journey music can continue. Use the same line of code as before in each of these state’s equivalent “OnEnter…” method.
Battle Music
We will play special music for each kind of battle. When a random encounter battle begins via the StartEncounterBattleState we can add the following code in the “OnEnter…” method:
musicController.PlayBattle(0);
When a gym battle begins via the AttackTeamState we can add the following code in the “OnEnter…” method (note that the code was already there and merely needs to be un-commented out):
musicController.PlayBattle(1);
Victory Music
I ended up not actually using the first music clip for a random encounter victory because of the way I changed it to the capture of a Pokemon, which has a chance to fail. So, we will only need to add this to the gym’s VictoryState – just uncomment the line:
musicController.PlayVictory (1);
Game Over Music
There is only one place to worry about for playing the end game music – the GameOverState. Like before you can add the call to the “OnEnter…” method like so:
musicController.PlayGameOver();
Sound FX
I went light on this project for SoundFX – normally you might include them all over, such as button taps, transitions, hits, etc. I only found SFX for the Pokemon battle cries. In the project I stored them under “Resources/Pokemon Cries/” with files ranging from “001” up to “151” – each one mapping to the Pokemon’s entity id.
Pokemon System
Since there are a lot of sound fx, it isn’t a good idea to load them all simultaneously. You wouldn’t want to have a direct reference to them in your scene or they will all load. Instead we will load them dynamically as needed. We can use the PokemonSystem for this since each resource is directly related to a Pokemon.
public static AudioClip GetBattleCry (this Pokemon pokemon) { return GetBattleCry(pokemon.Entity); } public static AudioClip GetBattleCry (Entity entity) { string fileName = string.Format("Pokemon Cries/{0}", entity.id.ToString("000")); AudioClip clip = Resources.Load<AudioClip>(fileName); return clip; }
Combat View Controller
The combat view controller is responsible for showing the battle including which Pokemon is attacking, so we will also use it to play the battle cry. There is already a placeholder comment inside of the “ShowAttackProcess” method which you should replace with the following:
battleCrySource.clip = battle.combatants[0].CurrentPokemon.GetBattleCry(); battleCrySource.Play();
Demo
Start a new game with a couple of players. Make sure to listen for the different music that will play such as on the menu screens, journey, battle, and game over, etc. Try having the different players earn gym badges at different rates so you can hear the journey music bounce back and forth in intensity depending on whose turn it is. While you battle listen for the battle cry sound fx – they should be different for each Pokemon.
Summary
To add music, we implemented a custom controller script and called its methods at various places within the flow controller states. To add sound fx of the Pokemon battle cries, we added an extra feature in the PokemonSystem and called it from the combat screen while an attack was animating.
We could have added audio much earlier in the development process. Sometimes this can be good because it helps your game feel more inspiring while working on it. On the other hand, if you work on it for too long you might start hating your music due to hearing it so frequently – it’s up to you to decide. Either way, its hard to deny that our project feels much more complete now.
Don’t forget that there is a repository for this project located here. Also, please remember that this repository is using placeholder (empty) assets so attempting to run the game from here is pretty pointless – you will need to follow along with all of the previous lessons first.
If you find value in my blog, you can support its continued development by becoming my patron. Visit my Patreon page here. Thanks!