// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #if GT_USE_INPUT_SYSTEM && ENABLE_INPUT_SYSTEM #define USE_INPUT_SYSTEM #endif using System.Collections.Generic; using UnityEngine; using UnityEngine.XR; #if USE_INPUT_SYSTEM using UnityEngine.InputSystem; #endif // USE_INPUT_SYSTEM namespace Microsoft.MixedReality.GraphicsTools { /// /// A simple "fly" camera for moving the camera while playing with a mouse/keyboard or gamepad. /// public class FlyCameraController : MonoBehaviour { private class CameraState { public Vector3 Position; public Vector3 Rotation; public void SetFromTransform(Transform t) { Position = t.position; Vector3 euler = t.eulerAngles; Rotation = new Vector3(euler.y, euler.x, euler.z); } public void Translate(Vector3 translation) { Vector3 rotatedTranslation = Quaternion.Euler(new Vector3(Rotation.y, Rotation.x, Rotation.z)) * translation; Position += rotatedTranslation; } public void LerpTowards(CameraState target, float positionLerp, float rotationLerp) { Rotation = Vector3.Lerp(Rotation, target.Rotation, rotationLerp); Position = Vector3.Lerp(Position, target.Position, positionLerp); } public void UpdateTransform(Transform t) { t.eulerAngles = new Vector3(Rotation.y, Rotation.x, Rotation.z); t.position = Position; } } [Header("Movement Settings")] [Tooltip("Exponential boost factor on translation, controllable by mouse wheel.")] public float Boost = 1.0f; [Tooltip("Minimum boost factor applied to translation.")] public float MinBoost = 0.5f; [Tooltip("Maximum boost factor applied to translation.")] public float MaxBoost = 4.0f; [Tooltip("Time it takes to interpolate camera position 99% of the way to the target."), Range(0.001f, 1.0f)] public float PositionLerpTime = 0.2f; [Header("Rotation Settings")] [Tooltip("X = Change in mouse position.\nY = Multiplicative factor for camera rotation.")] public AnimationCurve MouseSensitivityCurve = new AnimationCurve(new Keyframe(0.0f, 0.5f, 0.0f, 5f), new Keyframe(1.0f, 2.5f, 0.0f, 0.0f)); [Tooltip("Time it takes to interpolate camera rotation 99% of the way to the target."), Range(0.001f, 1.0f)] public float RotationLerpTime = 0.1f; [Tooltip("Whether or not to invert our Y axis for mouse/gamepad input to rotation.")] public bool InvertY = false; [Header("Other Settings")] public bool showControlsText = false; private CameraState targetCameraState = new CameraState(); private CameraState interpolatingCameraState = new CameraState(); private List xrDisplaySubsystems = new List(); /// /// Called when the game object state from from inactive to active. /// private void OnEnable() { targetCameraState.SetFromTransform(transform); interpolatingCameraState.SetFromTransform(transform); } /// /// Sets up initial state. /// private void Start() { #if UNITY_2023_1_OR_NEWER SubsystemManager.GetSubsystems(xrDisplaySubsystems); #else SubsystemManager.GetInstances(xrDisplaySubsystems); #endif } /// /// Called every frame to poll input and update the camera transform. /// private void Update() { if (!XRDeviceIsPresent() && Application.isFocused) { // Lock cursor when right mouse button pressed. #if USE_INPUT_SYSTEM if (Mouse.current != null && Mouse.current.rightButton.wasPressedThisFrame) #else if (Input.GetMouseButtonDown(1)) #endif // USE_INPUT_SYSTEM { Cursor.lockState = CursorLockMode.Locked; #if UNITY_EDITOR Cursor.visible = false; #endif } // Unlock when right mouse button released. #if USE_INPUT_SYSTEM if (Mouse.current != null && Mouse.current.rightButton.wasReleasedThisFrame) #else if (Input.GetMouseButtonUp(1)) #endif // USE_INPUT_SYSTEM { Cursor.lockState = CursorLockMode.None; #if UNITY_EDITOR Cursor.visible = true; #endif } // Rotation. Vector2 rotation = GetInputRotationDirection(); float rotationSensitivityFactor = MouseSensitivityCurve.Evaluate(rotation.magnitude); targetCameraState.Rotation.x += rotation.x * rotationSensitivityFactor; targetCameraState.Rotation.y += rotation.y * rotationSensitivityFactor; // Translation. float dt = Time.deltaTime; Vector3 translation = GetInputTranslationDirection() * dt; // Speed up movement when shift key held. #if USE_INPUT_SYSTEM if (Keyboard.current != null && Keyboard.current.leftShiftKey.isPressed) #else if (Input.GetKey(KeyCode.LeftShift)) #endif // USE_INPUT_SYSTEM { translation *= 10.0f; } // Modify movement by a boost factor (defined in Inspector and modified in play mode through the mouse scroll wheel). #if USE_INPUT_SYSTEM Vector2 scroll = Mouse.current != null ? Mouse.current.scroll.ReadValue() : Vector2.zero; Boost += scroll.y * dt; #else Boost += Input.mouseScrollDelta.y * dt; #endif // USE_INPUT_SYSTEM Boost = Mathf.Clamp(Boost, MinBoost, MaxBoost); translation *= Mathf.Pow(2.0f, Boost); targetCameraState.Translate(translation); // Framerate-independent interpolation. // Calculate the lerp amount, such that we get 99% of the way to our target in the specified time. float positionLerp = 1.0f - Mathf.Exp((Mathf.Log(1.0f - 0.99f) / PositionLerpTime) * dt); float rotationLerp = 1.0f - Mathf.Exp((Mathf.Log(1.0f - 0.99f) / RotationLerpTime) * dt); interpolatingCameraState.LerpTowards(targetCameraState, positionLerp, rotationLerp); interpolatingCameraState.UpdateTransform(transform); } } /// /// Displays the camera controls via a user interface. /// private void OnGUI() { if (!XRDeviceIsPresent() && showControlsText) { #if USE_INPUT_SYSTEM bool gamepadPresent = Gamepad.current != null; #else bool gamepadPresent = false; #endif // USE_INPUT_SYSTEM if (gamepadPresent) { GUI.Label(new Rect(10.0f, 10.0f, 256.0f, 128.0f), "Camera Controls\nRight Stick to Rotate\nLeft Stick to Translate"); } else { GUI.Label(new Rect(10.0f, 10.0f, 256.0f, 128.0f), "Camera Controls\nRight Click + Mouse Move to Rotate\n'W' 'A' 'S' 'D' to Translate"); } } } /// /// Returns true if an XR device is connected and running. For example a VR headset. /// private bool XRDeviceIsPresent() { foreach (var xrDisplay in xrDisplaySubsystems) { if (xrDisplay != null && xrDisplay.running) { return true; } } return false; } /// /// Turns mouse/stick controls into an input vector. /// private Vector2 GetInputRotationDirection() { Vector2 direction = Vector3.zero; #if USE_INPUT_SYSTEM if (Mouse.current != null && Mouse.current.rightButton.isPressed) #else if (Input.GetMouseButton(1)) #endif // USE_INPUT_SYSTEM { #if USE_INPUT_SYSTEM // Magical value to feel like the editor. const float rotationScaler = 0.075f; Vector2 delta = Mouse.current.delta.ReadValue() * rotationScaler; direction += new Vector2(delta.x, delta.y * (InvertY ? 1.0f : -1.0f)); #else direction += new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y") * (InvertY ? 1.0f : -1.0f)); #endif // USE_INPUT_SYSTEM } #if USE_INPUT_SYSTEM if (Gamepad.current != null) { // Magical value to feel like the editor. #if UNITY_GAMECORE && !UNITY_EDITOR const float rotationScaler = 1.0f; #else const float rotationScaler = 0.25f; #endif Vector2 delta = Gamepad.current.rightStick.ReadValue() * rotationScaler; direction += new Vector2(delta.x, delta.y * (InvertY ? 1.0f : -1.0f)); } #else // TODO - [Cameron-Micka] is is possible to query the right stick without setting up virtual axes in the legacy input system? #endif // USE_INPUT_SYSTEM return direction; } /// /// Turns WASD/stick controls into an input vector. /// private Vector3 GetInputTranslationDirection() { Vector3 direction = Vector3.zero; #if USE_INPUT_SYSTEM if (Keyboard.current != null) { if (Keyboard.current.wKey.isPressed) { direction += Vector3.forward; } if (Keyboard.current.sKey.isPressed) { direction += Vector3.back; } if (Keyboard.current.aKey.isPressed) { direction += Vector3.left; } if (Keyboard.current.dKey.isPressed) { direction += Vector3.right; } if (Keyboard.current.qKey.isPressed) { direction += Vector3.down; } if (Keyboard.current.eKey.isPressed) { direction += Vector3.up; } direction.Normalize(); } if (Gamepad.current != null) { Vector2 stick = Gamepad.current.leftStick.ReadValue(); direction += new Vector3(stick.x, 0.0f, stick.y); } #else if (Input.GetKey(KeyCode.W)) { direction += Vector3.forward; } if (Input.GetKey(KeyCode.S)) { direction += Vector3.back; } if (Input.GetKey(KeyCode.A)) { direction += Vector3.left; } if (Input.GetKey(KeyCode.D)) { direction += Vector3.right; } if (Input.GetKey(KeyCode.Q)) { direction += Vector3.down; } if (Input.GetKey(KeyCode.E)) { direction += Vector3.up; } direction.Normalize(); direction += new Vector3(Input.GetAxis("Horizontal"), 0.0f, Input.GetAxis("Vertical")); #endif // USE_INPUT_SYSTEM return direction; } } }