In this part of the tutorial, we will get the components set up for the Pawn pawn to have head and motion controller tracking. This is the second part of the tutorial. If you haven’t read the first part please read through that. It covers the basics such as creating the C++ class for the pawn and setting up the blueprint in the game mode. This part will not go over those basics. If you are already familiar with those concepts, you can go ahead with this tutorial.
Configuring the Prerequisites:
First, we will want to include the header files required by SteamVR for tracking. Let’s start by adding the following into the player pawn’s header file:
#include "HeadMountedDisplay.h" #include "SteamVRChaperoneComponent.h" #include "MotionControllerComponent.h" #include "XRMotionControllerBase.h"
Note that the include statements need to be added before the .generated.h include statement.
We will also need to add the dependencies for the project to use the header files that we just added. To do this, we need to edit the [PROJECTNAME].build.cs file and add “HeadMountedDisplay” and “SteamVR” to the private dependencies section. Since the tutorial project does not have other private dependencies at this point, the line looks as follows:
PrivateDependencyModuleNames.AddRange(new string[] { "HeadMountedDisplay", "SteamVR" });
Let’s compile the code at this point either by hitting the “Compile” button in the UE4 editor or by compiling the visual studio project. There should be no errors at this point.
Creating the Pawn Components:
If we open the newly created pawn, we can see that it has no components. There should just be a single “DefaultSceneRoot” component. Let’s start by adding the components that will be required for the VR pawn. This is where we set up the camera rig with the hand controllers and movement related components.
First we start by writing a new function that will handle all the component creation. Lets call this function “CreateComponents“. This is a private function that is declared in the in the pawn’s header file and will be implemented in the cpp file. Let’s add in the some simple components first. Here is the first implementation of the function:
void AcVRPlayerPawn::CreateComponents() { //The default root component for the pawn. USceneComponent* rootComponent = CreateDefaultSubobject(TEXT("SceneRoot")); RootComponent = rootComponent; //Component that will be used for the movement based on input UFloatingPawnMovement* compFloatingMovement = CreateDefaultSubobject(TEXT("FloatingMovementComp")); //Chaperone component that is required for Steam VR plugin USteamVRChaperoneComponent* chaperone = CreateDefaultSubobject(TEXT("SteamVR Chaperone")); }
We are now ready to call the function in the pawn’s constructor. Once the function call has been added to the constructor, we can compile the code and open the pawn blueprint. We should be able to see the pawn’s newly created components.
Since we have opened the blueprint class that derives from the cpp pawn class (see the previous post), we find that the components that we just created in the base class as shown as “(inherited)”.
Next, we will need to add the component for the HMD. This will be the camera that gets the motion from the HMD.
//Create a scene component that will act as the parent for the camera. This might come in handy later. USceneComponent* compVRCameraRoot = CreateDefaultSubobject(TEXT("VR Camera Root")); compVRCameraRoot->SetupAttachment(rootComponent); compVRCameraRoot->SetRelativeLocationAndRotation(FVector::ZeroVector,FQuatIdentity); compVRCameraRoot->SetRelativeScale3D(FVector::OneVector); //Create a camera component and attach this to the camera root. UCameraComponent* compVRCamera = CreateDefaultSubobject(TEXT("HMD Camera")); compVRCamera->SetupAttachment(compVRCameraRoot); compVRCamera->SetRelativeLocationAndRotation(FVector::ZeroVector, FQuat::Identity); compVRCamera->SetRelativeScale3D(FVector::OneVector);
We are now making some progress. To actually see the progress in action we will need to create and track the motion controllers. We will do this now.
Let’s start by writing a new function that will add an inidividual hand. We will need a private function “CreateHandController” in the pawn class. The signature of the function will be as follows:
void CreateHandController(USceneComponent* a_compParent, FName a_strDisplayName, FName a_nameHandType);
The implementation of the function in the cpp file is as follows:
void AcVRPlayerPawn::CreateHandController(USceneComponent* a_compParent, FName a_strDisplayName, FName a_nameHandType) { UMotionControllerComponent* compMotionController = CreateDefaultSubobject(a_strDisplayName); compMotionController->SetRelativeLocationAndRotation(FVector::ZeroVector, FQuat::Identity); compMotionController->SetRelativeScale3D(FVector::OneVector); compMotionController->MotionSource = a_nameHandType; compMotionController->SetupAttachment(a_compParent); }
We will need to call the “CreateHandController” function in the “CreateComponents” function to create the left and right controller component. The following code is added at the end of the function.
CreateHandController(compVRCameraRoot, "MC_Left", FXRMotionControllerBase::LeftHandSourceId); CreateHandController(compVRCameraRoot, "MC_Right",FXRMotionControllerBase::RightHandSourceId);
Compiling the code now you should see the components show up just like the previous step. But we still dont have any way to visualize the controllers. Let’s remedy that.
We will add some static meshes that represent our hands. This final step will help us to visualize all the changes that we have done so far.
We would need another function that will load and add cubes to the motion controller components. The function we will create will have the following implementation:
UStaticMeshComponent* AcVRPlayerPawn::CreateHandMesh(UMotionControllerComponent* a_compParent, FName a_strDisplayName, FName a_nameHandType) { UStaticMeshComponent* refComponentHand = NULL; //Find the default cube that ships with the engine content static ConstructorHelpers::FObjectFinderCubeMeshObject(TEXT("StaticMesh'/Engine/BasicShapes/Cube.Cube'")); if (!CubeMeshObject.Object) { UE_LOG(LogTemp, Error, TEXT("Could not load the default cube for hand mesh")); return NULL; } //create the mesh component refComponentHand = CreateDefaultSubobject(a_strDisplayName); //set the mesh to the component refComponentHand->SetStaticMesh(CubeMeshObject.Object); //Set the defaults refComponentHand->SetAutoActivate(true); refComponentHand->SetVisibility(true); refComponentHand->SetHiddenInGame(false); //Set the root refComponentHand->SetupAttachment(a_compParent); refComponentHand->SetRelativeLocationAndRotation(FVector::ZeroVector, FQuat::Identity); FVector vec3Scale = FVector(0.25,0.25,0.25); refComponentHand->SetRelativeScale3D(vec3Scale); return refComponentHand; }
We just have to call the function in the “CreateHandController” function. At the bottom of the functions let’s add the following code.
//Create the hand mesh for visualization FName strMeshDisplayName = a_nameHandType == FXRMotionControllerBase::LeftHandSourceId ? FName(TEXT("Hand_Left")) : FName(TEXT("Hand_Right")); CreateHandMesh(compMotionController, strMeshDisplayName, a_nameHandType);
Compile the code now and the VR player pawn should have all the required components. Here is the final set-up before we start testing:
Finally we can start testing out our VR player pawn. First we can start testing within the editor by hitting “VR Preview”. Once we have verified that everything works are intended we can create a build and test it out.
You can grab the project from the repository
Here is a video of the final output in the editor as well as the build.
Very good detail man!