diff --git a/README.md b/README.md index 0a0b2785e34958e7fbc54a9ede7458a2e790c51d..e01b4ce97f89619d964ad9a2198bbe116f49f8f6 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,13 @@ This allows users to use the mouse for looking around, with seperate sensitivite  1. Add a Empty with <kbd>Rightclick</kbd> + `Add Empty`. Rename it to something useful (we used `Player`) -2. Make sure the `Main Camera` is at the coordinates `0, 1.7, 0` and then drag the `Main Camera` onto the `Player`-Empty. This makes `Player` the parent of `Main Camera` +2. Make sure the `Main Camera` is at the coordinates `(0, 1.7, 0)` and rotated right and then drag the `Main Camera` onto the `Player`-Empty. This makes `Player` the parent of `Main Camera` 3. Create a Capsule that represents the Player-Body (for collisions and such) with <kbd>Rightclick</kbd> + `3D Object` > `Capsule`. Drag it onto the `Player`-Empty as well. Now things should look like in the picture above (highlighted in blue). Next we can add the Scripts. 1. Drag the `FPSController.cs`-script from the Assets onto the `Player`-Empty -2. Drag the `SmoothMouseLook.cs`-script from the Assets onto the `Main Camera` Object. And in the Script options (right sidebar) change the value for _Character Body_ to `Player` -3. Select the `Capsule` Object +2. Drag the `SmoothMouseLook.cs`-script from the Assets onto the `Player`-Empty. ## Tweaking diff --git a/SmoothMouseLook.cs b/SmoothMouseLook.cs index 6fd803637215c89a6e42850f5f4754c7f608bbc8..b954dafec7c17e4835265e2e1088e09a50f56b91 100644 --- a/SmoothMouseLook.cs +++ b/SmoothMouseLook.cs @@ -1,86 +1,133 @@ +using System.Collections; +using System.Collections.Generic; using UnityEngine; -[AddComponentMenu("Camera/Simple Smooth Mouse Look ")] +[AddComponentMenu("SmoothMouseLook ")] public class SmoothMouseLook : MonoBehaviour { - Vector2 _mouseAbsolute; - Vector2 _smoothMouse; - - [Tooltip("Limit the possible rotation to these values (Left/Right, Up/Down)")] - public Vector2 clampInDegrees = new Vector2(360, 180); - [Tooltip("Hide/Display Cursor")] - public bool lockCursor; - [Tooltip("Mouse Sensitivity (Left/Right, Up/Down)")] - public Vector2 sensitivity = new Vector2(2, 2); - [Tooltip("Movement Smoothing (Left/Right, Up/Down)")] - public Vector2 smoothing = new Vector2(3, 3); - [Tooltip("Set target direction to the camera's initial orientation.")] - public Vector2 targetDirection; - [Tooltip("Set target direction for the character body to its inital state.")] - public Vector2 targetCharacterDirection; - - // Assign this if there's a parent object controlling motion, such as a Character Controller. - // Yaw rotation will affect this object instead of the camera if set. - [Tooltip("Assign Parent Object if there is one (affects horizontal rotation around Y axis)")] - public GameObject characterBody; - + // The speed at which we turn (Mouse Sensitivity) + // mouseSensitivity.x is for left <-> right + // mouseSensitivity.y is for up <-> down + [Tooltip("How fast to turn when moving the mouse (bigger=faster, X: left<->right, Y: up<->down).")] + [SerializeField] Vector2 mouseSensitivity = new Vector2(70f, 60f); + + // How much smoothing goes on for each axis + [Tooltip("How smooth/mushy the mouse movement becomes (bigger=smoother, X: left<->right, Y: up<->down).")] + [SerializeField] Vector2 smoothing = new Vector2(2f, 2f); + + // How far up the head can tilt, measured in angles from the horizon + // Must be bigger than headLowerAngleLimit + [Tooltip("How many degrees the head can look up at max.")] + [SerializeField] float headUpperAngleLimit = 85f; + + // How far down the head can tilt, measured in angles from the horizon + // Must be smaller than headUpperAngleLimit + [Tooltip("How many degrees the head can look down at max.")] + [SerializeField] float headLowerAngleLimit = -80f; + + // Invert the Y Axis of the Mouse if true + [Tooltip("Invert Mouse Control for up<->down.")] + [SerializeField] bool InvertAxisY = true; + + // Our current rotation from start in degrees + float yaw = 0f; + float pitch = 0f; + + // Stores the orientations of the head and body when the game started + // We'll derive new orientations by combining these with the variables yaw + // and pitch + Quaternion bodyStartOrientation; + Quaternion headStartOrientation; + + // A reference to the head object (the object that will rotate up and down) + // The body is the current (this) object, so there is no variable needed. + // We don't want to expose this to the interface. Instead we just look for a + // Child object with type Camera, when the game starts. + Transform head; + + // Two 2D-Vectors that store both axis of the mouse + private Vector2 smoothedMouseDelta; + + + + // Start is called before the first frame update void Start() { - // Set target direction to the camera's initial orientation. - targetDirection = transform.localRotation.eulerAngles; - - // Set target direction for the character body to its inital state. - if (characterBody) - targetCharacterDirection = characterBody.transform.localRotation.eulerAngles; + // Find the head – this returns the transform parameter of this objects + // first Child of type Camera. If none is found head = null + head = GetComponentInChildren<Camera>().transform; + + // Cache the orientation of body and head. + // This errors if head (Camera Child) was not found + bodyStartOrientation = transform.localRotation; + headStartOrientation = head.transform.localRotation; + + // Lock and hide the cursor + Cursor.lockState = CursorLockMode.Locked; + Cursor.visible = false; } - - void Update() + + + + // A Easing function that smooths the mouse-movement. This is beeing + // done by making the head follow the mouse not exactly, but + // with some sort of a lag. The further away the heads point of focus + // is compared to the mouse, the faster it will move, once it comes + // closer, it slows down – this is called Easing. + // + // For an intuitive Explaination look here: + // https://processing.org/examples/easing.html + private Vector2 EaseMouseDelta(Vector2 mouseDelta) { - // Ensure the cursor is always locked when set - if (lockCursor) - { - Cursor.lockState = CursorLockMode.Locked; - } - - // Allow the script to clamp based on a desired target value. - var targetOrientation = Quaternion.Euler(targetDirection); - var targetCharacterOrientation = Quaternion.Euler(targetCharacterDirection); - - // Get raw mouse input for a cleaner reading on more sensitive mice. + // Scale input against the sensitivity setting and multiply that + // with the smoothing value. + mouseDelta *= mouseSensitivity.x * smoothing.x * Time.deltaTime; + mouseDelta *= mouseSensitivity.y * smoothing.y * Time.deltaTime; + + // Linear Interpolation ("Lerp") between the smoothed Delta from + // the last round/Frame and the actual mouse Position. + smoothedMouseDelta.x = Mathf.Lerp(smoothedMouseDelta.x, mouseDelta.x, 1f / smoothing.x); + smoothedMouseDelta.y = Mathf.Lerp(smoothedMouseDelta.y, mouseDelta.y, 1f / smoothing.y); + + // Return the smoothed 2D-Vector + return smoothedMouseDelta; + } + + + + // Every time Physics updates, update the movemnent of this object. + // Do this in FixedUpdate to keep pace with physically simulated Objects + void FixedUpdate() + { + // Read the Position-Change between this Frame and the last + // Note: GetRawAxis gives more sensitivity var mouseDelta = new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y")); - - // Scale input against the sensitivity setting and multiply that against the smoothing value. - mouseDelta = Vector2.Scale(mouseDelta, new Vector2(sensitivity.x * smoothing.x, sensitivity.y * smoothing.y)); - - // Interpolate mouse movement over time to apply smoothing delta. - _smoothMouse.x = Mathf.Lerp(_smoothMouse.x, mouseDelta.x, 1f / smoothing.x); - _smoothMouse.y = Mathf.Lerp(_smoothMouse.y, mouseDelta.y, 1f / smoothing.y); - - // Find the absolute mouse movement value from point zero. - _mouseAbsolute += _smoothMouse; - - // Clamp and apply the local x value first, so as not to be affected by world transforms. - if (clampInDegrees.x < 360) - _mouseAbsolute.x = Mathf.Clamp(_mouseAbsolute.x, -clampInDegrees.x * 0.5f, clampInDegrees.x * 0.5f); - - // Then clamp and apply the global y value. - if (clampInDegrees.y < 360) - _mouseAbsolute.y = Mathf.Clamp(_mouseAbsolute.y, -clampInDegrees.y * 0.5f, clampInDegrees.y * 0.5f); - - transform.localRotation = Quaternion.AngleAxis(-_mouseAbsolute.y, targetOrientation * Vector3.right) * targetOrientation; - - // If there's a character body that acts as a parent to the camera - if (characterBody) - { - var yRotation = Quaternion.AngleAxis(_mouseAbsolute.x, Vector3.up); - characterBody.transform.localRotation = yRotation * targetCharacterOrientation; - } - else - { - var yRotation = Quaternion.AngleAxis(_mouseAbsolute.x, transform.InverseTransformDirection(Vector3.up)); - transform.localRotation *= yRotation; + + // Run the Smoothing-Function we defined above on the Mouse + // Vector we read before and return a smoothed one + smoothedMouseDelta = EaseMouseDelta(mouseDelta); + + // Flip the vertical Control, if InvertAxisY is true + if (InvertAxisY) { + smoothedMouseDelta.y *= -1; } + + // Add the mouse movements to the current value of yaw and pitch + yaw += smoothedMouseDelta.x; + pitch += smoothedMouseDelta.y; + + // Clamp pitch so that we can't look directly down or up + pitch = Mathf.Clamp(pitch, headLowerAngleLimit, headUpperAngleLimit); + + // Compute rotations by rotating around a fixed axis (rotate yaw-degrees + // around the up direction for the body, and pitch degrees around the + // right direction for the head). + var bodyRotation = Quaternion.AngleAxis(yaw, Vector3.up); + var headRotation = Quaternion.AngleAxis(pitch, Vector3.right); + + // Finally combine the rotations for body and head with the start rotations + transform.localRotation = bodyRotation * bodyStartOrientation; + head.localRotation = headRotation * headStartOrientation; } } - \ No newline at end of file