Skip to content

Instantly share code, notes, and snippets.

@shiena
Created March 8, 2020 08:00
Show Gist options
  • Select an option

  • Save shiena/4b4f4e874287a37e4bc14974b76294ec to your computer and use it in GitHub Desktop.

Select an option

Save shiena/4b4f4e874287a37e4bc14974b76294ec to your computer and use it in GitHub Desktop.
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.UI;
using UnityEngine.XR.Interaction.Toolkit;
[RequireComponent(typeof(CharacterController))]
public class VRRigidbodyFirstPersonController : LocomotionProvider
{
[Serializable]
public class MovementSettings
{
public bool turnModeDisable = false;
[SerializeField]
public bool smoothTurnMode = true;
public bool freeWalkMode = true;
public float ForwardSpeed = 8.0f; // Speed when walking forward
public float BackwardSpeed = 4.0f; // Speed when walking backwards
public float StrafeSpeed = 4.0f; // Speed when walking sideways
public float RunMultiplier = 2.0f; // Speed when sprinting
public KeyCode RunKey = KeyCode.LeftShift;
public float JumpForce = 30f;
public AnimationCurve SlopeCurveModifier = new AnimationCurve(new Keyframe(-90.0f, 1.0f), new Keyframe(0.0f, 1.0f), new Keyframe(90.0f, 0.0f));
[HideInInspector] public float CurrentTargetSpeed = 8f;
public void UpdateDesiredTargetSpeed(Vector2 input)
{
if (input == Vector2.zero) return;
if (input.x > 0 || input.x < 0)
{
//strafe
CurrentTargetSpeed = StrafeSpeed;
}
if (input.y < 0)
{
//backwards
CurrentTargetSpeed = BackwardSpeed;
}
if (input.y > 0)
{
//forwards
//handled last as if strafing and moving forward at the same time forwards speed should take precedence
CurrentTargetSpeed = ForwardSpeed;
}
}
}
[Serializable]
public class AdvancedSettings
{
public float groundCheckDistance = 0.01f; // distance for checking if the controller is grounded ( 0.01f seems to work best for this )
public float stickToGroundHelperDistance = 0.5f; // stops the character
public float slowDownRate = 20f; // rate at which the controller comes to a stop when there is no input
public bool airControl; // can the user control the direction that is being moved in the air
[Tooltip("set it to 0.1 or more if you get stuck in wall")]
public float shellOffset; //reduce the radius by that ratio to avoid getting stuck in wall (a value of 0.1f is nice)
}
public PlayerInput InputSource;
private bool startingStill = true;
private Vector3 velocity = Vector3.zero;
public HMDVarietyHandler hmdVarietyScript;
public Transform headsetOffset;
public Transform worldRef;
public float RotationRatchet = 45.0f;
public float smoothTime = 0.15f;
private Vector3 currentRotation = Vector3.zero;
private Vector3 refVelocity = Vector3.zero;
public Camera cam;
public PauseMenuController pauseMenuScript;
public IntroFader fadeScript;
public GameObject fadeBox;
public float CurrentDistance;
public MovementSettings movementSettings = new MovementSettings();
public AdvancedSettings advancedSettings = new AdvancedSettings();
private CharacterController m_CharControl;
private Vector3 m_GroundContactNormal;
public bool ReadyToSnapTurn; // Set to true when a snap turn has occurred, code requires one frame of centered thumbstick to enable another snap turn.
private Vector3 desiredMove = Vector3.zero;
public bool loading = false;
public bool colliding = false;
private void OnEnable()
{
InputSource.MenuInputEvent_Left += openPauseMenu;
InputSource.MenuInputEvent_Right += openPauseMenu;
}
private void OnDisable()
{
InputSource.MenuInputEvent_Left -= openPauseMenu;
InputSource.MenuInputEvent_Right -= openPauseMenu;
}
public void openPauseMenu(bool value)
{
pauseMenuScript.pauseButtonPressed(value);
}
private void Start()
{
m_CharControl = GetComponent<CharacterController>();
StartCoroutine(waitThenStart());
}
IEnumerator waitThenStart()
{
yield return new WaitForSeconds(0.5f);
startingStill = false;
yield return null;
}
private void Update()
{
PreCharacterMovement();
}
public void changeTurnMode(bool value)
{
//only do this on button press
value = !movementSettings.smoothTurnMode;
movementSettings.smoothTurnMode = value;
}
public void changeMovementMode(bool value)
{
movementSettings.freeWalkMode = value;
}
void PreCharacterMovement()
{
// First, determine if the lateral movement will collide with the scene geometry.
var oldCameraPos = headsetOffset.position;
var wpos = cam.transform.position;
var delta = wpos - transform.position;
delta.y = 0;
var len = delta.magnitude;
if (len > 0.02f)
{
//Recenter the char controller times 100 for an instant move
//commented out for now due to unwanted fast movement glitches! To rewrite with an alternative
//if (!startingStill)
// {
m_CharControl.Move(delta);
// }
//m_CharControl.Move(delta;
var currentDelta = transform.position - wpos;
currentDelta.y = 0;
CurrentDistance = currentDelta.magnitude;
//Only set the x and z values here so we don't suddenly move the headset down!
headsetOffset.position = new Vector3(oldCameraPos.x, headsetOffset.position.y, oldCameraPos.z);
if (CurrentDistance > 0)
{
headsetOffset.position = oldCameraPos - delta;
}
}
else if (loading == false)
{
CurrentDistance = 0;
}
}
private void FixedUpdate()
{
Vector2 input = GetInput();
if ((Mathf.Abs(input.x) > float.Epsilon || Mathf.Abs(input.y) > float.Epsilon))
{
// always move along the camera forward as it is the direction that it being aimed at
desiredMove = cam.transform.forward * input.y + cam.transform.right * input.x;
desiredMove = Vector3.ProjectOnPlane(desiredMove, m_GroundContactNormal).normalized;
desiredMove.x = desiredMove.x * movementSettings.CurrentTargetSpeed;
desiredMove.z = desiredMove.z * movementSettings.CurrentTargetSpeed;
desiredMove.y = 0;
//desiredMove.y = desiredMove.y * movementSettings.CurrentTargetSpeed;
}
else
{
//In here, pass the values of the headset moving past bounds to move the character! To write later...
desiredMove = Vector3.zero;
}
//Rotation here
if (movementSettings.turnModeDisable == false)
{
if (movementSettings.smoothTurnMode == true)
{
//avoids the rotating if the game is effectively paused
if (Mathf.Abs(Time.timeScale) < float.Epsilon) return;
// get the rotation before it's changed
float oldYRotation = transform.eulerAngles.y;
float rightx = Input.GetAxis("RightHorizontal") * smoothTime;
if (rightx == 0)
{
EndLocomotion();
}
else
{
BeginLocomotion();
//To smooth this out, smoothTime value must be 0 for instant turning, nearer 0.15 is smooth/slower
Vector3 targetRotation = new Vector3(0, rightx, 0);
currentRotation = Vector3.SmoothDamp(currentRotation, targetRotation, ref refVelocity, 0.01f);
transform.RotateAround(new Vector3(cam.transform.position.x, 0, cam.transform.position.z), Vector3.up, rightx);
//EndLocomotion();
}
}
//Test this out with the Vive and Oculus Go
else if (movementSettings.smoothTurnMode == false && BeginLocomotion())
{
//snap turning code here!
// Vector3 euler = transform.rotation.eulerAngles;
float targetAngle = 0;
if (Input.GetAxis("RightHorizontal") < 0)
{
if (ReadyToSnapTurn)
{
// euler.y -= RotationRatchet;
targetAngle -= RotationRatchet;
ReadyToSnapTurn = false;
}
}
else if (Input.GetAxis("RightHorizontal") > 0)
{
if (ReadyToSnapTurn)
{
// euler.y += RotationRatchet;
targetAngle += RotationRatchet;
ReadyToSnapTurn = false;
}
}
else
{
ReadyToSnapTurn = true;
}
//transform.rotation = Quaternion.Euler(euler);
transform.RotateAround(new Vector3(cam.transform.position.x, 0, cam.transform.position.z), Vector3.up, targetAngle);
targetAngle = 0;
EndLocomotion();
}
}
if (movementSettings.freeWalkMode)
{
m_CharControl.SimpleMove(desiredMove * movementSettings.CurrentTargetSpeed);
}
}
private Vector2 GetInput()
{
Vector2 input = new Vector2
{
x = Input.GetAxis("Horizontal"),
y = Input.GetAxis("Vertical")
};
movementSettings.UpdateDesiredTargetSpeed(input);
return input;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment