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.


1 comment:

  1. I really appreciate your professional approach. These are pieces of very useful information that will be of great use for me in future.

    ReplyDelete

Made me post. 0/10.