Table des matières
-
Lessons
By setting a State upon entering a Trigger, you allow the music to dynamically change based on where the player chooses to go. This approach works well with separated regions where none of them cover the same area, but one problem arises when they intersect. The image below shows you a conceptual drawing of the Village and Woodlands Triggers.
Here the Woodlands Trigger slightly intersects with the Village Trigger. When the player moves from the Village towards the Woodlands and enters the Woodlands Trigger, the State will be set to Woodlands. However, with the logic we implemented in the previous section, as the player ventures further into the Woodlands and leaves the Village behind, the OnTriggerExitState() function would set our global Music_Regions State to Nowhere. The player would then be in a situation where they would be listening to Ambient Music while standing in the Woodlands region.
One solution to this problem is to make a system that keeps track of each Trigger that the player enters by making a list of States to manage the priority of music themes collectively.
With such a system we will know exactly how many Triggers the player is inside because it will count up the list of States whenever the player enters a Trigger. Once the player exits a Trigger, the system will look to see if the player is inside another Trigger. If not, it will set the State back to Nowhere. In the following steps, you will use the SetMusicState script created in the previous section and extend it to account for intersecting Triggers. You will achieve this by creating a list of States which would be updated dynamically as the Player enters and exits our region Triggers.
-
In the Unity menu, go to Audiokinetic > Certification > 301 > Lesson 8 and select Creating a System for Intersecting State Areas.
For this exercise you will be working with the Village and Woodlands music regions. They will both need to be configured with the SetMusicState script, which you created in the previous exercise.
-
In the Hierarchy, select both the Village Music Trigger and Woodlands Music Trigger game objects.
You can hold down Shift, Ctrl (on Windows), or CMD (on Mac) to mark both game objects.
When selecting multiple game objects, the components you add in the Inspector will be added to both game objects.
-
In the Inspector, click Add Component, then search for the SetMusicState and select it.
Before you head into the script, let's assign the Wwise-Type States. Make sure you still have the two Music Triggers selected.
-
With both the Village and Woodlands Music Trigger game objects selected, open the On Trigger Exit Stateproperty, go to MusicStates > Music_Regions, and select Nowhere.
You also need to edit the Triggers individually because they should have different States.
-
In the Hierarchy, select the Village Music Trigger only.
-
In the Inspector, open the OnTriggerEnterState, then go to MusicStates > Music_Regions and select Village.
-
In the Hierarchy, select the Woodlands Music Trigger only.
-
In the Inspector, open the OnTriggerEnterState, go to MusicStates > Music_Regions and select Woodlands.
Let's now open the script and perform the necessary modifications.
-
In the Inspector, double-click the SetMusicState script.
Next, you will make the list to keep track of the State changes that the Player - Trigger interactions will cause during gameplay. To make a list you write the following.
List< >
Inside the angle brackets you declare what type of properties you will be storing in your list. Here you will the a Wwise class type, AK.Wwise.State, written just like you would when assigning a property.
-
Open the SetMusicState script and, topmost in the SetMusicState class, add a new line and type List<AK.Wwise.State>.
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SetMusicState : MonoBehaviour { List<AK.Wwise.State> public AK.Wwise.State OnTriggerEnterState; public AK.Wwise.State OnTriggerExitState; private void OnTriggerEnter(Collider other){ if(other.CompareTag("Player")){ OnTriggerEnterState.SetValue(); } } private void OnTriggerExit(Collider other){ if(other.CompareTag("Player")){ OnTriggerExitState.SetValue(); } }
You have now declared a list of Wwise-Type States, but you need to give it a name as well.
-
Continuing from List<AK.Wwise.State>, name it ListOfStates.
public class SetMusicState : MonoBehaviour { List<AK.Wwise.State> ListOfStates
The List has now been created, but in order to see it from another script and make sure we can add States to it, you will have to modify it slightly. First, add a public modifier to it, so it can be seen from outside this class.
-
Insert public in front of the list.
public class SetMusicState : MonoBehaviour { public List<AK.Wwise.State> ListOfStates
Next, just like you need something to write on to make a shopping list, you need to instantiate the list and then start adding things to it. To instantiate a list, you can use the 'new' modifier.
-
Continuing from the public List<AK.Wwise.State> ListOfStates, add = new List<AK.Wwise.State>();.
public class SetMusicState : MonoBehaviour { public List<AK.Wwise.State> ListOfStates = new List<AK.Wwise.State>();
In the previous exercise you created two functions: OnTriggerEnter() and OnTriggerExit(). Although the same script is used on both the Village and the Woodlands Triggers, the functions were given the private scope, and so each of these Triggers will call their own instances of the OnTriggerEnter() and OnTriggerExit() functions. This will allow each Music Trigger with the SetMusicState script to add to the forthcoming State list independently. However, as the script is instantiated for each game object, how do you make sure that all scripts refer to the same list? The answer is the 'static' modifier. By making a list static, only one can exist at all times. So, when referring to this list, all scripts will be looking at the same list.
-
Insert static after the public modifier.
public class SetMusicState : MonoBehaviour { public static List<AK.Wwise.State> ListOfStates = new List<AK.Wwise.State>();
Next, you can modify the OnTriggerEnter() and OnTriggerExit() functions to allow adding and removing States from this list. Before proceeding, let's return to the Trigger illustration shown previously. Say the player starts in the Village and moves towards the Woodlands. As the player enters the Woodlands Trigger, the Woodlands State will be inserted on the top of our list.
If the player chooses to continue into the Woodlands, the Village State would simply be removed from the list and the topmost State (Woodlands) would remain being set. However, should the Player choose to return to the Village, by exiting the Woodlands before having exited the Village Trigger, the Woodlands State should instead be removed and the new topmost State (Village) would be set.
To create such a system you can use the Insert() function on the list to insert a State into a certain placement in a list. Within the parentheses of the Insert function, you give it the position it should be inserted at, followed by the State property. As you will only be inserting States in the top of the list, you can simply declare 0 as the index (position) in the list (as all lists start at 0, then 1, 2, 3, and so on).
-
In the OnTriggerEnter() function, replace OnTriggerEnterState.SetValue(); with ListOfStates.Insert(0, OnTriggerEnterState);.
public static List<AK.Wwise.State> ListOfStates = new List<AK.Wwise.State>(); public AK.Wwise.State OnTriggerEnterState; public AK.Wwise.State OnTriggerExitState; private void OnTriggerEnter(Collider other){ if(other.CompareTag("Player")){ ListOfStates.Insert(0, OnTriggerEnterState); } }
The Insert() function will never overwrite the targeted position, but instead shift the remaining elements down in the list.
To then access the first element in a list you use square brackets [ ] encapsulating the position of the element you want to access. As the top element in the list will be the most recent region entered, you can simply set the value at position 0. So, to get the topmost State you start by declaring the name of the list, then an opening square bracket, then the number 0, and finally a closing square bracket. Once you have the State, you can call the SetValue() function described in Lesson 5.
-
Add a new line after ListOfStates.Insert(0, OnTriggerEnterState); and type ListOfStates[0].SetValue();.
public static List<AK.Wwise.State> ListOfStates = new List<AK.Wwise.State>(); public AK.Wwise.State OnTriggerEnterState; public AK.Wwise.State OnTriggerExitState; private void OnTriggerEnter(Collider other){ if(other.CompareTag("Player")){ ListOfStates.Insert(0, OnTriggerEnterState); ListOfStates[0].SetValue(); } }
You are now adding to the list and setting the value, but not removing from the list when exiting the Trigger. When the player exits the Village Trigger, for instance, the Village Trigger's State should be removed from the list and the topmost State in the list should be set.
To remove an element from a list you can use the list's Remove() function, and it will find and remove the State you give it no matter in which position it is.
-
In the OnTriggerExit() function, replace OnTriggerExitState.SetValue(); with ListOfStates.Remove(OnTriggerEnterState); and save the script.
private void OnTriggerExit(Collider other){ if(other.CompareTag("Player")){ ListOfStates.Remove(OnTriggerEnterState); } }
Let's test the implementation by simply playing the game.
-
In Unity, click Play and run into the Village Trigger, then onto the bridge without leaving the Village Trigger, and then back into the Village.
Notice that when exiting the intersection of the Woodlands and Village Trigger, the music did not switch back to the Village theme. This is because the State is not set again, whenever an element is being removed. As such, when one element in the list has been removed, the new top State should be set. You may now stop the game.
-
Add a new line after ListOfStates.Remove(OnTriggerEnterState); and type ListOfStates[0].SetValue();.
private void OnTriggerExit(Collider other){ if(other.CompareTag("Player")){ ListOfStates.Remove(OnTriggerEnterState); ListOfStates[0].SetValue(); } }
You're almost done! One problem remains. When you are not inside any Music State Trigger, the Nowhere State should be set. With your current implementation, the list will be empty and you will get a Null Reference Exception. To solve this, you should add an If Statement that evaluates whether the list has any elements and, if it does not, sets the Nowhere State. To check whether there are any elements in a list you can use the .count property which contains the number of elements in the list; if the number is greater than 0, we know the list isn't empty.
-
Add a new line after ListOfStates.Remove(OnTriggerEnterState); and type if(ListOfStates.Count > 0){.
if(other.CompareTag("Player")){ ListOfStates.Remove(OnTriggerEnterState); if(ListOfStates.Count > 0){ ListOfStates[0].SetValue(); }
Notice that on this line, you are only writing an opening curly brace ' {', not a closing one ' }'. Because you need the 'ListOfStates[0].SetValue();' to only run when the condition is true, you should write the '}' after the 'ListOfStates[0].SetValue();', thereby defining the regions of the code you need to run if the If Statement is true.
-
Make a new line after ListOfStates[0].SetValue(); and write a closing curly brace ' }'.
if(other.CompareTag("Player")){ ListOfStates.Remove(OnTriggerEnterState); if(ListOfStates.Count > 0){ ListOfStates[0].SetValue(); } }
You are now only setting the value from the list if it is not empty. But what about the Nowhere State used for all areas without a Music Trigger? If the list count is 0, the Nowhere State should be set. To evaluate whether the list was empty, let's use an if statement along with and else{} condition.
By the use of an Else Statement, you can direct any call with a false condition to the enclosed code within the Else Statement's curly braces.
-
After the If Statement closing curly brace line, add else{.
if(other.CompareTag("Player")){ ListOfStates.Remove(OnTriggerEnterState); if(ListOfStates.Count > 0){ ListOfStates[0].SetValue(); }else{ }
If your code editor auto-completes the Else Statement with a closing curly brace, you can simply ignore the next step.
-
Press Enter two times, and type a closing curly brace '}'.
if(ListOfStates.Count > 0){ ListOfStates[0].SetValue(); } else{ }
You will now set the State to Nowhere whenever the else code section is being called. This will ensure that when the player is not inside any particular Music Region Trigger, the global State will be set to Nowhere and the music will play the Ambient theme.
-
Inside the Else Statement's curly braces, write OnTriggerExitState.SetValue();.
public class SetMusicState : MonoBehaviour { public static List<AK.Wwise.State> ListOfStates = new List<AK.Wwise.State>(); public AK.Wwise.State OnTriggerEnterState; public AK.Wwise.State OnTriggerExitState; private void OnTriggerEnter(Collider other){ if(other.CompareTag("Player")){ ListOfStates.Insert(0, OnTriggerEnterState); ListOfStates[0].SetValue(); } } private void OnTriggerExit(Collider other){ if(other.CompareTag("Player")){ ListOfStates.Remove(OnTriggerEnterState); if(ListOfStates.Count > 0){ ListOfStates[0].SetValue(); }else{ OnTriggerExitState.SetValue(); } } }
Let's try it out!
-
In Unity, click Play and run into the Village Trigger, onto the bridge without leaving the Village Trigger, and then back into the Village.
The Village theme will initially play as you enter the Village; once you enter the Trigger intersection on the bridge, the theme transitions to the Woodlands; but, once you head back into the Village, the Village theme will again be played.
-
Continue into the Woodlands Trigger until you exit the Village Trigger.
As you venture into the Woodlands and leave the Village behind you, the Village theme will transition into the Woodlands theme and keep playing until you enter the Village Trigger again. With the conditions we have added to our script, only when the player is outside any music Trigger will the Nowhere State be set. You may stop the game.
This is all you need in order to create a system that accounts for multiple State Triggers. If this certification is your first time to code anything, you should really be proud of your achievement! While you might not consider yourself as a programmer, you now know how to use many of the fundamental techniques that many programmers are using every day. By using the list functions wisely (Remove or Insert), you can control a collection of almost any type of property or variable. Programmers also use many of these techniques when developing outside of audio integration. So, with just a bit of knowledge about techniques like If Statements and Lists, you have learned how to program in general.