How to create a Smooth locomotion system for your VR project using UE4.
This tutorial was made possible by the very talented Corysia over in the GDXR Discord channel, thank you for allowing me to turn your work into a tutorial. I recommend heading over to Corysia's Youtube channel to keep up to date with their current projects.
In this tutorial, we will be setting up a smooth locomotion system similar to games like Half-Life Alyx, Pavlov VR and many, many more. This tutorial will allow your player to freely move around the scene, climb stairs, crouch beneath objects, fall of ledges, snap rotate in place and even grab objects without flying away.
One of the most important things in VR is a good movement system and this can often be extremely difficult to achieve. So in this blog post, I'm going to show how you can create your own smooth locomotion system using Unreal Engines default template. Giving us the ability to toggle between teleportation and the ability to smoothly move around the scene. While also including some extra ability such as Jumping, crouching, sprinting and snap turning. I will also show you how to set up custom collisions to stop the player from flying away when they interact with objects within the scene. If you would like access to download the project files from this tutorial you can get them from my Patreon: https://www.patreon.com/GameDevXR
Before we get started you will need to open up the default VR Template (or your project) as were going to use the existing MotionControllerPawn (your VR pawn) and I will be working on the Motion Controller map for ease of use.
1. Setting up our Custom Input's
The default template within unreal already has three of our inputs already created you can find these by going to Edit > Project settings > Input.
- MotionControllerThumbLeft_Y = Move Left Right - MotionControllerThumbLeft_X = Move Forward Back
- MotionControllerThumbRight_X = Snap Rotate Left Right
Our Left controls will be used to make us "Walk around" ThumbLeft_X will be used to move us left and right while ThumbLeft_Y will be used to move us forward and back. ThumbRight_X will be used to snap our rotation left and right.
These inputs are located under the Axis Mapping tab because they require inputs going from -1 to 1 defaulting at 0. but in our case, we're going to set up two new inputs so we can jump and sprint. And we're going to do this by adding them to our Action Mappings section as these inputs will be acting like bool's On or Off.
To do this hit the plus to the right of action mappings twice to create two new inputs. Name the first Jump and the Second Sprint. Now we can add our controller inputs.
For this tutorial ill be using the Oculus Touch inputs but you can always add more for different platforms or only add the inputs for the device your using.
For Jumping I'm going to use the B key (Oculus Touch (R) B Pressed) on my right controller and for sprinting I'm going to click the left stick (Oculus Touch (L) Thumbstick). Your Inputs should now look something like this.
That's it for our inputs and we can now close our project settings for now.
2. Setting up our Motion Controller Pawn.
Now we have our inputs created we can open up our MotionControllerPawn.
By default, our MotionControllerPawn has the Parent class Pawn. We need to change this to give our player the Parent Class of Character. as this will give our player a Capsule component and the ability to have gravity affect us.
We can do this by going to Class Settings located at the top of our window and on the right changing our Parent Class to Character.
You should now see our Components panel change to include a capsule component, arrow and mesh.
With this complete, we now need to select our MotionControllerPawn(Self) from the components list and make sure Use Controller Rotation Yaw is Disabled. As seen in the image below. (yellow arrow on the right).
With our class changed we can switch over to the Viewport tab so we can move our VROrigin's position. You will need to select the VROrigin component and move it down on the Z-Axis to -90 (The bottom of the Capsule).
If you're not using a game mode and the player starts to spawn your player you will need to reposition the MotionControllerPawn in the scene so the camera is on the floor.
3. Setting up or can teleport Bool.
In our MotionControllerPawn Event graph, we can start setting up our ability to toggle between teleportation and our smooth locomotion. To do this Start by creating a Bool Variable called bCanTelleport and making sure it's set to false. We then need to find our InputAction TeleportLeft and Teleport Right. This is where we are going to add some branches. We will need one branch after each pressed and released inputs. you can add a branch by holding B on the keyboard as you left click in the Event Graph. It should look like this.
You can now connect the bCanTelleport bool we created earlier to each of the branch nodes we created.
Now since our bool is set to false we won't be able to teleport. We can now work on setting up our Thumbstick movement.
4. Setting Up Our Thumbstick Movement.
To set up our thumbstick movement inputs we need to find some open space in our motion event graph. I like doing this to the right of our GrabLeft and Grab Right Events. We then need to right-click and search for our MotionControllerThumbLeft_X Input Event and our MotionControllerThumbLeft_Y event we have in our project settings.
Make sure to have both events in the graph. But for now, we're going to work from the ThumbLeft_X event as we can copy what we right for the forward movement with the exception of a single node.
for this section, we need to create two float variables. The first called "Movement Deadzone" With a value of "0.2" and the second "Movement Scaler" With a value of 0.6 If you want to keep your variables organised you can add a new Category by typing in SmoothLocomotion where it says Default on the right. This will let us place all our smooth locomotion variable's here as we will be creating quite a few.
With our Variables organised we now need to drag our Axis Value pin on our Input Axis and create an ABS (Absolute) node. This saves us having to do a greater than and less than branch to see if were going left or right. We can just do a greater than and check if the axis value is greater than our Movement Deadzone "0.2" and finish it by creating a branch and connecting it to the branches condition.
Now from the True output of our Branch we can drag off and create an Add Movement Input node, this is what will let us move in the direction of our Axis Value. We will now need to add in our Movement Scaler Variable and a float Multiplier. to connect to our Movement Input Scaler Value.
The next thing we need to do is bring in a reference to our Camera Component so we can "Get Right Vector" and use this to set our World Direction as our camera will control the reference to the direction we're moving in.
Your code so far should look like this. When you move the thumbstick in the X-axis we check to see if the value is < or > our 0.2 deadzone if it is we can move. we then multiply the Axis Value by our movement scaler to determine the speed we move at. You can change this value to move slower or quicker depending on what you're after. We then get the camera to determine our right and left to move in those directions.
Now to move forward and back we just need to copy everything except our even and connect it to our MotionControllerThumbLeft_Y input.
We will also need to replace our "Get Right Vector" with the "Get Forward Vector" node.
I also recommend commenting your code here and calling it Thumbstick movement
Now if we jump into the level and test in VR we can move around using the left thumbstick.
5. fixing/Setting Up Room Scale movement
One of our issues so far is that our current setup doesn't support the room-scale movement, You can see this by selecting Your capsule component and setting Hidden in-game to false so you can see our player capsule. You'll see that if you move in the real world our camera moves away from the capsule. This itself isn't very good for moving around. as we could make sure our capsule is on the edge of a ledge and if we move over it in the real world then we wouldn't fall off it. But that's what we want.
To fix this we first need to start by creating a "Custom Event" which will be named "Update Roomscale Movement" Which we will be updating with our event tick.
Finding our event tick we can use the already created sequence to "Update our Room Scale Movement"
Moving back to our custom event we can drag from our Exec and create two nodes, the first being "Add actor World Offset" and the second "Add World Offset (VROrigin)."
Now we need to bring in a reference to our "Camera" and our "Capsule Component" we then need to get the world location for both and subtract them from each other.
You will also need to split the output vector pin. And we also need to split the Delta Location pin on our AddActorWorldOffset. With those pins split you can connect the X and Y values from the minus node to the X and Y values of the Delta Location.
We now need to Negate our X and Y vectors for our VROrigin's add world offset. The Negate Vector node essentially acts as a multiply -1 so it inverts our vectors. essentially setting our camera to our capsule and our capsule to our camera.
You will need to connect our X and Y values to our negate node leaving Z as zero. You can then connect the negate vector to our Add World Offset Delta Location node
It's difficult to see in the gif but if you jump in and move around in the real world the capsule will now update its position to match.
6. Setting up Jump and Sprint.
Setting up jump and sprint is probably the easiest part of this process this is because UNreal already has a node we can use to control our jumping. The first thing we need to do is find some free space. In our MotionControllerPawn. Here we are going to add both the custom input actions we created earlier, Jump and Sprint.
To set up our jump all we need to do is drag from our InputAction Jump Exec pin and search Jump. We can now comment on our Jump code as that's it.
Now we can set up our sprinting. if you remember earlier in the tutorial we created a Float Variable called Movement Scaler. We will be changing this value to increase our player's speed. When working with this it's best to think of it as a speed multiplier. Our default speed is 0.6 so we're going to increase this on InputAction Sprint pressed to a value of 1 and on release, we will set it back to our default of 0.6.
We can do this by dragging in our Movement Scaler Variable and setting it. We will need two.
Now we have our Jump and Sprint set up we can jump into the headset and try it out.
7. Adding The Ability to crouch
In this section were going to start by creating two custom events, the first will be called "Initialize Capsule Height" and the second will be called "Set Capsule Height"
For the first part, we're going to concentrate on our Initialise Capsule Height as we will need this so we can reference it in our Set Capsule Height.
Now we need to create a new float Variable named "Previous Capsule Half Height". This will be set on Begin Play and referenced in our Set Capsule Height code later on. (Don't forget to set the category for organisation)
you will need to connect our Initialise capsule height Exec pin to our Set Previous Capsule Half Height. And then drag in a reference to our Capsule Component. We will use this to get the capsules Half heigh and use that to set our Previous Capsule Half Height variable.
We can now move on to changing our Capsule Height which will allow us to physically crouch to move under objects.
we need to bring in a reference to our Capsule Component so we can use it to Get the Scaled Capsule Radius and Set the Capsule Size node. Make sure to drag from the capsule Component reference to get them.
Before adding our math were going to get a reference to our VROrigin and use it to AddRelativeLocation you will want to connect it to the Exec pin of our Set Capsule Size
From our AddRelativeLocation node, we can drag in a reference to our Prevoisue Capsul;e Half Height and we will need a set as we're going to add some math to it. So far your code should look like this.
What were going to do here is start by using the Get Orientation and Position node which lets us access the HMD (Head Mounted Displays) information both rotation and location. We will then divide it by 2 so we get the halfway position and we are going to add 10 to compensate for the top of our head. With the Get Orientation and Position node, you will need to split the Position pin as we only need the Z position.
We also need to connect our + 10 node to our Set Previous Capsule Half Height. and we need to get our Previous Capsule Half Height as a reference so we can subtract our Z position from it. Your code should now look like this.
The last thing we need to do for our crouch is set our Custom events we created to fire. Our Initialise Capsule Height must be updated from Event Begin Play and our Set Capsule Height needs to be updated with On Tick.
Now if we jump into our headset and crouch with the capsule visible we should now see it update letting us crouch.
8. adding Snap rotation.
Snap rotation is by far the most complex part of the system. The first thing we need to do is create five variables one boolean and four floats. These will be.
Boolean named "bSmooth Rotation" = Flase
Float named "Turn Deadzone" = 0
Float named "Smooth Rotation Speed" = 1
Float named "Snap Rotation Degrees" = 15
Float named "Rotation Angle" = 0
With our variables created we can start by creating our input axis. if you remember back to our input setup we already have our thumbstick controls created. For our snap rotation were going to use our InputAxis MotioncontrollerThumbRight_X followed by a delay node = 0.5 and a branch.