In my last post I discussed the sound effects that have been added when the character is running, walking, wading, swimming, and treading. When animations are only a second or so in length, sounds can get very repetitive. Using a Unity Store product to manage sounds does save time. However, I couldn't figure out how to implement a certain feature I wanted. Since the sounds were spawned from a script attached to a Mechanim state, they only randomized when state changed. For example, when using WASD to maneuver the character in water, the sound randomization features of the product I purchased only performed the randomization when the state of the animation switched from say, swimming to idle or vice-versa. They would not change until the player state was different. I wrote the developer to ask if there was something to be done to fire the randomization event within the current state's animation. He politely responded that I would have to write my own behavior for that. So, that is exactly what I did.
State Machine Sound Controller
Digging around, I found I could attach a script to the state if it inherited from StateMachineBehaviour. This would enable me to attach my new script in place of the store asset's script which was controlling the sounds. There are three events that are necessary to override in order to manage the randomizing of sounds mid-state. Take a look at my new script.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | using UnityEngine;
public class STStateMachineSoundController : StateMachineBehaviour { public AudioClip[] AudioClipArray; private AudioSource _audioSource; private bool _isVariety; private int _loopCount; private void Awake() { //assign _audioSource _audioSource = STGameManager.Instance.PlayerObject.GetComponent<AudioSource>(); //determine if variety will be used _isVariety = AudioClipArray.Length > 1; //initialize loopCount _loopCount = 0; } public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { //This is a bit counter-intuitive to stop the sound OnStateEnter. //But this ensures the previous state's sound stops playing and doesn't overlap events. //This state's sound will get picked up on the first OnStateUpdate _loopCount = 0; _audioSource.Stop(); } public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { if (!_audioSource.isPlaying) { PlayClip(); } if (_isVariety) { //only change the current variety at the end of the current animation loop //get the integral portion of the normalizedTime int thisLoop = Mathf.FloorToInt(stateInfo.normalizedTime); float progress = stateInfo.normalizedTime; //get the decimal portion of normalizedTime; progress = Mathf.Repeat(progress, 1.0f); //at the end of the current state's loop //select a new random variety clip when the loop cycles if (_loopCount != thisLoop && progress >= .99f) { _loopCount++; PlayClip(); } } } public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { //stop audio in case next state does not use audio _audioSource.Stop(); } private void PlayClip() { if (_isVariety) { //randomly pick a new clip to play from the array int randomClip = Utils.MathHelpers.GetRandomInteger(0, AudioClipArray.Length - 1); _audioSource.clip = AudioClipArray[randomClip]; } else { _audioSource.clip = AudioClipArray[0]; } _audioSource.Play(); } } |
When dragging this script onto a Mechanim animation state, the AudioClipArray[] allows me to establish the number of sound variations desired for that particular state. If there is only to be a single sound, I set the number to 1. Regardless, all that is needed to make it work is to drag sound clips onto the AudioClipArray Elements in the inspector. That's it!
The code is heavily commented, but still pretty easy to follow. The _isVariety bool is used to randomly select from the array list if the list has more than one sound clip in it. When any particular state is entered, I found it best to stop the audio source from playing an existing sound. I introduced a loop counter to help ensure a complete cycle before changing to a different sound clip. The Mathf.Repeat() method is very nice for getting the decimal portion of the state's elapsed time. When the decimal portiono is >= .99f it means the animation loop is almost completed. I only wanted the sound to change at the end of the loop.
There are two additional things I might need to mention for any new developers. First, the _audioSource assignment in the Awake() simply identifies the game object in my scene that has the necessary AudioSource component attached. And second, the Utils.MathHelpers.GetRandomInteger method is part of my own Utils class that contains many helper methods I have written to use in my game. Simply replace that with something like the following to generate a random selection between 0 and the number of sounds in the AudioClipArray[].
1 2 | Random r = new Random(); int rInt = r.Next(0, AudioClipArray.Length - 1); |
I dragged this script onto all my movement Mechanim states, set the number of sound clips for each one, and dragged the appropriate clips onto each script's elements. This allowed me to remove the store product's script altogether. It's fast, easy to configure and use, and most importantly, I know what the code does.
Asset Store Considerations
When purchasing Asset Store packages for use in my game, I keep the following considerations in mind. First, using someone else's code means I am introducing unfamiliar code into my game. I don't have time to read every line of code in every Asset Store purchase I use in my game. I have to accept this as a risk. There could be bugs in that code. There could be added fluff (parts I will not be using) in the code that may bog down my game. There could also be future updates to that code which may break my code after it is updated. Of course, all developers must understand that every library we are "using" brings with it a certain level of trust we place in the developers who made them. But I think the most important thing to consider is being willing to step back and write something ourselves if the need arises. This is why learning C# is so important for Unity developers. Not everything can be purchased from the Unity Store. And while, yes, there are non-programming tools such as Bolt that can assist people in creating games, there is a whole lot of satisfaction in knowing you have the knowledge and skills to venture into the coding world yourself, if and when the need arises.
I will hold off on a video until I have a little more to show you. As you can see, I am in the very early stages of development of this game of mine. I appreciate you following along. I hope something from my experiences will help other developers get inspired. Until next time...