VR Doors - Unreal C++
I think my door mechanics are some of the best doors made for VR.
They involve these things (ordered by hardest - easiest to implement):
A UseDoor() function for pushing/pulling the door while grabbing.
A Swing() function for when the player lets go of the door.
Two quaternions representing the max and min rotations of the door.
A door hinge location / rotation (USceneComponent).
A knob collision sphere for the player to grab.
A door mesh.
Two bools for setting the door state (bUseDoor, bSwing).
Here is what the tick function looks like for our Door class:
void ADoor::Tick(float DeltaTime) { Super::Tick(DeltaTime); if (bUseDoor) // when the player is grabbing the door for push/pull. { UseDoor(); } else if (bSwing) // when the player lets go of the door, swing until stop. { Swing(DeltaTime); } }
When the player grabs the door, we set bUseDoor to true so that UseDoor() gets called every frame until the player lets go.
Here is UseDoor():
void ADoor::UseDoor() { // This is how much the hand controller moved from the last frame. // We set LastHCLocation when the player first grips the door. FVector HCDelta = LastHCLocation - HandController->GetActorLocation(); // We don't care about the Z value (think about it). HCDelta.Z = 0; FVector DFV = DoorHinge->GetForwardVector(); // We take the dot product of the HCDelta and the door hinge // forward vector so we can rotate the door in accordance. // Ex: Hand controller moves perpendicular to door, rotate door a lot. // Hand controller moves parallel to door, rotate door a little. float Dot = FVector::DotProduct(HCDelta.GetSafeNormal(), DFV); FQuat DHQ = DoorHinge->GetComponentQuat(); // This is how much we want to rotate the door based on how far // the player's hand controller moved and the dot product. // The (180.f / PI) is a degrees to radians conversion and the // 0.0002f is a constant that I found works perfect. SlerpSize = (-Dot * HCDelta.Size() * (180.f / PI)) * 0.0002f; // Don't let the SlerpSize go over a certain value to make the door feel more real. SlerpSize = (SlerpSize > 3.f) ? 3.f : SlerpSize; FQuat DQ = FQuat(DoorHinge->GetUpVector(), SlerpSize); FQuat NewQuat = DHQ * DQ; float MinDistance = UKismetMathLibrary::Quat_AngularDistance(NewQuat, MinRotation); float MaxDistance = UKismetMathLibrary::Quat_AngularDistance(NewQuat, MaxRotation); // Next we check if the door is about to be rotated all the way open or closed. // If its all the way open/closed, set the rotation to all the way open/closed. // If the door isn't all the way open/closed, then rotate by DQ. if (MaxDistance > MaxAngleRadians) { DoorHinge->SetWorldRotation(MinRotation); } else if (MinDistance > MaxAngleRadians) { DoorHinge->SetWorldRotation(MaxRotation); } else { DoorHinge->AddLocalRotation(DQ); } LastHCLocation = HandController->GetActorLocation(); }
MinRotation is an FQuat that represents the rotation of the door when it is closed.
MaxRotation is an FQuat that represents the rotation of the door when it is all the way open.
MaxAngleRadians is the Max angle that the door can be from the min or max rotation.
Once the player lets go of the door, we set bUseDoor to false and bSwing to true.
Here is our Swing() function:
void ADoor::Swing(float DeltaTime) { // If the SwingVelocity slows down to a stop, then set bSwing to false. if (fabsf(SwingVelocity) < 0.0001f) { bSwing = false; } // We keep making SwingVelocity smaller by subtracting HingeFriction * DeltaTime. // HingeFriction is set at 0.008f for me. SwingVelocity = (SwingVelocity > 0) ? SwingVelocity - (HingeFriction * DeltaTime) : SwingVelocity - (-HingeFriction * DeltaTime); FQuat DHQ = DoorHinge->GetComponentQuat(); FQuat DQ = FQuat(DoorHinge->GetUpVector(), SwingVelocity); FQuat NewQuat = DHQ * DQ; float MinDistance = UKismetMathLibrary::Quat_AngularDistance(NewQuat, MinRotation); float MaxDistance = UKismetMathLibrary::Quat_AngularDistance(NewQuat, MaxRotation); // when the door is closed all the way shut. if (MaxDistance > MaxAngleRadians) { bSwing = false; DoorHinge->SetWorldRotation(MinRotation); } // when the door opens all the way, we make it bounce and swing back. else if (MinDistance > MaxAngleRadians) { SwingVelocity = -SwingVelocity * 0.25f; } else { DoorHinge->AddLocalRotation(DQ, true); } }