Hey guys, there is a really tricky problem to deal with in VR: How do you prevent the player from putting their head through a colliding object?
I came up with a two part solution and it goes against the Epic VR documentation, but I feel my setup is far superior. I’m going to walk you guys through how to replicate my success.
- I am using the 4.13 Source build of the engine. You MUST download the latest source and compile the engine for this to work because we’re going to be making a couple small modifications to the engine source in order for this to work. Before we get into that, let me explain what’s going on. Normally, you would go into your Camera Component and find a checkbox which says “Lock to HMD” and you’d make sure that is checked. This would make sure that any HMD **translations **and **rotations **are applied to the camera, and it also applies a rendering late update. The important thing to note is that in the standard documentation, this will cause the Camera Component Relative Location to change by the HMD location, but the scene component your camera is attached to is NOT CHANGED. Okay? So this means people can put their head through a wall. In order to prevent people from putting their head through a wall, we’d have to do a sweep test between the cameras current location and its previous location to figure out if there’s anything colliding. We don’t want to sweep along the camera component location delta, we want to sweep along the actor location delta.
Conceptually, I have a disembodied head which contains all of the VR specific implementation code. I call this my “VR Head”. It will always be centered on the players head, and if you call “Get Actor Location” on this head, you’ll get the players head in world space coordinates! This is super simplified, because now you don’t have to derive this location from the actor location + camera relative offset. Anyways, lets get to the engine source!
-
Open up “CameraComponent.h” in \Engine\Source\Runtime\Engine\Classes\Camera\CameraComponent.h
-
Look for the following code (line 59)
/** True if the camera's orientation and position should be locked to the HMD */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CameraSettings)
uint32 bLockToHmd : 1;
And then add the following two code blocks:
/** True if ONLY the camera's orientation should be locked to the HMD. Position must be manually set. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CameraSettings)
uint32 bLockRotToHmd : 1;
/*This will use a late update to on the render thread. Use this for VR if you aren't locking the HMD.*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CameraSettings)
uint32 bUseLateUpdate : 1;
-
Next, open up “CameraComponent.cpp” in \Engine\Source\Runtime\Engine\Private\Camera\CameraComponent.cpp
-
Look for the “GetCameraView(float DeltaTime, FMinimalViewInfo& DesiredView)” method (Line 216). We’re going to be making some small modifications to this. We’re going to add support for locking the HMD rotation and support for a late render update. Change the top portion of the method code to look like this:
if (bUseLateUpdate && GEngine->HMDDevice.IsValid() && GEngine->HMDDevice->IsHeadTrackingAllowed())
{
ResetRelativeTransform();
const FTransform ParentWorld = GetComponentToWorld();
GEngine->HMDDevice->SetupLateUpdate(ParentWorld, this);
}
if ((bLockToHmd || bLockRotToHmd) && GEngine->HMDDevice.IsValid() && GEngine->HMDDevice->IsHeadTrackingAllowed())
{
ResetRelativeTransform();
const FTransform ParentWorld = GetComponentToWorld();
GEngine->HMDDevice->SetupLateUpdate(ParentWorld, this);
FQuat Orientation;
FVector Position;
if (GEngine->HMDDevice->UpdatePlayerCamera(Orientation, Position))
{
if (bLockToHmd)
{
SetRelativeTransform(FTransform(Orientation, Position));
}
else if (bLockRotToHmd)
{
SetRelativeTransform(FTransform(Orientation, FVector::ZeroVector));
}
}
}
-
Compile the engine and go get some coffee. It’s gonna be about ~30-45 minutes.
What did we just do?
We enabled ourselves to use a late rendering update, even if we’re manually setting the rotation and location of the camera. In the past, disabling “Lock to HMD” would also skip the late update, so if you manually modified the camera position / rotation, you’d get some very slight distortions due to the lack of this late update.
We can also lock the camera to the HMD rotation, but manually change the HMD position. If we have this option enabled, we’re automatically using the late update, so it’s not necessary to enable/disable the late update checkbox because it’s going to run. -
Open up your game and create a brand new PAWN based blueprint. It is super important to use the pawn instead of the character! The root component of the pawn is just a scene component, whereas the character blueprint’s root component is a capsule component.
-
We’re going to be adding in our components now! This is going to be a bit different from the Epic documentation, but I think it is a better setup.
8a) Create a scene component, rename it to “Base” and set it as the root component.
8b) Create two more scene components. Call them “Tripod” and “MC_Base”. They should be parented to the “Base” component.
8c) Within the tripod scene component, add a camera component. Make sure that “Lock to HMD” is false, and “Lock Rot to HMD” is set to true.
8d) Add two motion controller components and parent them to the “MC_Base” scene component. Make sure you set one of these components to the right motion controller.
8e) optional: Add two static mesh components and parent each one to the motion controller components. If you’re using the Oculus Touch Controller meshes, make sure that you multiply the right controller mesh scale Y value by -1.
After you’re done, it should look like this:
- Okay, now it’s time to create our blueprint logic. This is deceptively simple, but it took me a lot of trial and error to come up with this.
9a) Create a Vector variable called “HMDDelta”. This will be our change in HMD device position since the last frame!
9b) Create a second vector variable called “LastHMDPos”. This will be the last position of the HMD device at the last frame. We can’t get a delta value without knowing where we were last frame.
Here’s what your Begin Play should look like:
In the first node, we’re setting our Last HMD Position to the current hardware position when the game started.
In the second node, we’re going to be offsetting our MC_Mount relative location by the distance between the HMD and its play area origin (PAO). If you skip this step, your motion controllers will be offset! We have to do this because we’re doing everything manually.
- Now, we need to do our “Tick” event. This is where we’ll be manually moving our actor by the change in HMD location over time. Let’s look at the blueprint first:
10a) We’re calculating our HMD delta. This is the change in HMD position since the last frame. This is super important because we’re manually changing stuff!
10b) Now, instead of moving the Camera Component (as the documentation does), we’re changing the actor location by the HMD Delta amount!
10c) We can’t forget about changing the motion controller offset! We do this by changing the relative location by the change in the HMD delta. This will keep our hand positions correct even when players move their head around.
10d) Finally, we have to save our current HMD location into the “Last HMDPos” value so that we can repeat the process in the next frame update.
If it helps you to visualize what’s happening here, you can draw a bunch of debug spheres on each of the scene components. You’ll notice that that actor location moves instead of the camera component. If we attach a collision sphere to the actor, we can check “Sweep” on the “Set Actor Location” component in [10b]. Now, players can’t put their head through a virtual wall, yet move around in their play area using room scale VR
Although its not covered in this how-to, you’re now ready to mount the head on top of a character body! Since we’re controlling the HMD position manually by setting the actor position, we can manually control where exactly the head is in world space by calling “Set Actor Location” on it. So, the head can drive a character body position, and the character body position can also drive the head position!