Saturday, December 12, 2015

HT: Adding Dash movement

I was thinking if the dash feature should be a new script or just add it to the PlayerMover script. I thought, "dash is simply a speed boost for a limited time". There's no need to create a new move script, all I need to do is change the speed while dash is in effect.

There were two ways to specify this. Either I express it in terms of distance and duration, or I express it in terms of speed and duration. I almost always start things this way, thinking of what variables I need to implement the functionality.

Both methods should give the same end result, but my way of thinking differs depending on which I start with, and that tends to take the development in different directions.

I went with distance and duration (speed is derived by distance divided by duration).

My first attempts were crude. I simply created a boolean to signify if the character is dashing or not. It's set to true when the player presses the dash button (A on the Xbox controller). When I do, current speed is set to an incredibly high value. I record the time the dash should end (current time plus dash duration). This "end time" I check for continually, and if we've passed that time, the dash is set to false and speed is back to normal.

Pretty simple, but I noticed a lot of things that go wrong:

  1. The camera follows the character smoothly (it kind of lags behind the player when you start moving). This is normally fine, but since dash has high speed, it makes the character go off-screen for a few moments, enough to make me feel like it was an annoyance. There needed to be different cam follow behaviour while the character is in dash.
  2. The stopping of the dash was too abrupt. Imagine how The Road Runner (Looney Tunes) stops. Nothing bad there but the thing is, the camera is following the player, so the whole world feels like it's suddenly stopping. This makes it feel rather motion-sickness inducing. There needs to be some sort of easing out to make it smooth.
  3. The character's cape was flailing wildly when subject to the high speed of the dash.
  4. The character's turn speed is too slow for this high speed dash. The dash had already finished, and only after a second or so does the character finish facing the direction where it went to. I needed a different turn speed while in dash.
  5. I haven't made the character play the dash animation yet, so it looks kinda weird.
First thing I did was change my cam follow script to stop the smoothening behaviour when following the character. This is only done during a dash (my PlayerMover script now has a public bool IsInDash property). Later on I'll realize this was the wrong way to go. Just as bad as when the dash stops abruptly, gluing the camera to the player was making the whole thing feel abrupt. It made it look like the dash was moving in stutters. I thought my framerate was just bad. I will not realize my stupidity until at the end when I was polishing the dash movement.

So I moved on. To fix the abrupt stopping, at first I thought I'd be clever and create an AnimationCurve and use that for the easing out. Then I remembered why I hate using Unity's curve editor controls.

In Photoshop, the curve controls allow you to move the handles as far away as you want. This is important. The distance of the handles can influence the shape of the curve. And the farther the handles are, the finer control you have at rotating them.


In Unity, you can't move the handles farther or nearer. You can only rotate them. This was frustrating for me:


In Photoshop, it's so easy to make this kind of easing out curve:


You just needed to move the handles on both ends.

With Unity on the other hand, you can't move the handles away. So you need to compensate by adding more handles to achieve the same curve more or less, and it's not easy to get it right:


I highly doubt the things I like about Photoshop's curve controls is proprietary stuff. Blender (an open-source program) does it too.

So I thought to myself, "why am I being a masochist?". So I stopped using Unity's curve editor and just moved on. I'll polish that later.

Adjusting the turn speed was easy. I simply have a new turn speed variable meant to be used during dash, and use that instead whenever the dash boolean was true.

What took up the most time was making some appropriate dash animations. I had one prepared, but it was just a single frame pose. I thought that would be enough. I was wrong. It looked horribly amateurish.

I thought maybe if I went ahead and added that dash recovery animation that I wanted to make, it'll look better! You see, the dash animation I had in mind was inspired by Alice's dash in Alice: Madness Returns. When she dashes, she zips by fast, and we only get to see her already in a sort of semi-crouched position, slowly going back to her idle pose. It's like she was throwing her body momentum downwards to stop the dash. And it actually made sense visually. In my sheer poetic genius, I ended up calling it, simply, dash recovery.

So I created something to that effect in Blender, and added it in the Mecanim state machine.

I figured that, the player isn't allowed to move while in the dash recovery period, but he can still do another dash (provided he still has enough energy to do so, once I implement energy usage).

At first I had a separate state for the dash loop, and for the dash recovery animation. The more I tested it, the more I saw that the dash animation and dash movement were out of sync. Sometimes the animation played just in time, sometimes there was this weird delay, and the animation would play well after the dash movement finished. Turns out it's because the dash animation isn't getting played if the state machine is in the middle of a transition. So if the character was transitioning from idle to move, dash doesn't activate yet.


I Googled and checked the docs, and the way to fix it is to set the "Interruption Source" to something other than "None" (the default value). It helped, but it still didn't work quite well. I thought, maybe I should start using that new StateMachineBehaviour script I read about. And as I was skimming through the template file Unity gave me, I thought, no, I think I'm just making things harder than it should be.

Then I thought of what exactly I want Mecanim to do. So I Googled "mecanim state transition to itself and replay animation at beginning". Then I saw this page in Unity Answers: Restart same animation in Mecanim. As I was looking through it, I thought it looked familiar. I actually answered this question with "use Animator.ForceStateNormalizedTime" That doesn't exist in Unity 5 anymore though. Someone answered you could just use "Animator.Play". So this actually means we can bypass the transition rules in the state machine and force it to jump to a state, all of a sudden.

For a scant few moments I pondered on the meaninglessness of creating lots of nice state machine features, when you are provided with the means to undermine it.

Anyway, Animator.Play worked perfectly and fixed the problem.

I also simplified the state machine by removing the dash loop state and instead use the dash recovery animation itself as the dash animation. I don't have a dash recovery state anymore.

I also removed the Move.Forward state and just kept the Move.Forward.Loop state. The Move.Forward was actually an animation to play to transition from idle to move, but there was little need for it as Unity already handles fading of one animation to another.


For the dash easing curve, I settled for using the easeOutQuint function (see http://easings.net/) to make a nice, perfect quintuple curve. For the camera follow script, instead of gluing the camera to the character, I simply gave the camera a speed boost when dash is occurring so it can catch up easily.

I ended up expressing the variables in terms of speed and duration.


Sunday, December 6, 2015

HT: Adding lean left/right to the mecanim animation



It took a while to figure this out. To do this, I needed to create new animations, a steer left, and steer right animation, and play them when the player is turning left or right.

Technically, they weren't animations. They were just single frame poses.
Preparing the Mecanim state machine for this is easy: the move animation state just needs to be converted to a blend tree, then I'd add the steer left and right animations to it:


I made a new parameter called "Steer". It's a float. The "moving straight forward" animation is set to use 0, the "lean left" is in -1, and the "lean right" is in +1.

The preview shows how it will end up looking like:


Now, how to properly play those in runtime? What I want to do is detect when the player is rotating the thumbsticks, and use that to play the lean left and lean right animations correspondingly.


This same thumbstick is the one used for movement. So it's when I'm rotating the thumbstick I should transition from lean left to right and vice-versa.

Turns out it's not so easy.

I'm using CharacterController which doesn't calcualte angular velocity. I could've just used that to detect when the character is rotating (equivalent to when the player is rotating the thumbstick).

My first thought was to use dot product on the character's current forward direction, and the direction where he needs to go to. You see, I make the character smoothly rotate towards where the thumbsticks are pointing. When you want to do a smooth rotation, you'd need to store where they currently are facing, and where they need to face to, and slowly interpolate to where they need to face to. So I thought of using those existing variables already.

The red dot here is where the thumbstick is pointing to, and the yellow dot is where the character is currently facing.

The dot product tells me how far apart the two direction values are. But it doesn't tell me if the direction where the character should go to is to their left or to their right. That's important since I need to know if I should play the lean left or lean right animation.

This is the video that taught me dot products:



So I needed an additional way to determine as to what direction the thumbsticks are rotating to. I thought of deriving the angles (in degrees) that the direction values are creating. Then subtract the two to tell if we're rotating to the right or to the left.

It almost worked well.

I noticed "bumps" when I rotate my character to the left.



I realized it was because angle values I calculate always get clamped to 360 degrees. When the target angle is over the 360 degree mark but the current angle isn't over that yet, the resulting value is too big, causing the "bump".

I recorded the angle values to a graph, so I could see what exact values I'm getting.


I figured the only way I could get around this was to get the proper angle value when it crosses over the 360 degrees mark. With a little bit of trial and error, I found the code that best suited the situation. It looks like this:

if (_angleDelta > 270) _angleDelta = -(360 -  _angleDelta);
if (_angleDelta < -270) _angleDelta = 360 + _angleDelta;


The only thing left was that I still noticed spikes in the graph of my "Steer" getting function. Depending on how bad your framerate is, it made the whole animation look like it was stuttering. I guess there was noise in the input data.

This graph looked a lot worse before.


So I just added a sort of average filter on the last 4 values recorded in the graph and use that instead:



It all seems rather straightforward now that I've put all this in a blog post, but before this, I forgot what "dot product" is and what values it returns, I didn't know what code to use to clamp angle values, and I didn't know what an "average filter" was. There was lots of looking through the Unity manual, and I wasn't even sure if this "Steer" factor idea was the right way to go about it.

It's all exploratory when solving a problem like this and trying out clever solutions.

I was a little against posting a blog post about this because it did take up the entire day, making all those screenshots and videos, and it's making me feel that people would think I'm obsessing over the little insignificant details. At least the time it took to make and fix the whole steer feature took only about a day of work.