Adding smooth locomotion to your UE5 VR Project.
This tutorial was made possible by my Patreons. If you'd like to support more content like this please consider subscribing to the GDXR Youtube tutorial or by becoming a Patreon: https://www.patreon.com/GameDevXR
In this tutorial, I'm going to show you how to set up your own smooth locomotion system similar to that found within Half-Life Alyx and other similar VR Games.
If you followed along to my previous Half-Life Alyx movement system and tried implementing it into UE5 you might find it no longer works. This is due to the new template working quite differently from the UE4 template meaning we have to do some extra steps to make it work. If you would like to download the files used to create this project then you can do so by becoming a Patreon.
1. Setting Up Our Custom Inputs.
The first thing we need to do before setting up our player character is adding in our custom inputs. To do this we will first need to head over to Project Settings > Input. If this is your first time in here then you will notice that we have quite a few inputs already included in the project. What we need to do is add in our Sprint Input and Our Jump button.
Were going to do this by adding them to our Action Mappings section at the top here.
Press the plus next to action mappings twice to add two new actions. Were going to name the first one Sprint and the second Jump. Now we can add the inputs we want to use for our template. In my case, I'm going to add inputs for a couple of devices but you can add the inputs only required for the platform your using.
2. Setting up a custom collision preset
Staying in our project settings were going to head over to the collision tab and create our own custom Preset. This will come in handy later on when we are trying to grab our interactable objects.
At the bottom of the list, there is a heading called "PRESET" you will need to extend this and you will then be shown a list of current presets within the engine. Were going to use it to create our own.
And to do this you need to select New on the right-hand side of the window. In the New Profile box which appears name it VRInteractable.
We then need to modify some of the current settings.
CollisionEnabled = Collision Enabled (Query and Physics).
Object Type = PhysicsBody
Description = Add To Any VR Interactable To Stop It Colliding With The Player
We then need to change the trace channel for Pawn to be Overlap. So your settings should now look like this.
We can now press Accept and close our project settings as were done with it. We can now modify our VRPawn and start coding our movement.
3. Setting up our VR Pawn and Components.
The first thing we need to do is open up our VR Pawn Blueprint.
By default, our VR Pawn is of the parent class pawn. We need to convert it to a character Class.
We can do this by selecting Class Settings at the top of our event graph and changing our Parent Class to Character.
Make sure to compile to apply our new changes.
If you look at the components panel on the left you will see it's changed and we now have a player capsule and mesh component. But before we move on we need to add a new component as a child of the capsule.
Make sure to name it VROrigin.
Now we need to select: MotionControllerLeft
And were going to make these a child of our VROrigin (SceneComponent).
It should look like this.
After Attaching everything to our VROrigin we need to select it and change its Z location to -90
Now We have updated our VRPawn we need to quickly jump into the scene and move our player start position. so it's not intersecting with the floor as this will stop us from spawning.
We can now jump back to our VRPawn Event graph and start adding our Snap Rotation.
4. Setting Up Snap rotation
The first thing we're going to do here is to delete the existing Snap Turn code already included in the VRPawn as it doesn't work correctly for room-scale so we will create our own. We also need to delete the SnapTurn Function so we don't have spare code lying around our project.
Now we've removed the existing code we have some free space to work in.
Were going to start by adding two of our existing InputAxis Mappings. MovementAxisRight_X and MovementAxisLeft_X. We're doing this because we're going to give the option of choosing between teleportation and smooth locomotion later on in the tutorial.
Were also going to add a branch to each Axis and create a New Boolean Variable called UseSmoothLocomotion. This will be used to switch between teleportation and smooth locomotion later.
Now we have our branches we are going to create a new float variable called InputAxisValue which we will use to get the thumbstick value. You can do this by right-clicking either of the Axis Value pins on our events and promoting to a variable.
Don't make two as we will be using the same one for both events except we will connect our set InputAxisValue from our Right_X Branch to the True channel and our InputAxisValue from our Left_X Branch to the False channel. So it looks like so.
We now need to add another branch and connect both our InputAxisValues to its input pin.
This is where our code gets a little bit more complex. We need to first get our Input axis value and create two separate > greater than nodes coming from it. But we need to use an Absolute between one of them like so.
We can now add or greater than node, connected to the absolute condition input of our Branch node. And create another Branch node for our second Greater than node. We then need to create a new float variable called turn Deadzone. Make sure to set the TurnDeadzone Float value to 0.5
The next thing we need to do is create two new float variables. The first-named SnapRotationAngle and the Second one called SnapRotationValue. We only need to set our SnapRotationAngle to a rotation angle we like. I find a value of 30 to be quite nice. And we can keep SnapRotationValue set to 0.
We need to bring in One get Snap Rotation Angle (value of 30) and two Set Snap Rotation Values one connected to True and the Second connected to False. We will also connect our SnapRotationAngle to or Set SnapRotationValue from the false channel but we need to invert it for it to work for the SnapRotationValue Connected to the False Pin. And We can invert it by using a Multiply float and using a value of -1. Your code should look like this.
We can now connect both SnapRotationValues to a DoOnceNode followed by a sequence. from the Sequence, Then 1 pin we will add a delay node with a value of 0.2. and connect it back to the DoOnce Rest node.
Now from our, Then 0 Pin we can create a SetActorRotation Node. We will also need a Get Actor Rotation Function. Which we will then add our SnapRotationValue To the yaw Output pin.
Once you have added the SetActorRotation and hooked it all together it should look like this.
we can now comment on our section of code by selecting everything and pressing C on the keyboard to add a comment section.
5. Setting Up Room Scale To Make Our Capsule follow our VRPawn Camera.
In this section, I want to acknowledge Victor Lerp (Creator of the UE5 template) over at Epic did a great job with this template and not using the Event Tick node within the VRPawn. If your not sure why this is impressive it's due to performance. the code in the player is now fired only when it's needed rather than on tick which results in the player being updated every frame of your game. Basically, if your game is set to 90fps (Standard VR) then that's 90 times each piece of code is fired if running from the event tick. Even if it's not being used. As you can imagine it becomes quite expensive on performance.
So we're going to keep with this improvement and use a timer to control when we update the capsule as this is something we don't need to do on tick but rather in milliseconds. And this lets us manually control the speed at which we update our capsule position.
So to make our player work with the room-scale movement were going to start by finding some free space in our event graph and creating a custom event called Update Roomscale Movement. I'm actually going to do this to the right of our snap rotation code we added.
Now we have our custom event we are going to drag of our execution pin and search for Add Actor World Offset and from that, we're going to drag off and search for Add world offset (VROrigin).
We now need to get references to both our Camera and Capsule components. We can do this by selecting both in our content browser and dragging them into our event grap.
After adding both of them we need to drag each one and Get World Location functions which we will then subtract from each other like so. (Make sure the camera node is above the capsule node). And we also need to split the output pin from our subtract node.
We can now connect the X and Y from our subtract to the Delta Location X and Y of our Add Actor World Offset.
Now we need to create a Negate Vector node and we will split the left in to access the individual axis floats. We can now connect our X and Y values from the subtract node to the A X and AY Float values of the negate vector.
Now we've done this we can connect the Return Vector to the Delta location of our Add World Offset. Like so.
Before I Forget we also need to set Teleport on our Add actor world offset to true and do the same for our add world offset
If you've worked with blueprints in the past you would have noticed that we aren't firing our Update Roomscale Movement Event. This is what we're going to do now. And to do this we need to find our Event Begin play node located at the top of the Event graph in the original template. It should look like this.
Now if you remember back I explained that we won't be using event tick but we do need a timer to repeatedly fire our event so it updates periodically. and to do this was going to use a function called Set Timer by Function Name. This lets us say "Get this event and fire it every X seconds, and also make it loop). The lets us stagger when our code fires considerably improving our performance.
Now we can copy and paste the name of our Custom event (Update Roomscale Movement) into the Function Name variable. And Set our Time this can be anything you want but it's important to note that the lower the value the faster your code will update and it's not necessary to update this quickly.
I found a value of 0.3 worked extremely we and spaced out the events firing quite nicely. but use a value that you think works best for your game. If you need it to be extremely accurate then a value of 0.15 should work just as well. We all need to make sure Looping is ticked.
If you want to test if your player capsule is now updating you can set the capsule component Hidden in-game to false and jump into the scene. If you look at the ground you should now see the capsule moves with the headsets as we need it to.
Now our capsule follows our player we can add a comment around our code.
Now we have our capsule following our player we can now start working on changing the height of the capsule to allow our player to crouch beneath objects and actors.
6. Dynamically setting the height of our Player capsule.
Again we need to find some spare room in our event graph and create a custom function called Update Capsule Height.
Before working from the Update Capsule Height event we need to head to our Event to begin play code as we need to add some code there first. We need to start by getting a reference to our Capsule Component and then dragging off from it and getting our Capsule Half Height.
Now we have a reference to our Capsule Half-height we are going to right-click the pin and promote to variable and then connect it to our event begin play.
While we're here we are going to copy and paste our Set Timer by Function Name and use it to update our Update Capsule Height event. But we are going to change the Time to 0.35 so we aren't firing the timer at the same time as our other one.
Your event begin play should now look like this. I also added comments so I know what each part does.
We can now go back to our Update Capsule height event we created.
We are going to start by creating a set capsule size function and making sure it has our capsule component.
From our Capsule Component, we are going to drag off and search for getting Scaled Capsule Radius. And attach it to our in Radius.
We will now set up our In Half-height and to do that we need to first get an Orientation and Position function where we will split the Device Position pin. So it looks like this.
From our Z input, we are going to divide our z position by a value of 2. To get the divide node you will need to use / in the search box.
From our divide node, we now need to add 10 to the value this adds a litt