In this week’s class, we discussed strategies for audio, and covered the Singleton pattern – a very powerful tool (but also a potentially tricky/dangerous one) for organizing our game and exposing our “manager” scripts to the objects in our scene. The videos this week will cover updates to our Astral Attackers game to build a Sound Manager, create the Game Manager which will handle our game state and flow, and finally we will make some visual improvements to our environment and to our enemies to spice things up.
Part 1: Audio Strategy
When we are making our game, there are a number of decisions that will center around how we want to treat the sounds in our game. When should they be playing in our game? What should be emitting the sound? Do I need to have control over the sound, such as starting and stopping playback, or adjusting the speed or pitch? Are my audio clips short or long? How often can I expect to hear each sound? These questions will inform our decisions as to which objects should emit what sounds, and how much control we may need to exert over them through code.
For most of my games, my sounds tend to fall into one of four categories:
- Omnipresent Sounds – these are sounds that will generally play throughout an experience, such as background music, or ambient noise. The sound is continuous, long, and probably loops. It tends to remain at a steady volume, regardless of position. For these types, I prefer to use an empty object that contains only an AudioSource component dedicated to playing the one sound, with the loop property selected. These may be set to play on awake or respond to a Play( ) command, depending on the start/stop conditions.
- Object Sounds – these sounds have an obvious point of origin. We perceive them as coming from some object in our scene – perhaps a character, perhaps an inanimate object. These may be lines of dialog, footsteps, music coming from a radio, a cat meowing, or a twinkling sound to indicate a point of interest. We expect these sounds to come from “somewhere” as opposed to “everywhere” so often we will want them to have a spatial quality or directionality. These are the most likely to be controlled by some behavior or react to some condition. In this case, I expect these to be generated from an AudioSource component that is attached to a GameObject or the child of an object in the scene, and managed some script, likely on the object itself. These sources will often have the 3D sound at least partially implemented, and will likely rely on the Play( ) command.
- Prefab Sounds – I see these as “object sounds on autopilot”. There is no logic expected to control the sound, they simply play when instantiated, and may continue for the duration of the instance object. These are best used for environmental sound effects that are a little longer or loop (such as a motor running, or a crackling fire), or a larger sound effect (such as a door opening, or longer explosion). Generally this involves a prefab object with an AudioSource with a preset AudioClip that will Play On Awake and may or may not Loop. This can be 2D or 3D sound depending on if your noise needs a point of origin. These will play through until the object is destroyed, or the clip is finished (if not looping).
- One-Shot Sounds – these are useful for quick sounds, especially repeating sounds, or sound that may overlap. PlayOneShot( ) is a play-and-forget function… once it begins you cannot stop it until it has played through. Also, this does not loop. It must be applied to an audio source, so it will respect the settings of that objects component, such as the spatial quality (and thus position of the object). This type is best for quick sound effects, especially those that may overlap from the same source. Footsteps, gunshots, UI noises, beeps, scoring noises, quick audio effects that don’t need to be controlled or shut off.
To start, we will create two simple “prefab sounds” – sound effects that will play for the Player Bullet and the Enemy Bomb prefabs. In this case the implementation is simple. I open my prefab to edit it, and attach the sound to the object either by dragging the clip onto the game object itself, or by adding an AudioSource component and setting the AudioClip to the appropriate source. I chose a short “pew” noise for my bullet, and a longer descending whistle noise for my enemy bomb. Both components were set to Play On Awake, so the audio plays automatically as soon as the element appears, and stops the moment the element is destroyed. (This is particularly helpful for the Bomb, which may collide with something before the clip has finished playing, hence the reason we use this implementation rather than a “one-shot” style sound.
Next, I’m going to add some ambient space noises, by creating an empty game object that will server as our emitter. I will add an AudioSource component and set my Audio Clip to be the appropriate sound, and make sure to select “Loop” so that it keeps playing.
These sources will now play when they are part of the scene, either by being placed there in the editor, or instantiated by code. Next we will look at defining a script to handle more of our sound generation.
Part 2: Sound Manager (Singleton)
Back when we discussed our “train station” metaphor, I mentioned a special method for assigning and calling a single instance of an object, known as the “Singleton” pattern. This is a way of declaring a class so that there is only ever one single instance of it. In other situations, the Singleton instantiates itself. Our code, however, is living inside the confines of our game engine, so we will still need to attach it to an object that exists in our scene, rather than relying on self-instantiation.
Think of the Singleton as being similar to the Presidency. At any given time, there is only one President. A new President may come along and replace the old, and when they do the former ceases to be the President. The responsibilities of the office only ever point to one person at a time… the current President.
So the Singleton pattern is a programming method by which we can define a particular instance of a class within the class declaration itself, so that we never have to worry about “finding” or establishing a relationship or link… we simply call the only instance by calling a property in the class that holds the instance itself.
Sounds confusing, and it kind of is, but just roll with it and you’ll see how this works.
First, we need to create something to generate the sounds. I first create an empty game object by going to Create > Create Empty, and naming it “SoundManager”. I then add a script to this, which I also name “SoundManager”. (These don’t have to share the same name, this is just a personal preference as I only expect to have one of these). I also add an AudioSource component to the SoundManager object… this is what will emit our PlayOneShot( ) sounds.
In our Sound Manager script, I create a variable “audio” to hold the reference to the AudioSource component, and I assign it in the Start( ) command with GetComponent<AudioSource>( ).
Now we need to make our SoundManager into a singleton. This way we can adjust sounds or modify them or turn them off as needed. If we instantiate our sounds as children of the Sound Manager, we only need to look internally to make adjustments, rather than performing costly “find” processes.
The first step to declare a singleton is to define a public static version of the class within itself, like so:
public class SoundManager: MonoBehaviour { public static SoundManager S; ...
Weird, right? We just set up a SoundManager type variable inside of our SoundManager type script?
Weirder still, watch what we do next:
private void Awake() { S = this; // Singleton Definition }
As our object wakes up, it declares itself to be this value “S”.
What is “public static” anyways? The “public” designation is pretty easy – it is a value that can be accessed from outside of the class. But the “static” designation means that all instances of the class share the same value for that variable. So in theory, you can create as many SoundManagers as you want, but if you access the “S” variable, you will always get a reference to the same object, the last one that woke up and set itself to “this”.
So why is this useful? Because now we no longer have to find our SoundManager. We can simply get directly to the active instance of our SoundManagerscript by writing SoundManager.S.{{whatever public variable or method}}
As a demonstration of this, we create a public function named MakeEnemySound( ) which will play my enemy advancement sound. The declaration is simple:
public void MakeEnemySound() { audio.PlayOneShot(enemySoundClip); }
Now we can add a command in our MotherShip.cs script that calls this particular function to generate the sound by writing:
SoundManager.S.MakeEnemySound();
It all simply works.
NOTE – Often times, the variable name “S” is used as shorthand for Singleton, but you can choose whatever name you want. You could call your SoundManager singleton “Steve”, so our script made calls to SoundManager.Steve.StopAllSounds( ) instead. “S” is just a commonly used shorthand because Singleton starts with S.
Next we want to make the explosion sound for our Enemy objects. These could be one shot sounds, however they do last for a little bit, and I want to make sure I cut off all sounds when our player object is destroyed. Because of this, I am going to make these sounds prefab objects and I will instantiate them into the scene, so that I can destroy them if I need to cut them off early.
public void MakeEnemyExplosionSound() { GameObject littleBoom = Instantiate(enemyExplosionPrefab, transform); Destroy(littleBoom, 5.0f); }
Here, we generate an instance of the explosion prefab. We also set a self-destruct timer of 5 seconds to clean up the scene. (We could make this one second, but this better illustrates the point.) We also use the version of Instantiate( ) that takes a Game Object and Transform argument. By passing the current object’s transform, we make the Sound Manager the parent object. This will come in handy later when we want to stop all sounds.
We also setup a PlayOneShot( ) function for the Player explosion, and connect that through the collision script on our player object.
Next, we set up a function to stop all of the sounds, named StopAllSounds( ). We set up a public AudioSource named “ambientSound” and associate our ambient sound object from the last part, and then tell it to stop playing when this function is called. We call this function in the moment of our Player explosion, so the explosion sound plays and stop everything else.
public void StopAllSounds() { // stop the ambient noise ambientSound.Stop(); // stop all child sound foreach(Transform child in this.transform) { Destroy(child.gameObject); } }
The next section removes all child objects from the Sound Manager. This way, the enemy explosion noises that we generated are immediate wiped out. No object, no AudioSource. No AudioSource, no sound. To find these, we use a “foreach” command to find every Transform component that is contained in this object’s transform component, that is, all of the children. We destroy each one.
Finally, we can make our enemy march sound progress through a sequence of sounds by creating an array of audio clips. We declare this as:
public AudioClip[] enemyAdvanceSounds;
We set an integer variable to hold our position in the array, and increment it each time we make a noise, so that each subsequent call will advance to the next sound. If we have reached a position that is outside of the range of our array, we reset the index to 0 and start the count again.
public void MakeEnemySound() { // test the index if (enemySoundIndex >= enemyAdvanceSounds.Length) { enemySoundIndex = 0; } // make a sound audio.PlayOneShot(enemyAdvanceSounds[enemySoundIndex], 0.3f); enemySoundIndex++; }
Part 3: The Game Manager (also a Singleton)
During class, we mapped out our game flow and looked at how our various object interactions would move us through our state machine, and then used another singleton, the GameManger, to help coordinate this journey.
As discussed our game flow looks like this:

- We start in an inert state that invites us to “Press S to Start”. This is the Menu state and eventually we will replace it with a separate scene with way better UI.
- Pressing S takes us to a new game, where the number of lives are set, and the objects for the first round are prepared. Once ready, we will go into the GetReady state and hold there momentarily.
- Upon completion of the GetReady state, we move into Playing,
- During Playing, the player will have control of the ship and be able to shoot, and the enemies will begin their attack. There are three possible ways to exit this state.
- The first way occurs in case of a win condition – the player destroys all of the enemy ships – and are taken to the GameOver state with a win message.
- The other two methods are losing conditions – the player object is destroyed by an enemy, or the enemy ship reaches the ground. In this case, the player loses a life, and the round is reset after an oops state, or the game is over if the player has run out of lives.
The first step to build out this structure is for us to create our GameManager object (again created from an empty gameobject), and create the GameManager.cs script for it.
We declare our variable to hold the Singleton…
public static GameManager S; // set up the singleton
… and we assign it. This time, we add in some extra protection to make sure that a version of this singleton does not already exist. This can happen when we move between scenes, if we were to preserve our GameManager object, we might enter a scene that already has a GameManager inside of it. We will test for the instance and destroy if we find one already in place.
private void Awake() { // Singleton Definition if (GameManager.S) { // game manager exists, destroy this object Destroy(this.gameObject); } else { S = this; } }
Next we define a number of enumerated values for our various gamestates:
public enum GameState { menu, getReady, playing, oops, gameOver};
To implement Step 1, we set up a GameState variable in our script and test against it at appropriate times. For instance, pressing “S” would mean one thing during our Menu stage (it should be interpreted as the “start” command) but might mean something different during gameplay, if we are using our Input.GetAxis(“Vertical”), which relies on pressing WASD keys. We can do the same thing for the GameOver state with the R key:
void Update() { if (gameState == GameState.playing) { } else if (gameState == GameState.menu) { if(Input.GetKeyDown(KeyCode.S)) { // start the game StartANewGame(); } } else if (gameState == GameState.gameOver) { if (Input.GetKeyDown(KeyCode.R)) { StartANewGame(); } } }
We can begin to set up the stages. We created a StartNewGame() function that resets the lives left (and later will also reset the score, which is part of this week’s assignment)
We also create ResetRound( ) and StartRound( ) functions. ResetRound( ) is Step 2, and will place the block of enemies in the starting position. We make our Mothership object a prefab, and remove it from the scene, but add the prefab to a GameObject variable in this script. ResetRound instantiates the prefab and stores it in another GameObject variable that is our “currentMothership”. Before we instantiate, we test to see if one already exists. If this returns true, then we probably have arrived here after a player lost a round. We want to reset the enemy objects, so we destroy whatever already exists.
private void ResetRound() { // reset the enemy if (currentMotherShip) { Destroy(currentMotherShip); } currentMotherShip = Instantiate(motherShipPrefab); // put us in the get ready state gameState = GameState.getReady; // go to start round (for now, for assignment coroutine get ready state) StartCoroutine(GetReadyState()); }
Next we transition to the GetReady state, and kicking off a coroutine that will let us pause for 3 seconds, then trigger StartRound( ), which completes Step 3.
StartRound( ) moves us to the Playing state, and we remain here until it is time to end a round. For our class, we implemented one of the three conditions that will get us out – specifically the case where an enemy bomb hits the player. We created a PlayerDestroyed( ) function and then call it from a OnCollisionEnter that we add to the Player script.
private void OnCollisionEnter(Collision collision) { if (collision.transform.tag == "EnemyBomb") { // we should explode GameManager.S.PlayerDestroyed(); } }
So the enemy bomb hits the player, and player calls out to the GameManager and tells it to run the public method PlayerDestroyed( ).
public void PlayerDestroyed () { // remove a life livesLeft--; // test for lives left Debug.Log("Lives Left = " + livesLeft); if (livesLeft > 0) { StartCoroutine(OopsState()); } else { gameState = GameState.gameOver; } }
PlayerDestroyed( ) removes a life from our livesLeft integer. If there are still lives remaining, we move to the Oops state via a coroutine that operates similar to our GetReady state. Otherwise we move to the GameOver state.
NOTE – In this version, I only destroyed the player. For the homework, you will need to make sure to delete the player object and respawn it. This behavior will be similar to the Mothership object.
Our game is moving through the stages relatively well, but some things are still not behaving quite the way we would prefer. It’s time to dig into some states to add some detail.
The first thing that I don’t like is that the player can always move and shoot, so we add a gamestate check in the Player’s update function. By wrapping the contents with the following “if” statement, we ensure that motion only occurs during the playing state.
if (GameManager.S.gameState == GameState.playing) { ... }
Again, note that there is no need to associate the game manager with any object in our Player script. No reference to be made. GameManager.S is available to be called globally.
Next, I don’t like that our enemy object starts moving and shooting during the get ready state. I also don’t like that it continues to move after my player has been destroyed. I want to prevent the possibility of multiple state requests coming in while we are in our Oops state, so I am going to create new scripts in the Enemy object that will be responsible for starting and stopping the coroutines.
Since I am instantiating the Mothership in GameManager, I have access to the object that is the instance in our scene. I add public functions to StartTheAttack( ) and StopTheAttack( ) in the MotherShip.cs script, and then call those from GameManager using:
currentMotherShip.GetComponent<MotherShip>().StopTheAttack();
In our StopTheAttack( ) function, we call the StopAllCoroutines( ) function. This will cease any coroutines currently running from the component.
public void StopTheAttack() { StopAllCoroutines(); }
Finally, we add some messaging so that we understand our current state.
Part 4: Improving the Layout
This still looks a lot like a Unity demo, and less like a real game. In this section I made some simple cosmetic changes to the game to create an alien landscape. Through the addition of some primitive shapes (a ground plane and some “mountains”), the addition of some color (via some new materials), and the removal of the skybox (by setting the camera background to solid color), we have added a lot of personality to our game.

Part 5: Improving the Aliens
Our previous alien attackers were rather primitive – Unity primitives, to be exact. (Thank you, I’ll be here all week!) Now that we have a fancy setting, our enemies should be a little more… dramatic. For this section, we are going to look to the source material and draw some inspiration from the classic pixel-y goodness of the original, and build our enemies out of cubes.
Why cubes? First off, they are super easy to create and work with, and require surprisingly little overhead in terms of rendering and physics calculation. Second, I want to show you how you can create a game using just the tools provided – no need for special 3D modeling/animation software. Third, we are going to use Physics on those blocks to create a satisfying explosion effect on the alien ship that will add some fun to our game.
First, we create a single cube, using GameObject > 3D Object > Cube or Create… > 3D Object > Cube. I’m going to want my bottom center cube to represent the “position” of the object so I will set its position to the origin (0, 0, 0). If yours is not at that location, you can select the settings dropdown from the Transform component and select Reset Transform to do so.
Next we make copies of the cube and offset them by 1 unit increments so that you have a row of 5 cubes with their edges touching. Now copy those rows and move them up by 1 unit increments, until you have a 5×5 cube grid.

Then, we create an empty game object (GameObject > Create Empty or Create… > Create Empty) also located at (0, 0, 0) and make all of the cubes children of this empty object. Notice that when you add the cubes as children, they now indent under the parent object in the Hierarchy, and the parent object now has a small button that lets you roll-up the list.

I expect to create a number of aliens using this same block grid, and so I will create a prefab object from it that I can use as a template for future iterations. I name the parent object “Base Grid” and drag it into my Asset window. Now that I have saved a copy, I want to unpack this prefab and convert everything back into a scene object so that I can make local edits. I do this by selecting Prefab > Unpack Completely. Once I have edited this to make an alien shape, I will save this as a different prefab. (This is my path to create these objects, but this is by no means the only path. Do what works for you!)
Once I have unpacked the object, I edit it by removing some of the blocks. You can delete these, or simply turn the cube objects off using the check box next to the GameObject name in the Inspector. This way, the block is preserved, in case you want to go back later and edit quickly. Remove some blocks and make an alien shape like so:

Time to give the parent object a name. I called mine EnemyA_Frame1, since I will have multiple versions of this, each one representing a frame of animation.
Now I turn off EnemyA_Frame1, and place a new instance of the BoxGrid template in the scene at the same position. I unpack, edit it to look as though the legs have moved, and name the parent object EnemyA_Frame2.

Now I am going to create a duplicate of EnemyA_Frame1, but I will call this one EnemyA_FrameExplode. This object will be out “stunt double”, swapping places with the other frames when it is time for the ship to blow up. To get that great explosive force, add a Rigidbody component to each of the cubes in EnemyA_FrameExplode. (You can simply select all of the cubes at once and go to Add Component).
It is also worth noting that in my objects for frames 1 & 2, I have turned their colliders off. This is because I want to have to worry about managing collisions on the individual blocks – I want these to simply be a part of a cohesive whole.
I’m going to make each one of our alien parent objects a prefab by dragging it down into the Assets folder. Next, I am going to create a new empty game object and parent it to these prefabs, so that it serves as a container to the individual frames. Name this object EnemyTypeA1 and make this object into a prefab as well. I add my Enemy class to this, as well as a Box Collider component which I will resize to fit around my enemy object. (Don’t forget to change your layer designation to “Enemy”!)

Now, let’s add a little bit of scripting to bring our little attacker to life. We want to open our Enemy script and add variables to hold the three child objects of this particular enemy.
// enemy voxel frame prefabs public GameObject modelOne; public GameObject modelTwo; public GameObject modelExplode;
We define three objects (which we must populate with the Inspector), and then on Awake( ) we set their conditions so that only the modelOne model is active. I use Awake so that this is set instantly, just in case something else is going to access our models when the Start( ) event runs.
private void Awake() { modelOne.SetActive(true); modelTwo.SetActive(false); modelExplode.SetActive(false); }
When Start( ) does arrive, we use that to launch a Coroutine that I have created called NextStage( ) that will call my SwapFrames( ) command once per second. It accomplishes this by nesting the call and the 1.0 second yield inside of a “while” loop that will run continuously for the duration of the object because the condition is set to “true”.
private void Start() { StartCoroutine(NextStage()); }
public IEnumerator NextStage() { while (true) { SwapFrames(); yield return new WaitForSeconds(1.0f); } }
The SwapFrames( ) command simply set the active state of both frames to the opposite value.
public void SwapFrames() { modelOne.SetActive(!modelOne.activeSelf); modelTwo.SetActive(!modelTwo.activeSelf); }
NOTE
In previous versions of this assignment, I would tend to use the GameObject’s “active” property, getting and setting that directly. But over time, Unity started to become mad at this, because this particular interaction is being phased out. So instead the proper method is used, utilizing the GameObject.SetActive( ) command, and reading the value using the “activeSelf” property.
Once this is done, I am free to go back into my main scene and replace the MotherShip prefab enemies with our new advanced enemy prefabs.
Part 6: Bigger Explosions!
For this next part, we want to test out our explosion effect – where we swap out our model frames with our “stunt double” and then apply an explosive force to the individual boxes. We do this by creating an Explode( ) command in our AttackerScript, like so:
private void Explode() { modelOne.SetActive(false); modelTwo.SetActive(false); modelExplode.SetActive(true); Vector3 explosionPoint = modelExplode.transform.position + (Vector3.back * 2); Rigidbody[] cubes; // variable to hold the RBs // get every rb in this object cubes = modelExplode.GetComponentsInChildren<Rigidbody>(); // apply our explosive force foreach (Rigidbody rb in cubes) { rb.AddExplosionForce(explosionForce, explosionPoint, explosionRadius); } Destroy(modelExplode, 5.0f); }
Once we have turned off our models and turned on our “stunt double” object, our goal is to apply the Rigidbody.AddExplosionForce( ) to each cube in the object. This method takes three arguments – a force to exert, an origination point for the explosion, and a radius within which the explosion will have an effect. We create public float objects for “explosionForce” and “explosionRadius” in the class declaration, and then set those values.
In our explode command, you see that we declared a variable as “Rigidbody[ ] cubes”. These brackets mean that we expect to receive more than one result – an array of Rigidbody components. We then generate this list using the GetComponentsInChildren<>( ) method which returns the array of all child objects for “modelExplosion” that have a Rigidbody component in their backpack. Finally we use a “foreach” loop that lets us cycle through each result in our array. In each pass of the loop (that is, for each Rigidbody object in cubes[ ]) we set that object to the variable “rb”, and then apply our explosive force to that Rigidbody. By cycling through all of these we ensure that every object is affected by the force.

One thing that you will notice is that our other frames keep popping back into view. This is because we set our Coroutine to run continuously. We can try to prevent this by issuing a StopCoroutine( ) command, but there is a better solution – let the Enemy object be destroyed on impact, like it was.
Now, this creates a conundrum. If we destroy the object, the children will be destroyed as well, along with our “stunt double”. Everything will just blink out of existence. But what if our stunt double was not a child of the enemy object? To accomplish this, we are going to emancipate our stunt double from it’s parent object, making it an orphan (or as I like to call it, a “child of the world”). We do this by setting the parent of that object to “null”.
modelExplode.transform.parent = null;
Now our enemy object destroys itself, but not before throwing the stunt double out into the world and applying the explosion just before it disappears. Because our Explode( ) command runs prior to the command that destroys the Enemy object, our stunt double makes it out alive (but not for long).
Next, we are going to insert some special effects, using an explosion prefab built with Unity’s particle system. (More on these later…). You can download this prefab in the package posted on the course Box site. By adding this prefab as a child of our EnemyA_FrameExplode prefab, it will appear and play once when the stunt double wakes up. It make a fiery explosion with a bright orange light, sends out a “shockwave”, and shoots some smoking embers off into the distance.

Finally, we can edit our MotherShip script to queue the SwapFrames command on each enemy object, rather than each object running it’s own independent coroutine.
private void MoveLegs() { Enemy[] attackerObjects; attackerObjects = GetComponentsInChildren<Enemy>(); if (attackerObjects != null) { // send the flip command to each object foreach(Enemy attacker in attackerObjects) { attacker.SwapFrames(); } } }
While I could use the SendMessage( ) method to blast everyone with a command to swap frames, that is messy and imprecise. This time around I want to be surgical, and only send this function to the Enemy types. I use the “GetComponentsInChildren” to return an array of Enemy components. There is a chance that there will be no objects, in which case the foreach will not run correctly, so I run a “null” test on attackerObjects to make sure it exists before I test within it.