DSprtn / GTFO_VR_Plugin

A plugin to add full roomscale Virtual Reality support to your favorite game!
MIT License
144 stars 13 forks source link

implement roomscale movement #61

Closed Nordskog closed 7 months ago

Nordskog commented 7 months ago

What

Adds limited support for roomscale movement. This is mostly intended to allow you to not think about where your center is, without accidentally waking enemies or allowing players to cheat. Supporting the use of something like a treadmill where the controller would not be the primary movement input has very different implications, and is not the focus of this PR.

The problem

The VR player( HMD ) is parented to the Player model, which is in turn moved using controller input. If the player steps 1 meter to the right in real life with their head, as far as the game is concerned their player has not moved at all, and moving the Player model using the controller leaves the HMD in this same offset location relative to the Player model.

Since collisions are based on the Player model location, this means the player may collide with walls, enemies, or even fall because they did not realize they had moved away from the center of their Player model.

Since this is a sneaking game, simply moving the Player Model to wherever the VR Player is not sufficient.

The solution

The basic logic goes as follows:

Player moving using controller and Player to HMD distance >0.05m? Move Player to HMD. [roomscale.webm](https://github.com/DSprtn/GTFO_VR_Plugin/assets/8961771/3c7322d3-ef42-4ea7-942b-bc225ee3c5ac)
Player is standing still and Player to HMD distance > 1m? Move player to HMD. [standing_still.webm](https://github.com/DSprtn/GTFO_VR_Plugin/assets/8961771/458f57a9-e87a-4432-95cb-3fc0d8dbab96)
Moving player would cause a drop of >0.3m? Do not move player. [no_fall.webm](https://github.com/DSprtn/GTFO_VR_Plugin/assets/8961771/3681db3f-28de-4703-be4e-6f9895e06ff3)
Player to HMD Distance is >1.75m for any reason? Move player. [forced_drop.webm](https://github.com/DSprtn/GTFO_VR_Plugin/assets/8961771/b36e2658-97de-4e43-8825-d7ed18e071e6)
Moving the player will trigger enemies just like normal movement would. [alert.webm](https://github.com/DSprtn/GTFO_VR_Plugin/assets/8961771/f15974be-5349-4c98-97c4-b970a0fe6456)
Movement is handled by the PlayerController which considers collisions, so you cannot move anywhere you would otherwise be unable to. [collision.webm](https://github.com/DSprtn/GTFO_VR_Plugin/assets/8961771/85d4696c-7da1-4651-9d94-cb5652d89535)
While the shift is instant to the Player, it is interpolated for other players. Here the player is moved 1m [1m_jump.webm](https://github.com/DSprtn/GTFO_VR_Plugin/assets/8961771/db6a5386-decb-4ba1-ad8a-75e7c8f05113)

How

In PlayerOrigin.updateRoomscale() we first calculate the distance between the Player model and the HMD on a horizontal plane.

Depending on their locomotion state, and current velocity, we decide whether they should be moved, adding a Raycast downwards from the HMD position to check how far it would cause the player to drop.

Once it has been decided we will be moving the Player model, we call PlayerCharacterController.ManualMoveToWithCollision() with the offset to have it attempt to move the Player model to the HMD. Depending on obstacles in the level this may end up moving the player a shorter distance than requested.

We store the offset between the original position and the new position of the Player Model in PlayerOrigin.m_roomscaleOffset for later.

All the above logic is performed in PlayerOrigin.FixedUpdate(), so PlayerOrigin.UpdateOrigin() is called first, as otherwise the Player Model may have been moved far away by teleporting or restarting from a checkpoint, but the VR Origin with the HMD would not have been moved to it yet.

PlayerOrigin.UpdateOrigin() is called several times per frame to move the PlayerOrigin ( HMD origin ) to the Player model position, as well as apply the rotation offset resulting from smooth/snap turning. Here we simply add PlayerOrigin.m_roomscaleOffset to the PlayerOrigin.Transform, in addition to all the existing offsets. This cancels out the effect of moving the Player model to the HMD position, resulting in a completely seamless transition.

As a sanity check, we re-center the player ( move HMD to wherever the Player Model is ) if the distance is greater than 15m. Previously, the player would not be re-centered correctly when respawning ( checkpoint or host mitigation), resulting in the HMD being offset from the player model by whatever distance they are to the origin of their VR playsplace This has been fixed in https://github.com/DSprtn/GTFO_VR_Plugin/pull/61/commits/8830e369e154107098f6b1eecbbd1a3ed1752f38, but the sanity check will hopefully prevent any similar issues from becoming a problem.

Rather than using the HMD position directly, we calculate a rough Torso position by shifting the HMD position backwards by 20cm on a horizontal plane. When PlayerOrigin.HandleOriginShift() is called as a result of smooth/snap turning, we clear the PlayerOrigin.m_roomscaleOffset and initialize it with the vector from the HMD to the Torso, so it centers you to the same position as the roomscale logic.

Considerations

Since the player is unable to move more than 1m without triggering movement, using this to cheat would be slower than just moving using the controllers.

It is very common to lean over ledges to get a better shot, and the player may move using the controller while doing this. The 1.75m threshold for causing a fall by moving the player is enough to avoid any accidental falls. Even if they stop the maximum of 1m short of the ledge and walk over to it, they will still be walking completely in thin-air long before being dropped. I did try keeping track of the last grounded position, and measuring a distance from that instance, but you end up with pretty much the exact same result.

Tested 2-3 hours each by 8 players, and haven't run into any new issues, but any potential gameplay implications should be carefully considered.

DSprtn commented 7 months ago

Looks good!

I do not yet fully understand all of the implications, but I reckon if we let people beta this for a while it should be alright.