Followed a free running game tutorial, got it working, but now I want to edit it more

So I found this great tutorial on how to make a free running game which goes into a lot of detail about how to do the acual code for it. However now I want to take a look at doing the aesthetics behind everything, like for instance, two things I want to do is have the camera and the player rotate on the Z axis by about 45 degrees while doing wall running, because of course, that’s what your body does normally because otherwise your body would just end up flat against the wall and wouldn’t move.

The second thing is for me to put in some hand animations, now i can do all the modelling myself and so on, that’s not the worry, but I just need to know where to put this so I don’t fuck things up. I thought at least for the angle change I should put it in with my raycasts for when the script finds out if the player has hit the wall which sort of worked but it seemed to interfere with all the other code.

Here’s the tutorial for those who want to take a look: Scavenger Stuff: Unity3D parkour, wall running, and you! Part 1: Basic Movement.

using UnityEngine;
using System.Collections;

[RequireComponent ( typeof ( CharacterController ) ) ]

public class jumpMotor : MonoBehaviour {


    private CharacterController controller;
    public Camera camera;


    void Start ()

    {
        camera = Camera.main;
        controller = GetComponent<CharacterController> ();
    }




    public float TurnSpeed = 8f;
    public float MouseSensitivity = 2.5f;
    float cameraRotX = 0f;

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

    public float RunSpeedIncrease = 4.0f;

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

    private Vector3 lastDirection;

    private MotorStates motorState = MotorStates.Default;

    private bool CanWallRun = true;

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



    void Update ()

    {

        switch (motorState) {

        case ( MotorStates.Jumping ):

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


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










    void 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 );
    }



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



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

        }




        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);
        }



        if (controller.isGrounded) {
            if (!moveKeyDown) {
                moveDownTime = 0f;
            }

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




            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;

    }

    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;
    }


    // Wall Running Code

    public enum MotorStates{

        Climbing,
        Default,
        Falling,
        Jumping,
        LedgeGrabbing,
        MusclingUp,
        Wallrunning


    }




    void UpdateJump ()
    {
        StandardCameraUpdate ();

        wallHit = DoWallRunCheck ();
        if (wallHit.collider != null)
   
        {
            motorState = MotorStates.Wallrunning;
            return;
        }



        moveDirection.y -= Gravity * Time.deltaTime;

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


    }


    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 {

            return new RaycastHit ();

        }


    }

    void UpdateWallRun ()

    {
        if (!controller.isGrounded && CanWallRun && wallRunTime < wallRunMaxTime)
   
        {
            wallHit = DoWallRunCheck ();

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

            motorState = MotorStates.Wallrunning;

            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));

            if (wallRunTime == 0.0f)
       
            {
                moveDirection.y = JumpSpeed / 4;
            }

            else
       
            {
                moveDirection.y = previousJumpHeight;
                moveDirection.y -= (Gravity / 4) * Time.deltaTime;
            }


            wallRunTime += Time.deltaTime;

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


        }

        else
   
        {
            StopWallRun ();
        }


    }

    void StopWallRun ()

    {
        if (motorState == MotorStates.Wallrunning)
   
        {
            CanWallRun = false;

            wallRunTime = 0.0f;
            motorState = MotorStates.Default;

        }
    }



    // End of Wall Running code
}

For those wondering, as the tutorial describes, the aim is to make a game very similar to mirror’s edge style of free running, I’ve also noticed that for whatever reason the code seem to have a bit of trouble jumping between two parallel walls like you do to get up to a higher obstacle.

Not to discourage you, but the code is very messy and I started using CharacterController when I started and now I absolutely hate it because it hides a lot of functionality of Unity that is better to learn first. A good thing to do is to go through all your code and make comments about what things do what and organize the functions a little better. You will eventually also want to break up the code into separate functionality classes like movement, cameracontrol, menucontrol, etc. The code is not bad itself just really unorganized.

To figure out why it can’t ‘double jump’ between parallel walls, look at where it’s transitioning between wallrun jump and wallrun. When ‘Stopwallrun’ is ran, CanWallRun is set to false, and I don’t see anywhere where it’s ever set to true again while the character is in the air, so this could be why although I am not certain.

Thanks and don’t worry about it I have literally just learned this code in the past few weeks so it is going to be a bit of a mess anyway with this much code.

I guess I’ll have to just poke around, I sort of got the affect I wanted because I tried using transform.rotation on the raycasts when the checks happen but it looks like that was too soon and it causes the whole thing to jerk and then reset itselfe when you land.

However my brain just clicked on the tilting camera issue and It may well be that the isWallRunning bits of code will be the answer so I’ll see if I can put stuff in that and tidy it up. Even if the code is messy the guy does a good job of breaking it down at least.

I do need to figure out though how to get animations in at certain points, maybe it will be worth just doing some really obvious and idiotic animation like a massive head bob and just insert it into each function to find things out.

Something to keep in mind with the rotating camera and player is that the ‘forward’ direction will change when the player changes so depending on how it’s coded it will have unwanted effects.

For doing timed stuff and transitions you’ll eventually want to look into Coroutines to do timed functions, like say you wanted to respawn the player after 3 seconds, or wanted the camera to change over 2 seconds instead of instantly:

int playerHealth,playerHealthMax;
bool isAlive=true;

void Update()
{
         if(playerHealth <= 0 && isAlive)
             StartCoroutine(killPlayer);
}

IEnumerator killPlayer()
{
   isAlive = false;
   //Display player is dead message, red screen, whatever
    yield return new WaitForSeconds(3.0f);
    transform.position = respawnPosition;
    playerHealth = playerHealthMax;
    isAlive  = true;
}

This can be useful later for transitions otherwise you may have code X triggering code Y to transition multiple times instead of just once and cause glitches. Just thought I’d show you coroutines they can be very useful.

Everyone will suggest the new Animator feature, but I find it overly complicated for simple animations. If you want to do something as simple as make animations in blender, import the model, and do animation.Play(“Walk”) in your script, then use Legacy Animation over Animator.