|
Team
Solo
Engine
Unity
Plugin
SteamVR
Gravity Rotation
Most VR games use realistic physics models to make the interaction with the virtual world feel more realistic and immersive. However, I could not find any VR games which use gravity rotation as an in-game mechanic. That is why I decided to implement a gravity rotation system to see how it feels and what can be done with that.
Motion sickness
The biggest problem of VR in my opinion is that it causes motion sickness for many players if some interactions are not done in the right way. I would guess that for this reason, no VR games use gravity rotation so far. For example, from my testing, I noticed that rotating the player according to gravity often causes dizziness. However, it can be fixed from the game design standpoint: for example, by giving the player magnetic or sticky boots. For my prototype, I still implemented player rotation to test how it would feel.
Gravity rotation "testing facility"
I needed to create a place where I could quickly test how well my scripts work. For that reason, I created a box-shaped room with a sealable wall activated with a HoverButton which is included with the Steam VR library. Placing some rigidbody objects inside shows how things get affected by the updating gravity. Also, I put a SpringJoint-based gravity pointer in the middle of the room to be able to see the gravity's direction at the moment.
Gravity rotation controller
Controller for gravity rotation can be done in many ways: buttons with arrows, two linear drive mechanisms attached to each other, levers, etc. But in my case, I decided to go with a rotation ball with a handle for its intuitiveness. Rotating the ball to any side starts gravity rotation in the same direction. However, this choice made me accommodate a lot during development due to many unexpected factors.
Gravity Controller Hierarchy
The first problem I met is that attaching an object to the hand makes it the hand's child. Because of that, the device's local rotation always stays zero, it is affected by the hand's initial rotation, and its position is entirely controlled by the hand's transform. Position constraints will not help you and moving the device to its initial position every frame will just make it jitter.

As a solution to this problem, I created two parts of the device: an "interactable" part, and a "visible" part. The first one contains the Interactable.cs script, so you can attach it to your hand, but has no mesh renderer. The "visible" part has the same hierarchy, but in the opposite way to the "interactable" part, it has a mesh rendered and no Interactable.cs script. While the player interacts with the fully transparent "interactable" part, its script (InteractableRotationBall.cs) constantly updates the "visible" part's rotation without changing its position. In my case, I am linearly interpolating its position for additional smoothness.
Gravity rotation
I tested many different ways of applying rotation to gravity. My first choice was to use the delta position vector at the top of the handle and rotate gravity in its direction. It worked well, but was not very optimal and had to be adjusted for all gravity controllers on each wall. So, I moved away from this idea to quaternions.

Since gravity is a vector, multiplying a quaternion by it gives a new vector with applied rotation. As a quaternion, in this case, I used the "visible" part's rotation. Since rotation should have adjustable speed, I used Quaternion.Slerp between Quaternion.identity and the "visible" part's rotation with a ratio t equal to speed multiplied by deltaTime. Apply it to the gravity and voila, now we can rotate gravity!
Player's world perception
While using a gravity controller, the player's position should be static. We can simply store it on the gravity controller's attachment and update it every frame to the initial one. But this will not be enough since gravity is constantly affecting the player's velocity, so we also have to nullify it.

Here we come to the big problem. While playing the game, the user is standing on a flat floor (hopefully), which is perpendicular to the real gravity's direction. However, the player cannot finish gravity rotation perfectly in one of six main directions. In that case, the real floor will not be perpendicular to the in-game gravity which would cause brain confusion as well as motion sickness. For this reason, we always have to finalize gravity rotation to one of the main directions when the gravity controller gets released.
Finalizing rotation
I created a GravityPointer.cs script which takes control of rotation completion. It is attached to a gravity pointer game object. Every time the player stops using the controller, it calls the FinishGravityRotation function. Also, the gravity controller's rotation is being added to the gravity pointer's rotation every frame (quaternions can be added to each other with multiplication).

The script contains an array of six main direction vectors, as well as their corresponding quaternions. On controller detachment, it compares its transform.position.up vector with all main direction vectors, and finds the closest one. Then it starts spherical interpolation to the direction's corresponding quaternion. All that is left is to update the gravity direction every frame with the pointer's transform.up.normalized vector, multiplied by the gravity strength.
Player rotation
As I have said before, the player's rotation might cause motion sickness for some people. However, it can be fixed by adding a fading-out screen or using some other project-specific techniques. In my case, I went with none of them to test how straight rotation. For this, I created a RotatePlayerByGravity.cs script and attached it to the player.

When the gravity pointer finishes gravity rotation, it makes this script start player's rotation. The overall idea is very similar to the finishing gravity's rotation. The only difference is that this time I spherically interpolate the player's current rotation to the gravity pointer's rotation. In order to make sure player does not get stuck in the ground, I lift them up a little (transform.up) before rotation.
Adjustable public variables
InteractableRotationBall.cs:
· "Visible" part's rotation smoothness
· Gravity rotation speed

GravityPointer.cs:
· Gravity strength
· Spherical lerp speed for finishing the rotation
· Maximum offset for spherical lerp to stop

RotatePlayerByGravity.cs:
· Distance of the lift applied before rotation
· Spherical lerp speed for player's rotation
· Maximum offset for spherical lerp to stop
Feel free to use it for your projects!
Virtual Reality
Unity
Unreal
Augmented Reality
C#
C++
Agile
Version Control
AI