Unity VR : Climbing

One of the most compelling experience I have had in VR was when I walked up to a ladder in Half Life Alyx. When grabbing onto a bar, I thought it would just teleport me to the top. Instead, I began to float up in the air as I climbed the ladder. I felt the inertia in my body and a mild amount of motion sickness. It was amazing.

With that in mind, I wanted try and develop a climbing system. There are many approaches to developing this feature and I hope you find my approach helpful in you VR development journey. Let’s learn how to climb!

To follow along, you can find the GithubProject can be found here.

1 : Scene Review

Opening up the Scene, we will be greeted with our table with a few Grab Interactables and a ladder. Nothing else left to this Scene! Now we just need to get our ladder ready for climbing.

2 : Grabbing Ladder Handles

To save time, the bars are all from the same prefab. When we make changes to one of them, we can then extend those changes to all the other handles. Now to make it so we can grab these handles, we need to do the following.

  •  Select one of the Handles
    • Add Component Grab Interactable.
      • Track Position = False
      • Track Rotation = False
    • On Rigidbody
      • isKinematic = True
    • Tag = Climmable 
Let’s see what we’ve done here. We’ve added a Grab Interactable so our hands can interact with the handle. We’ve also turned off the tracking for position and rotation so the bar doesn’t get ripped off the ladder when we grab it. The Rigidbody is set to Kinematic to prevent all the handles from falling to the ground due to gravity. Finally, we set the tag to Climmable. We will use this tag later to help us differentiate between Grab Interactables that we can climb and ones we cannot.
 
Now that we can grab onto the handles, let’s set up our climbing system.

3 : XR Direct Climb Interactor

First we need to have a way for our XR Direct Interactors to detect and signal that they have encountered a climbable object. Instead of have XR Direct Interactors, we are going to replace them with the XR Direct Climb Interactor. It will have, all the functionality of the XR Direct Interactors, but it will also signal to our Climb System when it encounters a climbable object. 

We can also notice that it won’t just signal to our Climb System, but will also send in the name of the controller. We’ll need this to determine which controller is active. 

Analyzing the code, we start off with two public static events Actions that will be used to signal when a controller has encountered a climbable object and when it has released a climbable object. We also override the OnSelectEntered/Exited functions from the XR Direct Interactor. Inside these overalls, we still call the base functions, but add checks for climbable objects and signal when one is encountered.

Now that our hands can fire off whenever we grab a object tagged with climbable, lets get our Climb Provider working.

4 : Climb Provider

First let’s add the Climb Provider to our Locomotion System.

  • Select Locomotion System
    • Add Component ClimbProvider script
    • Drag XR Origin into empty slot for the Character Controller.

We can see it’s asking for our Character Controller located on the XR Origin and a reference to the velocity from our left and right controllers. The only problem is that we don’t currently have that reading from our Input Mapping. Let’s fix that now.

  •  Open up the XRI Default Input Actions
    •  Click on XRI LeftHand
    • Select the ‘+’ symbol for Actions
    • Name the new action Velocity
    • Select the new velocity action
      • Under Action Properties
        • Change the Action Type to Value
        • Change Control Type to Vector3
    • Expand Velocity and select <No Binding>
      •  Under Binding set the path to XR Controller -> Oculus Touch Controller (Note: You should choose the specific controller type here that you are using) -> Oculus Touch Controller (Left Hand) -> velocity.
  • Repeat the above steps, but for the XRI Right Hand
  • Select the Locomotion System
    • Click the Check Box for Use Reference under Velocity Right. 
    • Locate and add the newly created Input Action XRI RighHand/Velocity
    • Repeat the two steps above for the Left Hand Velocity.
 

With this change, we’ll be able to track the changes in velocity in our controllers. This will be incredibly useful for climbing. We can apply velocity to our Character Controller to move us. Let’s open up the script and see what we’re working with.

We start off with two public static event Actions. These will be used to signal to any movement system that needs to know when climbing is occurring. This could be useful if you wanted to turn off Continuous Movement or Teleportation when climbing. We’ve added these here to help add gravity to the character controller once climbing is inactive. We also have all our other variables that we will be using in other functions to make our climbing functional.

The Start and OnDestroy functions are subscribing to the ClimbHandActivated/Deactivated events from our XRDirectClimbInteractors. 

Next we have the HandActivated function that takes a string. It’s already subscribed to the event that is called from the XRDirectClimbInteractor and will be called anytime that event triggers. It will set either the left or right as active depending on which controller is passed in and it will turn the other on off. For this climb system I though it made the most sense to have the most recently activated hand as the climbing hand.

For HandDeactivate, we do a similar thing as HandActivated, but we’re also making sure that the current hand that is deactivating is also the most recent hand. 

In FixedUpdate, we check if there is an active climbing hand and call the Climb function if there is.

Last is the Climb function. Inside it will check if the Left Controller is active and either give the velocity of the Left Controller or Right Controller based off that result. We then use the Character Controller Move function to move our character controller. We use the rotation from the character controller since we want to retain that and multiply it by the negative velocity of the controller. The reason we use the negative velocity is because we want to give the feeling of pushing ourselves up. So if the controller is moving down, we move up! 

And with that, we have a functioning Climb Provider, but there is still one last thing to go over.

5 : Character Controller Gravity

One problem with using a Character Controller is the fact that it doesn’t automatically apply gravity. Now that we are lifting ourselves into the air, that’s a bit of a problem.

  • Select the XR Origin and Add Component script called CharacterControllerGravity.
Opening up the script, let’s see what we have.

Starting off we have a public function to adjust gravity with, a reference to our Character Controller, and a bool to keep track of when we are climbing. We subscribe to the ClimbActive/InActive events from the Climb Provider so we know when to turn gravity on and off. If we left gravity on while climbing, it would prevent the ClimbProvider from working. In the Update  function, we check if we our on the ground and that we are not climbing, if both those are true, we then use the SimpleMove function. Notice that we give it an empty Vector3. According to the Unity Documentation, SimpleMove will actually apply physics to a Character Controller so we don’t even need to give it any Vector information. Unity already has physics build into the function!

With that, we’re all done!

6 : Conclusion

We’ve come a long way with this one. I will make no claims that this is the best climbing system for VR, but it does function the way we need it to!

Thanks for stopping by and happy climbing!