Tuesday, November 17, 2015

Unity3d At Home Project - Day 9 - Twistin' by the Pool

So this week is a story about getting sidetracked, about building things you didn't expect to build, and that sometimes it's exactly what you need to do, and sometimes it's exactly what you want to avoid, and of course there's no easy way to tell the difference.

Where are the Guns?
So it started off with me thinking about weapon systems. I've got a mech that's moving about pretty good, decent sound effects, decent control, and decent camera. It working in multiplayer, and the next logical step is to start thinking about making these things shoot. That is, after all, the point!

There is an entire blog to write (in fact, probably my next blog) about how the design space here really explodes. I won't go into a huge amount of explanation by what I mean about that here (after all, there's that blog I've yet to write about it). But I will say this got me to thinking about (and wanting to look at) other games I've played in the past of similar genre. And that reminded me of MechAssault. MechAssault was a game I played for the original XBox, and I mostly remembered two things about it. One - I loved it. Two, it was a very arcade style version of a mech game, with simplified weapon systems, control systems, and interface. And it used an XBox controller to drive the mech.  Perfect! I don't even have my original XBox anymore, but I was able to find some Let's Play videos on YouTube, and that gave me the information I needed to have.  Take a quick look for yourself below.

Now, I went to the video looking to refresh myself on how they handled their weapons.  And I got a lot of good information that will inform my own decisions. But what made the biggest impression on me was their control scheme. And one thing you'll notice is that all the mechs have 360 degree turret spin. This so the mech's movement direction and fire direction can be completely independent of each other. The camera is slaved to the torso, while the legs spin about to move in whatever direction the player is driving the mech. The result is a mech that is very 3rd person shooter-esque, allowing you to move forwards, back, and sidestrafe, all while keeping your camera and reticle focused on your target.

It works pretty darn well. And while I didn't necessarily feel like I had to copy their control scheme verbatim, what was certain to me now was that I really did need to give the player independent control of the mech's turret (torso), and thus their aiming reticle. And if I ever wanted to support player's having jumpjets in the game, or any target up on a ledge, they needed to be able to aim up and down as well, to at least some degree.  So turret control it was!

Unity Makes it Easy - Maybe Too Easy
So at first I thought, I have no animation for the turret spin, but I think I can easily create one, and then have the input feedback play the animation.  And then a second thought was - that's dumb. Just have the input control the rotation of the turret completely. You just need to rotate it's local orientation, and maybe place some limit's on it.

That seemed pretty doable, so I sat down, preparing myself to do some of the dirty work of turning an input axis variance of -1 to 1 to turret rotation value. Now, Unity already gives you so much, just built right into it's math library, to assist with these kinds of operations. With functions like Quaternion.SetLookRotation and Vector3.SmoothDamp, I figured I would be able to build what I needed without much difficulty. But then Unity does one more! While rummaging through my project, I found, already provided, a script in the StandardAssets folder called SimpleMouseRotator. Guess what it does? Yup - exactly what I need! I literally slapped this monobehavior on the gameobject that represented the turret mount point, fired up the game, and bam! Turret rotating!

Don't Look a Gift Horse in the Mouth - But Know What it's Eating. Or Something..
I was actually tempted to not use this, and build my own anyway. But my own personal limitation was I would build the code we needed, or use whatever was provided in Standard Assets - and this was right in Standard Assets. It would be dumb not to take advantage of it. And while I may be stubborn and hard headed, I try to not be dumb.

I played around a bit with the parameters, and quickly made the thing my own. In fact, I ditched the SimpleMouseRotator component altogether, but pulled the salient code into my MechController component, where it could have easy access to the camera, animator, and other things on the controller that it might want to talk to. And other things could talk to it.

The 'thrust' input axis
The Last 20% Takes 90% of the Time
And boy did it ever! In fact, in this case I'd say it took 99% of the time. It took me literally less than 30 minutes Saturday morning to get my turret rotating. In fact, it took me longer to figure out how to map the Xbox controller's right thumbstick to an input axis. Namely because I knew nothing about Unity's base input system, and needed to spend some time learning how to set up an axis! By the way, the XBox 360 controller page on the Unity Community Wikipedia was a really useful resource for this. I'll link it below.

But then I proceeded to spend all day Saturday, much of the day Sunday, late Sunday night, and pretty much all night Monday getting things to a point that I liked it. Yap - just about three full days of programming to get it where I liked it.  So what took so long?

The Perfect Storm of Competing Systems
Mostly this was a case study in too many systems interacting with each other in too many ways, and me trying to get it right, while doing a poor job of isolating each system.  We have the mech's rotation code, with its own collection of turning rates and dampening factors. We have the turret rotating code, also with its own collection of turn rates, dampening factors, and constraints. And then we have our follow camera, which also has its own collection of turning parameters, angular velocities, and constraints. I would try to get one of those systems working just right, but then when it interacted with the other systems, things would go to hell again. And it's not always clear where the problem was. I noticed at high angular velocities, it seemed the mech was shuttering. Was the actual mech shuttering though, or just my camera? Turns out it was the camera itself.

I won't go into every problem that I came across, tried to solve, and then re-approached over the weekend. I will say that I had a lot of fun, though it was also extremely frustrating as well. For most of the weekend, I had the camera slaved to the turret's direction - which made sense. I wanted the reticle to always stay center of the screen, so you when you rotated the turret you were also rotating the camera, and you always looked down then center off the turret, though not necessarily in the direction you were moving. But this produced a whole host of problems, both around controlling the mech, and also just being able to keep the center of the screen on any given target while driving at full speed.  At one point I built a self-centering system. While it worked really well, it produced other side effects that resulted in super fast turret rotation only when changing turret direction. I finally turned it off entirely while I sorted out other control and camera issues.

Finally, after having driven way off into the weeds in multiple ways, I decided to back way the hell up, turn the turret rotation off altogether, put the camera back on the mech itself, and think really hard about what I liked, and didn't like, about how the mech controlled. I then began turning the systems back on, just a little at a time. And I realized that I really liked having the camera back on the mech itself, especially with the turret rotation turned on. It allowed me to see the turret rotating left and right, which gave it a very MechWarrior feel. When the camera was slaved to the turret, I never saw the turret rotate, you just saw the word spin with you as you looked up and down. This proved to be really disjointing though, as the camera spun wildly about. Plus the camera's rotation speed then needed to be super fast to keep up with the turret, which introduced a lot of oscillation and variance when trying to aim. By putting the camera back on the base mech, it didn't have to be super fast, which allows it to move more smoothly, which makes driving the mech much more of a pleasure. Plus I can see the turret's rotate left and right as I drive, which is just satisfying. This would mean though, that I could no longer keep the aiming reticle in the center of the screen. It will need to move about the screen to match wherever the turret is aiming. So be it. The gains gathered more than made up for it, in my opinion. It shouldn't be hard to build an aiming system to accommodate that.

The Last Big Shift
So things were now falling into place, and I was once again getting happy with the mech's control. But I still had one major problem. In driving around the mech, whenever we turned sharply, we would immediately lose speed. So far, I've been controlling the mech in 3rd person movement style. The w,a,s,d keys, or the left thumbstick, move the mech forward, back, and rotates left and right. When controlling the mech with the gamepad, as you hauled the thumbstick far right, it moved more towards the center. This reduced forward motion. If you were very careful, you could keep the thumbstick mostly full forward and pulled right or left slightly, to make a sharp turn at-speed, but in the heat of battle you're mostly just going to want to haul left or right on the stick, and not worry about maintaining speed. That simply wasn't going to be possible with the current control scheme.

Then I remembered back to my days of MechWarrior 4: Mercenaries - also referred to fondly as The Last Game I used a Joystick For. And I remember that game put the thrust control for the mech on a separate lever. So to get the mech moving, you rolled the thrust up, and left it. And to slow the mech, or back up, you had to pull thrust back. What if I did that? The more I thought about it, the more I liked it. It just felt right. It felt MechWarrior-ish.

So I checked in all my changes thus far to GitHub, just to be safe. Then I created a new input axis, called 'thrust'. I mapped it to the controller's A and B buttons (and also to keys 'W' and 'S'). Then I made all the other necessarily modifications to my controller, so that forward momentum came from thrust, and the left thumbstick controlled direction only.  And I loved it! It feels so much better now. The mech maintains speed, and controlling the thrust gives the mech much more of a simulator feel. Which, in my opinion, isn't a bad thing at all.

The Sound Pass
Finally, to top it all off, remember last week I mentioned I wasn't super happy with the mech's footstep sounds I ended up with. And I new I wanted a sound for the turret. So I made another pass on all the mech's sounds. Much happier now with the overall sounds. The turret sound is really nice when controlled with the gamepad. It's kind of crappy when controlled with the mouse, because of the sporadic way mouselook feeds that axis. But I'm going to save fixing that problem for another day.

What Did We Learn
I actually learned a lot in this upgrade. I learned how to map and modify Unity Input schemes. I refreshed myself on some fundamental trigonometry - let me tell you it's been awhile. And I was re-reminded that camera and control schemes can be really hard to get right. Small changes have incredibly huge impact. But you shouldn't quit until you really feel good with your system, because these systems are fundamental to the enjoyment of your game. Here's this week's recap video!

Useful Links

Unity Input
The manual page for Unity's base input system

XBox 360 Controller
The Unity Wikipedia page for the XBox 360 controller. Really useful if you want to use a gamepad with your game.

The At Home Project Main Page
If you'd like to read all of the blogs associated with this project, you can find them here.

No comments:

Post a Comment