Tuesday, November 10, 2015

Unity At Home Project - Day 8 - Sound Effects


First, I have to start off with stupid things I do. This morning, I blew almost the entire morning trying to find a good plugin, piece of javascript, whatever, to allow me to imbed code snippets into blogger. Sadly, while my C# is strong, my javascript is weak, and though I found several alternatives, I was at every turn foiled in some way or another. Two hours later, I'm no better off than I was before, but out two hours of my time. I'm still fuming about it, so I just had to put a quick rant up. I'm sure I'll tackle the problem again, or maybe use a GitHub gist.

In the meantime, let's talk about this week's goal - getting some sound effects on our walking mechs. Again, my original goal was pretty simple - get an engine sound, and some walking legs sounds on the mech, and that's pretty much it.  Of course, as we'll discover, even the most simplest of goals can be fraught with peril!

Getting the Assets
So the first thing to do was to acquire some sound effects. Again, to the Asset Store, credit card in hand! There are a ton of sound effect libraries out there, and unfortunately sampling them is something of a pain. What I learned is that most sound effect assets put their effect out in a SoundCloud for you to sample, but they also put a music or ambient sound bed under the sound effect samples, to keep you from stealing them directly from the sound cloud. And while I appreciate the need, it also makes it darn hard to actually sample the sound effects.  I ended up going with Sci-Fi Sounds from Forge3D. Over 500 sound effects for $25.00 - there's bound to be something in there I can use, plus some GUI sounds and what not that will be useful later.
Importing Libraries ProTip - Never import an entire asset collection into your main project on initial download. Create a new sample project, and import them into that. Use that project to get familiar with the asset collection, and then move what you need, once you understand how it works, into your main project. I don't need 500 sounds floating around in my main project right now - I need two, to be precise.
Once I had the sounds though, it was time to start going through them one by one, to find something I liked. After an hour or so of rummaging through the sounds, I settled on a few that I thought would be good, and moved them into the Robot-Sandbox project.

The Mech Mixer
Getting Started
So going into this week, I new the basics of Unity Audio - at least "old school" Unity audio. I knew about listeners, and sources, and that sort of thing. But I also knew that as of Unity 5, they had added quite a bit of functionality through the use of mixers of some sort, and that seemed like something I'd want to take advantage of. So the first step again, was back to Unity tutorial videos, and the online manual. And it turns out, it was actually quite useful. I won't go through all of it, but I will provide a link at the bottom of the blog if you want to dig in yourself.

Coming out of the tutorials, I knew that I wanted at least two mixers for my project. The master mixer, which would use to blend SFX and Music, and a submixer, for my mech itself. And on the sub mixer, I would set up different groups for each of the engine and footsteps sounds.

I then added two audio sources directly to the mech controller prefab, and gave them audio clips. I then modified the MechController script to grab a handle to each of these components, so I could manipulate them.

The Engine Sound
There's a really cheap and easy trick you can do with vehicle engine sounds to produce an "engine-like" effect, and that is to continuously update the pitch of the sound with the overall speed of the vehicle. And this is exactly what I did. I put the code in the move function, which is called every update, right after I calculate ForwardSpeed from the user input controls. This was, as I discovered, actually a big mistake.

OnFootstep event added at two
locations in the animation timeline
The Footstep Sounds
For the footsteps, I knew I needed to add animation tags to the animation, that would fire callback code, which would play the animation. Adding animation tags, or events, to animations used to be hard to do in Unity, but since mecanim was introduced, it's pretty easy. Just go to the events drop down in the animation editor, scrub along the timeline to the desired location, and insert an event. Whatever you name the event will be the name of the function that is called on the object that the animation controller is associated with, which in our case is our MechController.

I was able to get all of this set up and working pretty quickly. The engine sounded nice. I ran into one problem with the foosteps sounds in that they were always playing, even when the mech was standing still. I realized that because the walk animation is blended with the idle and the run, at all times some point of the walk animation was playing, and that's enough to fire the events. It would be nice to have a way to say only fire the animation event if the weight of this animation is above a threshold, but I didn't find any such setting. So I modified the callback function to only play the footstep sound if the forward speed of the mech was above a certain threshold. The other thing that I noticed was that when the mech was only just barely moving the footstep clang had the same, really loud volume. So I attenuated the volume of the footstep sound by the forward speed of the mech.

Leg Sounds
I toyed with the idea of having a separate sound for the legs lifting. I like the idea of sort of a hydraulic servo noise coinciding with each leg lifting up, and then a loud clang as the footstep came down. Unfortunately, this turned out to be really problematic. Because the leg moving animation is blended, you never really know at what speed the leg will be going through it's cycle. In fact, you don't even know that it will complete the cycle, as it could reverse direction in midstep. This meant that the servo noise, once started, may or may not ever actually line up with a full leg raise and lower cycle. I played around with a variety of sounds and adjustments, but never could really get it to be what I wanted, so eventually abandoned separate leg lifting sounds, and just stayed with footsteps.

So I was playing the engine noise, playing the foosteps, and the sounds were getting fed into my mechmixer, so I could add effects or adjust the volumes independently if I wanted to, and that was in turn getting fed into the main mixer, which was in turn getting fed to the listener. We're all good right? Well, not so fast.

Multiplayer Problems
And now is the time when we learn why we've already hooked up multiplayer. Once I was satisfied with the sounds in single player, I connected a second mech to the play screen, expecting, for the most part, everything to work as expected. Instead, it pretty much blew up - soundwise that is. As soon as I connected the second mech, all I could hear was this super loud, constant noise I didn't recognize, playing on top of the sounds my own mech was making. Other than that, the other mech in the scene wasn't making any noise that I could tell - no engine noise, no footseps, nothing. What the hell was going on?

Well it turns out, a lot of things.  And it took me the better part of a Sunday morning and afternoon to sort these things out. The first problem, the super loud constant noise - turned out to be the engine of the other mech, playing at a constant pitch - no attenuation at all. This was because I was attenuating the pitch inside the move function, which as I mentioned earlier, is called every update by the user input controller - but only on my local player! So on client mechs, this function never got called. First thing to do was to move that pitch modification to the general purpose update. And, at the same time, make the ForwardAmount variable I was using a SyncVar, which is Unity's way of replicating variables across the network.

Well, when I did that, the other mech now was making no sound. Dammit, that should have fixed it! Some debugging logic later, I learned that the ForwardAmount variable was always zero on the client machine. More pondering and investigation, and I finally had a grip on what was happening. Setting the variable to [SyncVar], causes the server to replicate the value out to clients. But my variable was getting set by my local client. I needed to get the value from my local client up to the server someway, so that it could then get replicated to the other clients by the server. But how to do that?

The method unity gives you for getting information from a client to the server is to create a Command function. Command functions, when called, will be executed on the client and server. But Unity also warns you against making Command Functions every single update - and this was something I pretty much needed updating as often as it could. I was pretty stumped at this point - I didn't want to flood the server with CommandFunctions, but I needed some way to get a value replicated from the client, through the server, to other clients. And that was when, it hit me. The animator!

The animator has a NetworkAnimator component attached to it that Unity has thoughtfully provided that replicates animator properties across the network. And one of these properties was the forwardSpeed property I needed to query! So I could, thereoretically, on any client, query the property from the animator, and I would either get the value I personally set on my local client, or the replicated value on network clients - but the same code should work either way! So I tried this, and it worked, and that, in the end, was the key to solving the rest of my multiplayer sound effect problems.

I have no idea how actual efficient this is at this point. But using the animator as a general purpose means of replicating player-controlled-variables to networked clients seems like a really useful trick. We'll see if it bites us in the ass later on, but it's something I'm keeping up my sleeve for future use.
Make sure Spatial Blend is set to 1.0
Where's the 3D Sound?
So this wasn't a problem specifically with multiplayer, but was a problem I didn't discover until I added the client to the game. When I did finally get the sounds attenuating and playing correctly, they still weren't fading over distance. Why not? These were supposed to be 3D sounds, and I could see clearly the volume falloff curve set up on the audio component. Well there is a parameter on the audio component called Spatial Blend. Turns out this parameter is really important. It determines how much of the sound is made available for spatial modifications, including distance attenuation (and reverb zones, and other goodies). If your sounds aren't modifying their volume over distance, this should be the first thing to check on your audio source. Set it all the way to 1.0, which is full 3D.

The Lesson
This week turned out to be a lot harder than I thought it would be, which is often the case in game development. I ended up spending as much time if not more figuring out the multiplayer problems with my audio set up than I did in setting up the audio in the first place. I've said this before, but this really drove the point home - if you're going to multiplayer, do it early in the project. And then for each piece you think you have working, fire it up in multiplayer, and see if it's truly working. And then be prepared for a bit more coding.

Once I had everything working (finally) mostly to satisfaction, I put together a short video, showing off the audio. This time, I even added some commentary, which relates to the stuff I've talked about in this blog.  Enjoy the video, and let me know if you have any questions or thoughts!




Useful Links

Sci-Fi Sounds 
This is the audio asset pack I ended up using from the Asset Store

Unity Audio Tutorial(s)
If you're getting started with Unity Audio, this is the place to get started

Main Page for the At Home Project
If you'd like to see all of the At Home Project posts, I've provided an index here