Thursday, June 19, 2014

Unity3d parkour, wall running, and you! Part 3: WALL RUNNING!

I think I get unusually excited about wall running. Here's where we're leaving off from last time:

public float TurnSpeed = 90f;
public float MouseSensitivity = 2.5f;
private float cameraRotX = 0f;

private Vector3 moveDirection;
public float BaseSpeed = 4.0f;
public float JumpSpeed = 8.0f;
public float Gravity = 20.0f;

public float RampUpTime = 0.75f;
private bool moveKeyDown = false;
private float moveDownTime = 0f;
private float friction = 15.0f;

private Vector3 lastDirection;

void Update(){

     UpdateDefault();

     controller.Move(moveDirection * Time.deltaTime);
     lastDirection = moveDirection;
}

StandardCameraUpdate() {
     transform.Rotate (0f, (Input.GetAxis("Mouse X") * MouseSensitivity) * TurnSpeed * Time.deltaTime, 0f);
     camera.transform.forward = transform.forward;

     cameraRotX -= Input.GetAxis("Mouse Y") * MouseSensitivity;
     camera.transform.Rotate(cameraRotX, 0f, 0f);
}

// Default movement update for when someone's just on the ground, running and such.
void UpdateDefault() {

 // Update camera and general house keeping.
 StandardCameraUpdate();

 // Update momentum amount based on continuous run time.
 moveKeyDown = Input.GetKey(KeyCode.W);
 if(moveKeyDown && moveDownTime <= RampUpTime) {
  moveDownTime += Time.deltaTime;
  if (moveDownTime > RampUpTime){
   moveDownTime = RampUpTime;
  }
 }

 if (controller.isGrounded){

  // Stop  momentum only if grounded. Can't slow down while airborne.
  if (!moveKeyDown){
   moveDownTime = 0f;
  }

  // Update move direction with standard forward, back, and strafe controls.
  moveDirection = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
  moveDirection = transform.TransformDirection(moveDirection);
  moveDirection.Normalize();
   
  moveDirection *= BaseSpeed + (RunSpeedIncrease * (moveDownTime / RampUpTime));

  // slow to a stop if no input
  if ((moveDirection == Vector3.zero && lastDirection != Vector3.zero)) {
   if (lastDirection.x != 0){
    moveDirection.x = DoSlowDown(lastDirection.x);
   }
   
   if (lastDirection.z != 0){
    moveDirection.z = DoSlowDown(lastDirection.z);
   }
  }

  if (Input.GetButton("Jump")){
   moveDirection.y = JumpSpeed;
  }
 }

 moveDirection.y -= Gravity * Time.deltaTime;
}

float DoSlowDown(float lastVelocity){
 if (lastVelocity > 0){
  lastVelocity -= friction * Time.deltaTime;
  if (lastVelocity < 0)
   lastVelocity = 0;
 }
 else{
  lastVelocity += friction * Time.deltaTime;
  if (lastVelocity > 0)
   lastVelocity = 0;
 }
 return lastVelocity;
}

So here's where we're at now. We have neatly encapsulated code that handles our standard movement, and our look direction. I like using state machines perhaps a little too much as well. But they're great for games. Our state machine is going to handle switching from a finite number of states that the player character can be in. Is the player jumping? Default? Wall running? Climbing? Ledge grabbing? The machine we build will figure these things out and update itself for us.

Of course, technically most programs are state machines. The moment you write an if block, your machine is determining what to do because it's in one "state" or another. Most of them are not organized to run solely around the concept of different states, and only being one at a time.

First, we want to add an enumeration to our motor. We want to add it to the very bottom of the class file (not to the class itself, this will actually be outside the class.) It will only be accessible to the motor for now, there is currently no reason that anything else should have access to this enumeration. So scroll down, all the way down to the very final brace...Then go further. Add a couple empty lines, and then add:

public enum MotorStates {
 Climbing,
 Default,
 Falling,
 Jumping,
 Ledgegrabbing,
 MusclingUp,
 Wallrunning
}

Whoa! That's more than we need, right? Heck, it's more than I am actually using at the moment (I have yet to implement Falling as a state in my own code, Default is working for that. Also, thanks to my friend Kyle, who has actually done Parkour and told me what "Muscling Up" was.) I figured I'd just give you all of them, we don't need to use every single one of these yet, and later on I won't have to tell you to add them one at a time. We will, however, want to set ourselves up nicely to switch between wall running, jumping, and default states. We're going to set a MotorStates variable, and then set up a simple switch statement in our Update(). I'm also going to ask you to do something that will make you think I am purposely defeating the nice switch statement, and you don't have to, but I have this canWallRun variable to make sure a player is eligible for a wall run. I have rules, you know! Also, some wall run time limits and such, similar to the run speed ramp up. Here's the things you shall want to add to the beginning of the file, since a lot of things will be referring to these:

private MotorStates motorState = MotorStates.Default;

private bool canWallRun = true;

private float wallRunMaxTime = 1.5f;
private float wallRunTime = 0.0f;
private RaycastHit wallHit;

Now, if you've been paying attention since the last post, I'm pretty sure you can figure out what wallRunMaxTime, and wallRunTime are for (they're for limiting how long you can wall run, if you weren't!) WallHit is something that I originally had inside a function, but I moved it to the class level to facilitate with some things, and really I think it can go either way. In fact, I may change this before I'm done writing this tutorial.

Now lets stub some things out, because we have a lot of code to add. This is stuff that I built up incrementally, so how it interweaves isn't going to make perfect sense at first most likely. We're going to need a few things. We'll have a jump state, and that will check if we can wall run, or wall climb (once we get there) so we're going to need an UpdateJump(). We're going to have the wall run state, so we'll need an UpdateWallRun(), and we'll need to check if we can wall run, so we'll need a DoWallRunCheck(). I'm also going to spoil the surprise for you, we're going to want to do a StopWallRun() method as well, so lets stub everything out, and put some stuff where we know it'll be useful. You can place it wherever in your class, but here's what all we'll have:

void UpdateJump(){
 StandardCameraUpdate();

 moveDirection.y -= Gravity * Time.deltaTime;
}

RaycastHit DoWallRunCheck(){

}
 
void UpdateWallrun(){

}

void StopWallRun(){

}

For the UpdateJump(), I added in two things that we definitely know it will do. It will do a camera update, so we can look around while flying (because it doesn't make sense not to), and it will apply gravity.

UpdateJump() is one of our most important additions here, as it's the bridge between default behavior and new, vertical movement behaviors (have you played Mirror's Edge? Because you should really, really play that. I mean, I can show you stuff, but seriously, it should be a staple, like Portal.)

So now we need to have a way to get to UpdateJump(). Lets add our switch statement to Update() to get the pathway opened up:

void Update(){

 switch(motorState){

 case(MotorStates.Jumping):
  UpdateJump(); 
  break;
 case(MotorStates.Wallrunning):
  UpdateWallRun();
  break;
 default:
  UpdateDefault();
  break;
 }


 controller.Move(moveDirection * Time.deltaTime);
 lastDirection = moveDirection;
}

With that simple addition we only have to set motorState to something different to entirely change the motor's locomotion. Lets do that now! We already have a clear spot! Go to the UpdateDefault() code, and add a simple line to the area where we start jumping:

if (Input.GetButton("Jump")){
 motorState = MotorStates.Jumping;
 moveDirection.y = JumpSpeed;
}

Now on the next Update(), we can see that it's going to go to UpdateJump(). We need to make sure UpdateJump() returns us to a default state if we hit the ground:

void UpdateJump() {
 StandardCameraUpdate();

 moveDirection.y -= Gravity * Time.deltaTime;

 if (controller.isGrounded){
  motorState = MotorStates.Default;
 }
}

I recommend testing it right now. If it doesn't work, you should put a comment on my page, because I am re-making up this stuff as I go, I don't have test projects for you yet. In fact, you're probably getting an error because the stubbed out DoWallRunCheck() isn't returning anything.

Are you back? It works? Okay, great! Oh, it doesn't? Try commenting out DoWallRunCheck() and try again. Now lets go to the UpdateJump(), and get it started on honestly checking for a wall run. This is going to seem obtuse most likely, because I did the wall run check for the UpdateWallRun() long before I added UpdateJump() as a thing, but it will all come together, trust me. We'll fill out the DoWallRunCheck() in just a moment. For now, lets set up UpdateJump():

void UpdateJump() {
 StandardCameraUpdate();

 // Do a wall run check and change state if successful.
 wallHit = DoWallRunCheck();
 if (wallHit.collider != null) {
  motorState = MotorStates.Wallrunning;
  return;
 }

 moveDirection.y -= Gravity * Time.deltaTime;

 if (controller.isGrounded){
  motorState = MotorStates.Default;
 }
}

Now, as you may expect, DoWallRunCheck() is supposed to return a RaycastHit. This is used in the actual UpdateWallRun(), and since it already existed when I created UpdateJump(), I just checked to see if the collider isn't null.

DoWallRunCheck() does a ray cast to both the left and the right sides, then checks the angles if any impacts, and then returns what it views as the best impact available, though it could do that even better than it does. For our purposes, I'm going to show you the simple function as it is right now, and explain it in detail after. It should only fail in rare, weird circumstances that shouldn't happen. Naturally it means someone is going to break this immediately. Here it is:

// Does a raycast to check if a wall was hit on either side, then checks if the angle between
// the forward vector and the wall's normal is appropriate for a wall run (ie: don't wall run 
// if facing away), then returns the closest, properly angled impact (if there are two somehow)
RaycastHit DoWallRunCheck(){
 Ray rayRight = new Ray(transform.position, transform.TransformDirection(Vector3.right));
 Ray rayLeft = new Ray(transform.position, transform.TransformDirection(Vector3.left));

 RaycastHit wallImpactRight;
 RaycastHit wallImpactLeft;

 bool rightImpact = Physics.Raycast(rayRight.origin, rayRight.direction, out wallImpactRight, 1f);
 bool leftImpact = Physics.Raycast(rayLeft.origin, rayLeft.direction, out wallImpactLeft, 1f);

 if (rightImpact && Vector3.Angle(transform.TransformDirection(Vector3.forward), wallImpactRight.normal) > 90) {
  return wallImpactRight;
 }
 else if (leftImpact && Vector3.Angle(transform.TransformDirection(Vector3.forward), wallImpactLeft.normal) > 90) {
  wallImpactLeft.normal *= -1;
  return wallImpactLeft;
 }
 else {
  // Just return something empty, because nothing is good for a wall run
  return new RaycastHit();
 }
}

So the very first thing this does is make two rays. One that shoots out from the game object's transform to the right, and one to the left. It makes two variables for the impacts, and then two booleans, which are then assigned to the results of the appropriate Physics.Raycasts of each ray.

The Physics.Raycast returns True or False depending on if the ray hit something. It will also fill a RaycastHit variable with info about the impact when called. The final float is the distance. This is very important, as otherwise the Raycast will do an infinite ray in the proper direction, and you don't want to know about hitting a wall a mile away. I had this problem elsewhere.

After doing these raycasts, the boolean values are tested in an if statement, which also has an AND that then checks if the Vector3.Angle is appropriate. This is where the HitInfo is important. We can compare our forward direction, and if the angle between the forward, and the surface's normal is greater than 90 degrees, we know that we're actually moving away from the wall. The surface normal is the vector if you drew a line straight out perpendicular to the surface impacted by the raycast.

Now, another trick is that if we're wall running on a wall to the left of the player, we need to multiply the HitInfo's normal vector by -1. There is a mathematical reason for it, but I've been writing these blogs all day to get through this point and my brain is getting a little fuzzy. Sorry! I'll update this if a way to explain it suddenly comes to me. The important thing to know is that the surface normal for the left side is the opposite of what we want it to be, so we just fix that by multiplying it by -1.

The final thing is that the HitInfo is returned for whichever one was appropriate. If neither are, an empty hit is returned. This is because RaycastHit is a struct, so we cannot return null. We return an empty struct, and then the UpdateJump() verifies that something that wouldn't be null if the hit were successful is. If the RaycastHit's collider is null, we know it didn't hit anything.

However, now we're at a point where DoWallRunCheck() will return a value that will evaluate to true in UpdateJump(), which means on our next Update(), the switch will call UpdateWallRun(), which is empty! This will cause the program to hang. So now we should discuss UpdateWallRun().

Lets just start with the initial check in UpdateWallRun() to make sure we should be wall running:

void UpdateWallRun(){
 if (!controller.isGrounded && canWallRun && wallRunTime < wallRunMaxTime){
  // Wall run stuff...
 }
 else {
  StopWallRun();
 }
}

So, the first if is a little convoluted even for my tastes, but there's a reason. Obviously, if any of the three parameters fails, we StopWallRun(). The first may seem silly, because we just got to the wall run by jumping, right? So we won't be grounded! But Update() is going to repeatedly UpdateWallRun(), and as time goes on, we're going to fall more and more even as we wall run. So eventually, we either hit max time, or we hit the ground that is hopefully there. If we hit the ground, we stop wall running, because it's better and safer to run on the ground than on a wall. Also, I didn't always have this, and it was very awkward to be stuck against a wall until you hit a time limit or the wall ends.

The next is if we can wall run. This is more optional and less necessary than making sure we can be grounded. As you will soon see, I have the wall run set up so once you stop wall running for any reason, you can't do it again. Even if you make it through the air to another wall, you don't get to wall run off a second wall. These are the rules I've established, I didn't want to allow for the temptation of any obstacles that are just timing to make sure you repeatedly wall run properly. This is where your game design can really vary. Maybe you want someone to be able to perpetually wall run as long as they keep jumping between walls, or maybe you only want them to be able to do it twice, or thrice! It's up to you, but that's why I put that check in.

The next check you can also change if you want. I put a time limit. Once the wall run time is no longer less than the max time, it is time to stop.

So now with that explained, lets do one final check, and then reset our state (this is more of an artifact, but this also means we can just get to the UpdateWallRun() from somewhere else too, and don't have to worry about changing it.) Inside our if block, where we actually do wall run stuff, add this real quick:

wallHit = DoWallRunCheck();
if (wallHit.collider == null){
 StopWallRun();
 return;
}

motorState = MotorStates.Wallrunning;

Here is where DoWallRunCheck() is actually setting us up for wallHit to do a lot of work. Before it was just telling us if we were eligible to wall run. But now wallHit's very important information is going to help us calculate the direction we need to go. What we are going to do is get the Cross Product between our character and the surface normal that was impacted. The cross product returns a vector that is at a right angle (perpendicular) to two vectors. We use the surface normal of the wallHit, and Vector3.up to get this cross product, and that becomes our movement vector.

We then Slerp the actual rotation over a short amount of time so it's a visually organic movement, even though our actual moveDirection changes at a sharp angle. Slerp is a gradual rotation over time using Quaternions. I'm not going to go into what Quaternions are, there are many other blogs about that, but just remember: you need to rotate over time, you can use Quaternion.Slerp to do it. Here is the code we shall add right after resetting the motorState in the UpdateWallRun function:

float previousJumpHeight = moveDirection.y;

Vector3 crossProduct = Vector3.Cross(Vector3.up, wallHit.normal);

Quaternion lookDirection = Quaternion.LookRotation(crossProduct);
transform.rotation = Quaternion.Slerp(transform.rotation, lookDirection, 3.5f * Time.deltaTime);

moveDirection = crossProduct;
moveDirection.Normalize();
moveDirection *= BaseSpeed + (RunSpeedIncrease * (moveDownTime / RampUpTime));

See? Calculate the crossProduct, it then goes to helping find our lookDirection (a quaternion.) We Slerp towards that direction, and then the movement set up virtually exactly the same as before. One thing to note: do the way the rotation is set here, and the way it's set in the StandardCameraUpdate(), there will be a jerk in camera angle once you land. I'm working on how to make these things mesh together.

One thing to note, however. Calculating the crossProduct flatlines the y movement. The easiest way around it is to save the previous Y axis of the movement vector, and reset it later. Also, if the jump is just starting, the Y-axis should be set so there's some vertical movement to the jump. If it's not just starting, then we subtract a reduced amount of gravity from the Y-axis, since we have all this wall running stuff to help fight gravity. Add the following code just after the previous moveDirection code:

if (wallRunTime == 0.0f){
 moveDirection.y = JumpSpeed / 4;
}
else {
 moveDirection.y = previousJumpHeight;
 moveDirection.y -= (Gravity / 4) * Time.deltaTime;
}

Pretty familiar, right? If not, go back and read the previous two parts to this post series!

So now we've updated our forward movement. We've updated our vertical movement. The only thing left to do is update the timer! At this point, I don't think I need to explain all of the code, as it's similar to stuff you've seen previously. If we've gone over our time, I turn the canWallRun to false, and let the player drop on the next update:

wallRunTime += Time.deltaTime;

if (wallRunTime > wallRunMaxTime){
 canWallRun = false;
}

This is it for our wall run. The only thing left is the StopWallRun(). We need to set this so it resets our variables and changes states properly. It's also pretty self explanatory at this point:

void StopWallRun(){
 if (motorState == MotorStates.Wallrunning)
  canWallRun = false;

 wallRunTime = 0.0f;
 motorState = MotorStates.Default;
}

I just do a little validation and make sure that canWallRun is set to false. Here's one game design choice: I set the motorState to Default. This means it goes back to UpdateDefault(), which allows the player to look, and also applies gravity. I could, for example, set it to UpdateJump(), and because canWallRun is false, they won't wall run again, but they could potentially wall climb (which I've added in the most recent, but will post in a later blog.) As it is, I feel that wall run is not enough of an upward, "jump up" movement to give the momentum to wall climb. Perhaps, when I add the wall jump, I may change my mind, but that would be a WallJump() function switching state back to Jumping.

I'm going to stop here for right now. My original goal was to talk about wall running in Unity, and create a tutorial for that since there wasn't one. This has come about by my personal parkour project called Jump! You can see the full code on my Github here: https://github.com/ScavengerHyena/Jump/blob/master/Assets/Scripts/jumpMotor.cs

Please, once again, this is my first blog, and first tutorial. Let me know how I might improve it and if anything has gone wrong where it shouldn't have, or if you're just entirely lost. I haven't been afraid to provide this information since everything is in a work-in-progress state, and this has been done as a personal project to get practice and keep busy in between professional contracts. If you directly use my classes, please attribute, or talk about it, or something. The gesture would be greatly appreciated, since the entire thing I hope for with this work is to create a fun parkour game, and also recognition.

Wednesday, June 18, 2014

Unity3d parkour, wall Running and you! Part 2: Intermediate Movement

So where we left off last time was with this code:
public float TurnSpeed = 90f;
public float MouseSensitivity = 2.5f;
private float cameraRotX = 0f;

private Vector3 moveDirection;
public float BaseSpeed = 4.0f;
public float JumpSpeed = 8.0f;
public float Gravity = 20.0f;

void Update() {
     transform.Rotate (0f, (Input.GetAxis("Mouse X") * MouseSensitivity) * TurnSpeed * Time.deltaTime, 0f);
     camera.transform.forward = transform.forward;

     cameraRotX -= Input.GetAxis("Mouse Y") * MouseSensitivity;
     camera.transform.Rotate(cameraRotX, 0f, 0f);

     if (controller.isGrounded) {
          moveDirection = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
          moveDirection = transform.TransformDirection(moveDirection);
          moveDirection.Normalize();

          moveDirection *= BaseSpeed;

          if (Input.GetButton("Jump")){
               moveDirection.y = JumpSpeed;
          }
     }

     moveDirection.y -= Gravity * Time.deltaTime;

     controller.Move(moveDirection * Time.deltaTime);
}

So what we want to work on next is...still not the wall running. Not just yet. I'm sorry, we'll get to that. Lets do some house keeping though to keep things clean.

Remember how I said we're not applying movement when we're grounded? Well, there's a chance we don't want camera updates happening at certain times either. Lets take the camera code out, and put it in a new function:

StandardCameraUpdate() {
     transform.Rotate (0f, (Input.GetAxis("Mouse X") * MouseSensitivity) * TurnSpeed * Time.deltaTime, 0f);
     camera.transform.forward = transform.forward;

     cameraRotX -= Input.GetAxis("Mouse Y") * MouseSensitivity;
     camera.transform.Rotate(cameraRotX, 0f, 0f);
}

Ah, that feels better. Right? And you know something, we're already moving and jumping. We're also going to be wall climbing, ledge grabbing, wall jumping, muscling up ledges, which will all be controlled via the Update()...So before it becomes a mess (and conveniently I've already been through and cleaned up that mess for you guys), lets take the default movement code, and give it its own function instead:

UpdateDefault(){

     StandardCameraUpdate();

     if (controller.isGrounded) {
          moveDirection = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
          moveDirection = transform.TransformDirection(moveDirection);
          moveDirection.Normalize();

          moveDirection *= BaseSpeed;

          if (Input.GetButton("Jump")){
               moveDirection.y = JumpSpeed;
          }
     }

     moveDirection.y -= Gravity * Time.deltaTime;
}

Please note, the first thing called again is the StandardCameraUpdate(). Now, whenever we're doing default movement, we need to aim the camera. Of course, there may be other times when we're not doing default movement that we want to look around (like when we're jumping), so we'll want to call StandardCameraUpdate() then too. This is why it isn't folded into our default movement, and the camera update has its own function.

This means our Update() code is now cut down to this:

void Update(){

     UpdateDefault();

     controller.Move(moveDirection * Time.deltaTime);
}

Now that looks simple! But this encapsulation is pointless if we don't create other updates. We will, but lets perfect our UpdateDefault() for our default movement first. And by, "perfect", I mean, "get you guys to basically where I'm at," because nothing is ever really perfect.

Now, an important part in my design has been the organic feeling. When you start running, you usually don't immediately hit top speed. You kind of work up to it. Also if you're running at full tilt, you probably take a couple steps to stop, even when you're trying hard. It's subtle touches like this that are important, I think. Lets try adding this in!

First, this type of stuff is going to require information from frame to frame. We also need a maximum speed that we're going to hit, and we need to decide how long it's going to take of continuous running before we hit that speed. (Also, if you haven't already, I recommend taking some time on your scene in Unity to add boxes, ramps, or walls, preferably of different colors, to give your platform some visual reference for what you're doing, where you are, and how fast you're going while you test it.)

So lets add some variables:

public float RunSpeedIncrease = 4.0f;

public float RampUpTime = 0.75f;
private bool moveKeyDown = false;
private float moveDownTime = 0f;
private float friction = 15.0f;

Now, these guys give us some important ways to keep track of what's going on. RampUpTime is the total amount of time it takes to get to top speed (my RampUpTime here is geared toward hitting top speed after running about 1 unit), RunSpeedIncrease is the total increase to the run speed, moveKeyDown is whether or not our move key is held down, and moveDownTime is how long that key has been held down. Friction is how fast we stop which we'll use when we get there.

Our very first step is an addition to UpdateDefault(). I've put this at the beginning, before anything else just to get it out of the way:

moveKeyDown = Input.GetKey(KeyCode.W);
if(moveKeyDown && moveDownTime < RampUpTime) {
 moveDownTime += Time.deltaTime;
 if (moveDownTime > RampUpTime){
  moveDownTime = RampUpTime;
 }
}

Now, what are we doing here? We are setting our moveKeyDown from the Input class. In the future, it should be more fluid to user control layout, but for now, we can hardcode it to 'W', or anything else we want. Fun fact: if you anticipate or mean for this to be played with joystick controllers, you should think about using the analog values to help determine how fast a person should go too!

Now, if the move key is down, and our move down time is less than our ramp up time, we need to adjust our moveDownTime to reflect that, so we add the deltaTime. If adding that delta time has made it greater than the total RampUpTime possible, we just set the moveDownTime to the RampUpTime.

If the user lets go of the key, however, then we've stopped pushing forward. We need to kill all that ramping up we've done. We should only do this if the user is grounded. Just after the isGrounded check, add this code:

if (controller.isGrounded){

 // Stop  momentum only if grounded. Can't slow down while airborne.
 if (!moveKeyDown){
  moveDownTime = 0f;
 }

That will make sure that as long as the player is holding down the move key when they land, they can continue at their heightened run speed.

Now we actually need to apply this heightened run speed. We're already appropriately getting our movement, so lets just update the line where we multiply the moveDirection by the base speed. We need to add the product of the total run speed increase mutiplied by the quotient of the run time over the total ramp up time. Or, in a better equation and code form:

moveDirection *= BaseSpeed + (RunSpeedIncrease * (moveDownTime / RampUpTime));

There we go! If you test it now, your character should run faster over time. If you don't notice, I recommend making the ramp up time just a bit longer, somewhere around 1-2 seconds instead of 3/4's of a second like I defaulted to. You can also make your RunSpeedIncrease a lot higher too.

What we need to do to add a more gradual slowdown is keep track of if we were moving before, and gradually reduce that amount. You'll want to add a new class level variable for this since it will have to persist from frame to frame (I suppose it doesn't have to, but it's easier this way to me.) We'll also want to assign it after we've done movement every frame. So add the variable, and then a final line before the end of your Update():

private Vector3 lastDirection;

void Update(){

     UpdateDefault();

     controller.Move(moveDirection * Time.deltaTime);
     lastDirection = moveDirection;
}

Now, this is one of those moments where I'd like to remind you that I am no genius. If you know a better way to do this, please do that (and let me know what it is if this has helped you out so far.) But if you don't, here's a new function you can add that's going to help us update our default movement when we slow down. We're going to pass in vector axis for our movement and reduce them based on our friction over time:

float DoSlowDown(float lastVelocity){
 if (lastVelocity > 0){
  lastVelocity -= friction * Time.deltaTime;
  if (lastVelocity < 0)
   lastVelocity = 0;
 }
 else{
  lastVelocity += friction * Time.deltaTime;
  if (lastVelocity > 0)
   lastVelocity = 0;
 }
 return lastVelocity;
}

I know what you're thinking. "Wait! What if lastVelocity is equal to 0?" Well, I don't have it that way. The code is only going to get as far as calling this if the axis in question is not equal to 0.

If we're not moving and need to slow down, it's because no keys are being pressed. Now, granted this is different if we suddenly started going backwards, but I'm going to pretend this isn't a problem yet (and if you figure it out before I eventually update this, I'd be happy to know!) Right now I don't want to figure out inverse vectors and all that. So, basically, if our poll for moveDirection has left it equal to Vector3.zero, we need to start slowing down. For each horizontal axis (X and Z is what I'm saying) that is a non-zero value, we're going to start slowing them down using DoSlowDown(). So lets add this new code just after we've gotten the final verdict on our movement:

// slow to a stop if no input
if ((moveDirection == Vector3.zero && lastDirection != Vector3.zero)) {
 if (lastDirection.x != 0){
  moveDirection.x = DoSlowDown(lastDirection.x);
 }
    
 if (lastDirection.z != 0){
  moveDirection.z = DoSlowDown(lastDirection.z);
 }
}

That should do it! Test your code, and see how your movement goes. I've noticed a slight lean to one side with this slow down code, but it's been minor enough that I haven't freaked out about it yet. This is also a learning experience.

To give a summary, your final code should look something like this:

public float TurnSpeed = 90f;
public float MouseSensitivity = 2.5f;
private float cameraRotX = 0f;

private Vector3 moveDirection;
public float BaseSpeed = 4.0f;
public float JumpSpeed = 8.0f;
public float Gravity = 20.0f;

public float RampUpTime = 0.75f;
private bool moveKeyDown = false;
private float moveDownTime = 0f;
private float friction = 15.0f;

private Vector3 lastDirection;

void Update(){

     UpdateDefault();

     controller.Move(moveDirection * Time.deltaTime);
     lastDirection = moveDirection;
}

StandardCameraUpdate() {
     transform.Rotate (0f, (Input.GetAxis("Mouse X") * MouseSensitivity) * TurnSpeed * Time.deltaTime, 0f);
     camera.transform.forward = transform.forward;

     cameraRotX -= Input.GetAxis("Mouse Y") * MouseSensitivity;
     camera.transform.Rotate(cameraRotX, 0f, 0f);
}

// Default movement update for when someone's just on the ground, running and such.
void UpdateDefault() {

 // Update camera and general house keeping.
 StandardCameraUpdate();

 // Update momentum amount based on continuous run time.
 moveKeyDown = Input.GetKey(KeyCode.W);
 if(moveKeyDown && moveDownTime <= RampUpTime) {
  moveDownTime += Time.deltaTime;
  if (moveDownTime > RampUpTime){
   moveDownTime = RampUpTime;
  }
 }

 if (controller.isGrounded){

  // Stop  momentum only if grounded. Can't slow down while airborne.
  if (!moveKeyDown){
   moveDownTime = 0f;
  }

  // Update move direction with standard forward, back, and strafe controls.
  moveDirection = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
  moveDirection = transform.TransformDirection(moveDirection);
  moveDirection.Normalize();
   
  moveDirection *= BaseSpeed + (RunSpeedIncrease * (moveDownTime / RampUpTime));

  // slow to a stop if no input
  if ((moveDirection == Vector3.zero && lastDirection != Vector3.zero)) {
   if (lastDirection.x != 0){
    moveDirection.x = DoSlowDown(lastDirection.x);
   }
   
   if (lastDirection.z != 0){
    moveDirection.z = DoSlowDown(lastDirection.z);
   }
  }

  if (Input.GetButton("Jump")){
   moveDirection.y = JumpSpeed;
  }
 }

 moveDirection.y -= Gravity * Time.deltaTime;
}

float DoSlowDown(float lastVelocity){
 if (lastVelocity > 0){
  lastVelocity -= friction * Time.deltaTime;
  if (lastVelocity < 0)
   lastVelocity = 0;
 }
 else{
  lastVelocity += friction * Time.deltaTime;
  if (lastVelocity > 0)
   lastVelocity = 0;
 }
 return lastVelocity;
}

Here's this lesson's fun fact: DoSlowDown() was invented on the spot while writing this tutorial. I came back to explain that code, realized I had written the same thing twice just switching out "x" and "z", and realized I could make it its own function. I pushed it to Git just now. Yay!

Okay, now that we've gotten this out of the way, next time we'll get to the wall run. It's going to be kind of a big deal, I think, so get ready! We're also going to use a switch statement in the Update() to turn this into a simple state machine, making the motor more flexible and able to negotiate what it needs to do on its own.

Unity3D parkour, wall running, and you! Part 1: Basic Movement.

Hello! This is my first blog, so I apologize if it's rough. I also apologize if you come back and I assault your eyes with random and endlessly varying themes.

I wanted to do at least a few posts though for an easy reference for something that I feel there is a dearth of information on for budding gamers trying to program in Unity. When I started to search for advice on how to wall run in Unity, I found just people asking questions on Unity Answers, and really they were too vague for the area. People appropriately posted, "Use a ray cast", and that was all well and good, but there was very little detail. Now that I've worked this stuff out on my own, I wanted to post a tutorial as a reference for people looking to do the same thing.

I'm not saying mine is best, or even optimized. For some projects, this could be a terrible idea for all I know. This all is posted here as a learning experience.

The goals of my project that got me started have been fairly simple:

  • Have first-person perspective parkour movement
  • Have procedural puzzles generated level by level so it's always different and ever-continuing
So my choices have all worked for this concept so far. If you want to spawn millions of enemies and have bullets flying everywhere like in Mirror's Edge, you may like to try to be very efficient with ray casts if my ideas don't help. This also means that by and large I haven't made this class modular enough to just be tossed onto an NPC and have it use this motor, but you could potentially do that with some modification.

Anyway, without further ado...

Basic Movement!

Before you can go running on walls, you need to be running on the ground. There's a stunning variety of tutorials for this too, and having looked through a lot of them, they can be confusing. I wanted standard, WASD movement, and my controls ended up like this:
  • W - forward
  • A - Backwards
  • S - Strafe left
  • D - Strafe Right
  • Space - Jump
I'll guide you how to get there now.

Create the world

To start, create a new project, and make sure to import at the very least the character controllers that Unity comes with. This will save you some time later on.

I'm not going to go into detail for the following steps, you should probably be familiar in general, but you need to create the world. My standard run for this is:
  • Create a cube (some just do a plane), scale its X and Z properties to 100 or something, make sure its position is 0,0,0. Now you have a platform.
  • Create directional light (so you can see.) Point it in some direction, position doesn't matter. I usually put it at an angle so the shadows can help me discern what's going on.
  • Create a capsule. Parent the main camera to it. The easiest way is to click on the main camera and drag it to the capsule in the heirarchy. Make sure the capsule's position is above your ground platform. Mine is at 6 meters above, so it actually starts with a drop.
Now you have a person! I actually went about this fairly backwards, since I was experimenting. I dragged the FPS controller prefab out from my project into the scene, and started deleting things off it in my inspector view (the instance I pulled out, not the prefab itself) to figure out what I needed and how to do what I was going to do.

The next thing you are going to want to do is grab your capsule, and go to "Add Component" in the Inspector window. Then just type "Character Controller", and it will bring you to an item that you want. It's going to have a little icon like a capsule with three smaller capsules around it. At least, it does in my setup.

The Character Controller is an important helper that allows you to deal with movement while also having collisions set up with a rigidbody. It does so much stuff for you that I'm telling you to use this thing, and I'm not even sure of the full-depth of its utility yet. If you are not familiar with it, check out the documentation for it.

Test your scene, make sure it doesn't crash. Make sure you can see stuff. Your camera won't move. Why? because we have nothing set up!

Now, the character controllers package has a lot of scripts in it that can give you a general FPS setup, but it's in Javascript, and I hate Javascript. It's also gargantuan, and a huge file is difficult to modify when you don't know what's going on (and I'm presuming if you're here, you don't want to wade through all that code.) We're going to start from brass tacks here.

Looking Around

We need to start on our first script! Create a new script somehow. You can click on your capsule in heirarchy, go to "Add Component", and then add a new C# script, or my preferred way is to go to my project's Assets folder in the project window, add a "Scripts" folder (my own, separate from the "StandardAssets\Scripts" folder, that way I know what's mine and what I've imported), then add a new C# file. You can name it however you want, but think of it is a Motor. I named mine "jumpMotor", because my project's working title is "Jump!" and I forgot that I prefer to PascalCase files, but you can name yours whatever. "myMotor" always works if you're just learning.

Now, I'm going to present all this like I'm some genius, but I went through a lot of research and experimentation, and an entire iterative process. Never feel bad if you're trying something new and it doesn't come together like a tutorial does.

Open your new C# file in MonoDevelop, or your preferred editor if you have a different one set up.

At the very beginning of your file, lets just do a little bit of housekeeping. You'll have your Using imports that come standard, and your class declaration below that. We want to add line that makes sure the Character Controller component is on the item you're about to create. Add this:
using UnityEngine;
using System.Collections;

[RequireComponent(typeof(CharacterController))]

public class jumpMotor : MonoBehaviour {

The "RequireComponent" sets it up so that the script will always make sure there's a CharacterController on the object it's placed on. If there isn't a CharacterController on the object there when the script is added, Unity will add one for you if "RequireComponent" is set up. If you try to accidentally delete the Character Controller, Unity will tell you that you can't.

Now, the next step is a little more housekeeping to get started since this is an FPS. We want to have a reference in our script to the controller since we'll be using it, and I like to cache a reference to the camera since we'll also be using this. You should probably just set a public camera variable and set the Main Camera to that variable in the Inspector. Unity gives me a warning (but not an error) over the code I'm about to show you, but I'm lazy and haven't changed it:

Private CharacterController controller;
Public Camera camera;

void Start(){
 camera = Camera.main; // You'll get a warning over this, but it's fine for now.
 controller = GetComponent<ChacterController>(); // We know this exists because of our "RequireComponent"
}

Now we have a good start (no pun intended)! This makes sure we can safely reference two items we need to move and look at things. From here, the real magic can start!

Our next step will be to just start looking around. We'll start doing this magic in our Update() function. For those who are very new, Update() is called basically every frame, and is the driving force behind this motor. I'm going to do the filthy, teacher-y thing where I have you put the code in the wrong place, then make you change it later. Start asking yourself why wouldn't you want this code here? But if you don't know where to put it now, just follow along, and update your Update() to this:

// Here are some quality of life variables so we can adjust movement a little more easily.
Public float TurnSpeed = 90f;
Public float MouseSensitivity = 2.5f;
float cameraRotX = 0f;
void Update() {
 transform.Rotate (0f, (Input.GetAxis("Mouse X") * MouseSensitivity) * TurnSpeed * Time.deltaTime, 0f);
 camera.transform.forward = transform.forward;

 cameraRotX -= Input.GetAxis("Mouse Y") * MouseSensitivity;
 camera.transform.Rotate(cameraRotX, 0f, 0f);
}

Now what we're doing here is setting up two variables to make our lives easier. TurnSpeed is how quickly you turn in general. I decided to mess around with MouseSensitivity as well, which means TurnSpeed can still be used for custom, scripted rotations and MouseSensitivity only comes in when doing mouse turns.

cameraRotX is different, that variable is to keep track of the rotation on the X-axis. I included this because if I updated it from 0 every time, I could never get the rotation I wanted.

What then happens is the capsule itself (of which the camera is a child) is rotated around its Y-axis based on the mouse's X position multiplied by the turnspeed, mouse sensitivity, and Time.deltaTime. This is the amount of time since the last frame in fractions of a second. If it's been half a second (hopefully not), Time.deltaTime would be 0.5. This means I turning is based on how fast things are being processed and rendered, which is a good thing. You'll see Time.deltaTime a lot if you're not familiar with it.

Then we update the camera's forward, so it's the same forward as the capsule. Trust me, you want to do this, it just keeps your camera movement clean. I recently tried to reorder this to do some fancy camera tricks, and things went insane.

cameraRotX is then updated with the change in the Mouse Y, and the camera's transform is changed again.

Now if you test your new Update() function, you should find yourself able to look around, but you don't move!  Lets fix that.

I like to move it, move it!

So now we're looking around. We can aim ourselves. This is great! Now we just need to apply movement. We're still just experimenting, right? So now that we're faced in the right direction, lets add some code to the tail end of our update. We're also going to add some more helper stuff that will be very useful later on.

Public float TurnSpeed = 90f;
Public float MouseSensitivity = 2.5f;
Private float cameraRotX = 0f;

Private Vector3 moveDirection;
Public float BaseSpeed = 4.0f;
Public float JumpSpeed = 8.0f;
Public float Gravity = 20.0f;

void Update() {
 transform.Rotate (0f, (Input.GetAxis("Mouse X") * MouseSensitivity) * TurnSpeed * Time.deltaTime, 0f);
 camera.transform.forward = transform.forward;

 cameraRotX -= Input.GetAxis("Mouse Y") * MouseSensitivity;
 camera.transform.Rotate(cameraRotX, 0f, 0f);

 // New code:

 if (controller.isGrounded) {
  moveDirection = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
  moveDirection = transform.TransformDirection(moveDirection);
  moveDirection.Normalize();

  moveDirection *= BaseSpeed;

  if (Input.GetButton("Jump")){
   moveDirection.y = JumpSpeed;
  }
 }

 moveDirection.y -= Gravity * Time.deltaTime;

 controller.Move(moveDirection * Time.deltaTime);
}

Okay, now it may seem like a bit to digest, but don't worry. We've added a few new things. A moveDirection that's at the class level because we will be manipulating this a lot. This is a vector3, and handles walking forward and backwards, strafing, and jumping. JumpSpeed controls how high the motor jumps, BaseSpeed is how much movement is applied every frame, and Gravity is how quickly we fall. These are all public so you can manipulate them while testing out your code in the Unity editor.

Our first line of code makes our first reference to the controller to check if we're grounded. That is, the bottom of the capsule is colliding with ground. If it is, then we can do the rest of our movement. If it isn't, then the script will only apply gravity, dragging the capsule down until it hits something. This way you're not reversing direction in the middle of flight. If you want to change direction in the middle of flight, don't check for this, but for the project I'm making, once you are in the air, you are committed!

After that check, like I said, we update the movement. The moveDirection queries Unity's input, which is conveniently tracking our W,A,S,D keys for presses without having to say anything. This is what it's doing when it's doing GetAxisRaw. There's also regular GetAxis, but chose to work with the raw variables, "just in case" in my head. Choose whichever.

The move direction is transformed to be appropriate for the game object's local space, otherwise you'd turn around and backwards (S) would be forward, and forwards (W) would be backwards! That would be bad.

Finally the moveDirection is normalized, which means the vector keeps the same direction, but its length is 1. What that means is on the next line, where we multiply the whole vector times the base speed, it's like updating something that could move the range of 1 meter per second by 4. So now it's 4 meters per second in whatever direction we're facing, including diagonals! If you've ever programmed movement in a game, you know that moving faster on the diagonal is always a problem.

Following this, we check if we're jumping. This makes sense to do on the ground, because unless you have the mythical double-jump, you can't jump in the air. All we have to do is see if Input has detected "Jump", which defaults to space bar, and then apply the Jump speed to the moveDirections Y value. Note that we directly assign it, we don't add it. This is because moveDirection represents magnitude and direction, not position.

Now, the final steps, we subtract gravity from the movement using Gravity multiplied by deltaTime, so this will be a small amount, but over a second it will be equivalent to dropping by 20.

The very, very last thing we now do every frame is inform the controller what we're moving by. We've worked very hard to apply all movement we need to, so now we just call, controller.Move(), and pass it in our direction multiplied by the deltaTime. controller.Move() doesn't automatically account for frame time, so we have to multiply it on our own. Test it out!

I'm going to stop this blog here. It's already gone pretty long, and it is a lot to digest. For the next blog, I'll do some advanced movement with adding momentum to the forward run, so the user goes from the base speed to a full run speed as the user runs continuously. I may also start on the wall run, I'll at least start talking about the encapsulation that we want to really make this a powerful movement class.

Please let me know if there are any errors or stuff that doesn't work, as this is my first time writing a tutorial like this, and this more basic code was pulled from previous experience rather than a pre-typed, pre-tested class. The final code will be that, but not this code.