How To Implement Walking And Jumping In Unity SteamVR

This code and an example scene are included in the VR instincts plugin on github.

I did not expect it but my previous walking tutorial has become one of the most popular articles I’ve ever written. For me it’s strange because Its a very short undetailed tutorial. As we all know, us programmers love improving our code, and I have improved my code quite a bit. So the logical decision was to make another article that is a lot better than the last one, with that, let’s get started. I’m going to refer to my previous code quite a bit so if you want to read my previous article, you can do so here. The code still works and is great if you just want to walk around, so take a look.

This is what the final code looks like

Ok, so the changes I’ve made from the last article are pretty significant. First, I added the ability to jump. It was easy enough but brought to light a problem with the code, If you notice in the above video when I jump while moving towards the main pillar cube and bump into it, I fall down. Simple right? False. In the original code (with jump added) I would stick to walls when moving towards them, this also stopped me from sliding along walls I was pressing against. I also changed the code from the movement itself so that it always applies the perfect amount of force to move in the direction you want, instead of using an if clause. That’s about it except for a few other changes like changing the class name to Player instead of Movement. So let’s get to the code:

using UnityEngine;
using Valve.VR;

public class Player : MonoBehaviour
{
    private Vector2 trackpad;
    private Vector3 moveDirection;
    private int GroundCount;
    private CapsuleCollider CapCollider;

    public SteamVR_Input_Sources MovementHand;//Set Hand To Get Input From
    public SteamVR_Action_Vector2 TrackpadAction;
    public SteamVR_Action_Boolean JumpAction;
    public float jumpHeight;
    public float MovementSpeed;
    public float Deadzone;//the Deadzone of the trackpad. used to prevent unwanted walking.
    public GameObject Head;
    public GameObject AxisHand;//Hand Controller GameObject
    public PhysicMaterial NoFrictionMaterial;
    public PhysicMaterial FrictionMaterial;
    private void Start()
    {
        CapCollider = GetComponent<CapsuleCollider>();
    }

    void Update()
    {
        updateInput();
        updateCollider();
        moveDirection = Quaternion.AngleAxis(Angle(trackpad) + AxisHand.transform.localRotation.eulerAngles.y, Vector3.up) * Vector3.forward;//get the angle of the touch and correct it for the rotation of the controller
        Rigidbody RBody = GetComponent<Rigidbody>();
        Vector3 velocity = new Vector3(0,0,0);
        if (trackpad.magnitude > Deadzone)
        {//make sure the touch isn't in the deadzone and we aren't going to fast.
            CapCollider.material = NoFrictionMaterial;
            velocity = moveDirection;
            if (JumpAction.GetStateDown(MovementHand) && GroundCount > 0)
            {
                float jumpSpeed = Mathf.Sqrt(2 * jumpHeight * 9.81f);
                RBody.AddForce(0, jumpSpeed, 0, ForceMode.VelocityChange);
            }
            RBody.AddForce(velocity.x*MovementSpeed - RBody.velocity.x, 0, velocity.z*MovementSpeed - RBody.velocity.z, ForceMode.VelocityChange);

            Debug.Log("Velocity" + velocity);
            Debug.Log("Movement Direction:" + moveDirection);
        }
        else if(GroundCount > 0)
        {
            CapCollider.material = FrictionMaterial;
        }
    }

    public static float Angle(Vector2 p_vector2)
    {
        if (p_vector2.x < 0)
        {
            return 360 - (Mathf.Atan2(p_vector2.x, p_vector2.y) * Mathf.Rad2Deg * -1);
        }
        else
        {
            return Mathf.Atan2(p_vector2.x, p_vector2.y) * Mathf.Rad2Deg;
        }
    }

    private void updateCollider()
    {
        CapCollider.height = Head.transform.localPosition.y;
        CapCollider.center = new Vector3(Head.transform.localPosition.x, Head.transform.localPosition.y / 2, Head.transform.localPosition.z);
    }

    private void updateInput()
    {
        trackpad = TrackpadAction.GetAxis(MovementHand);
    }

    private void OnCollisionEnter(Collision collision)
    {
        GroundCount++;
    }
    private void OnCollisionExit(Collision collision)
    {
        GroundCount--;
    }
}

There’s a lot, I know but we really only need to cover the update function with token explanations for all the extra functions. First, we have all of our private variables:

    public SteamVR_Input_Sources MovementHand;//Set Hand To Get Input From
    public SteamVR_Action_Vector2 TrackpadAction;//action for getting trackpad input
    public SteamVR_Action_Boolean JumpAction;//get jump action
    public float jumpHeight;//set height in meters that we can jump
    public float MovementSpeed;//set speed in m/sec we can walk
    public float Deadzone;//the Deadzone of the trackpad. used to prevent unwanted walking.
    public GameObject Head;// used for the position of the head
    public GameObject AxisHand;//Hand Controller GameObject
    public PhysicMaterial NoFrictionMaterial;//I'll explain these later
    public PhysicMaterial FrictionMaterial;

Then we have our update function:

void Update()
    {
        updateInput();                                     //#1
        updateCollider();
        moveDirection = Quaternion.AngleAxis(Angle(trackpad) + AxisHand.transform.localRotation.eulerAngles.y, Vector3.up) * Vector3.forward;//get the angle of the touch and correct it for the rotation of the controller
        Rigidbody RBody = GetComponent<Rigidbody>();
        if (trackpad.magnitude > Deadzone)                 //#2
        {//make sure the stick isn't in the deadzone
            CapCollider.material = NoFrictionMaterial;
            if (JumpAction.GetStateDown(MovementHand) && GroundCount > 0) 
                                                            //#3
            {
                float jumpSpeed = Mathf.Sqrt(2 * jumpHeight * 9.81f);
                RBody.AddForce(0, jumpSpeed, 0, ForceMode.VelocityChange);
            }
            RBody.AddForce(moveDirection.x*MovementSpeed - RBody.velocity.x, 0, moveDirection.z*MovementSpeed - RBody.velocity.z, ForceMode.VelocityChange);                                   //#4
        }
        else if(GroundCount > 0)                             //#5
        {
            CapCollider.material = FrictionMaterial;
        }
    }

#1) We first update the position of our collider and the input from the trackpad which we then use to find an angle that we then offset by the rotation of the controller and multiply by a Vector3 to get our direction of movement as a vector, we also set up a few variables.

#2) see we are touching somewhere outside the dead zone and then set our colliders material to a physics material with no friction.

#3) we check if we want to jump and if we do, calculate the perfect amount of force to jump to the specified height and apply it.

#4) Apply movement force

#5) If we aren’t moving set colliders material to on set with high friction.

That’s about it for the code but I want to explain physics materials a bit better, to create them just go to the unity file browser and right-click and create a Physics material. Now when you click on it you should see something like this:

Simply put Dynamic friction is how much force you need to keep things moving and Static is how much you need to start things moving. What we need to do is to make two materials. One with lots of friction, one with none. You can see the lots of friction one above, to make the other just set all the floats to 0 and the Combine settings to Minimum. From there set up you scene components as described in the last tutorial, fill all the variables on the script and you are walking!

Liked it? Take a second to support WireWhiz on Patreon!
Become a patron at Patreon!

8 comments

  1. Hey, I followed your tutorial from your last article to get the touchpad walking functionality, but it does not work. I have your code copied, and I took the time to understand it. I attached the script to the camera rig, and I believed I correctly changed the Steam VR input. I created a new action: Movement Axis like you suggested, and I added the touchpad position to my Vive controller. That’s it. I did not change the capsule collider size however, could that be my problem? Thanks

    1. Sorry! I did change the capsule collider size, but I just didn’t know what changing the size did.

      1. Also, I realized I forgot to press save bindings when I changed my position to the “MovementAxis” variable. Could that be it? I am not sure because when I exited out the tab, and rechecked if I changed it, it was still there.

        1. Haha, it was saving the binding controls. Thank you for the tutorial! I understand it so much more now!

  2. Hopefully someone is still watching this thread. I don’t understand what GameObject I am supposed to assign to the “Head” attribute on the script. Can someone clarify?

    1. The head attribute should be linked to an object that is being positioned by your HMD, Most of the time this is the camera component of your SteamVR CameraRig.

  3. So, I have tried it now for some hours, but I still have some problems. Hopefully someone can help me, since im trying to make a walking script for a month now. First, I can’t go back or forward, the trackpad is only tracking, if I move my thump to the left or right. I don’t know why, but i can’t jump. I created a new Action “jump” like the “movementaxis” and binded it to the trackpad click. Also, it doesn’t matter in which direction I am looking. The right or left movement doesn’t get affected, so even if I turn my head 90° to the right, the movement is the same as before, which would make me moving forward then, when my thump is at the right side of the pad.

    I did everything you mentioned I think.
    Movement hand = Right Hand
    Head = Camera
    Axis Hand =Controller right

    1. okay, I finally found the error. I did constrain not only the rotation, but also the position for all axis excpet one. Now everything works fine. Thanks for the tutorial!

Comments are closed.