Last active
June 29, 2017 15:22
-
-
Save scryptonite/90c63e7ebf02dfa4bf3c19173754b410 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
// Requires the MousePointer script if you plan on having the mouse be restored to its former position when released from being locked. | |
/// <summary>A Camera Controller that allows the user to pan/orbit and zoom around a target with their mouse.</summary> | |
[DisallowMultipleComponent, AddComponentMenu("Camera Controllers/Better Mouse Orbit")] | |
public class BetterMouseOrbit : MonoBehaviour { | |
#region Enumerations | |
/// <summary> | |
/// An enumeration of possible MouseButtons combinations for panning. | |
/// <para>See BetterMouseOribit's panMouseButton property.</para> | |
/// </summary> | |
public enum MouseButton { | |
None, | |
AnyMouseButton, | |
LeftMouseButton, | |
RightMouseButton, | |
MiddleMouseButton, | |
LeftOrRightMouseButton, | |
LeftAndRightMouseButton, | |
} | |
#endregion Enumerations | |
#region Camera Target Settings | |
/// <summary>The target that the camera is looking at and following.</summary> | |
[Header("Camera Target Settings"), Tooltip("The target that the camera is looking at and following.")] | |
public Transform target; | |
/// <summary>Controls the offset from the target's center.</summary> | |
[Tooltip("Controls the offset from the target's center.")] | |
public Vector3 targetOffset = Vector3.up * .5f; | |
/// <summary>Controls whether or not the camera should inherit the target's rotation.</summary> | |
[Tooltip("Should the camera be positioned relative to the target's rotation?")] | |
public bool inheritTargetRotation = false; | |
#endregion Camera Target Settings | |
#region Camera Distance & Zoom Settings | |
/// <summary> | |
/// Controls the desired distance away from the target. | |
/// <para>Changes when the user scrolls the mouse's scrollwheel.</para> | |
/// </summary> | |
[Header("Camera Distance & Zoom Settings"), Tooltip("The distance the camera is away from the target.")] | |
public float distance = 8f; | |
/// <summary>Represents the *true* distance from the target. Always being lerped towards the desired distance if not obstructed by a raycast.</summary> | |
public float _distance { get; private set; } | |
/// <summary>The closest the camera may be from the target.</summary> | |
[Tooltip("The closest the camera may be from the target.")] | |
public float distanceMin = 0f; | |
/// <summary>The furthest the camera may be from the target.</summary> | |
[Tooltip("The furthest the camera may be from the target.")] | |
public float distanceMax = 15f; | |
/// <summary>A multiplier that controls how fast the desired distance can change per delta of the mouse scrollwheel.</summary> | |
[Space, Tooltip("A multiplier that controls how fast the desired distance can change per delta of the mouse scrollwheel.")] | |
public float zoomSpeed = 8f; | |
/// <summary>The speed in which the camera's true distance lerps towards its desired distance.</summary> | |
[Tooltip("The speed in which the camera's true distance lerps towards its desired distance.")] | |
public float zoomLerpSpeed = 20f; | |
/// <summary>The speed in which the camera's true distance lerps towards its desired distance when the raycast behind the camera hits a surface.</summary> | |
[Tooltip("The speed in which the camera's true distance lerps towards its desired distance when the raycast behind the camera hits a surface.")] | |
public float zoomLerpClipSpeed = 50f; | |
#endregion Camera Distance & Zoom Settings | |
#region Camera Orientation & Pan Settings | |
/// <summary> | |
/// Controls the desired orientation of the camera relative to the target. | |
/// <para>Changes when the user pans the camera by activating the panMouseButton and moving their cursor.</para> | |
/// </summary> | |
[Header("Camera Orientation & Pan Settings"), Tooltip("The orientation of the camera relative to the target.")] | |
public Vector2 orientation = Vector2.zero; | |
/// <summary>Represents the *true* orientation of the camera. Always being lerped towards the desired orientation.</summary> | |
public Vector2 _orientation { get; private set; } | |
/// <summary>Restricts both orientations' y- axis from going below this value.</summary> | |
[Tooltip("Restricts both orientations' y- axis from going below this value.")] | |
public float orientationYMin = -20f; | |
/// <summary>Restricts both orientations' y- axis from exceeding this value.</summary> | |
[Tooltip("Restricts both orientations' y- axis from exceeding this value.")] | |
public float orientationYMax = 80f; | |
/// <summary>A multiplier that controls how fast the desired orientation can change per delta of mouse movement.</summary> | |
[Space, Tooltip("A multiplier that controls how fast the desired orientation can change per delta of mouse movement.")] | |
public Vector2 panSpeed = Vector2.one * 200f; | |
/// <summary>The speed in which the camera's true orientation lerps towards the desired orientation.</summary> | |
[Tooltip("The speed in which the camera's true orientation lerps towards the desired orientation.")] | |
public float panLerpSpeed = 50f; | |
/// <summary>The mouse button combination that must be pressed before panning takes place.</summary> | |
[Tooltip("Controls the mouse button combination that must be pressed before panning takes place.")] | |
public MouseButton panMouseButton = MouseButton.LeftOrRightMouseButton; | |
/// <summary>A property that is true while the configured panMouseButton combination is pressed.</summary> | |
private bool IsMouseButtonHeld { | |
get { | |
// switch depending on the configured mouse button: | |
switch (panMouseButton) { | |
// always being held if not configured: | |
case MouseButton.None: return true; | |
// true if any of the three mouse buttons are held: | |
case MouseButton.AnyMouseButton: | |
return Input.GetMouseButton(0) | |
|| Input.GetMouseButton(1) | |
|| Input.GetMouseButton(2); | |
// true if the left mouse button is held: | |
case MouseButton.LeftMouseButton: return Input.GetMouseButton(0); | |
// true if the right mouse button is held: | |
case MouseButton.RightMouseButton: return Input.GetMouseButton(1); | |
// true if the middle mouse button is held: | |
case MouseButton.MiddleMouseButton: return Input.GetMouseButton(2); | |
// true if either the left or right mouse button is held: | |
case MouseButton.LeftOrRightMouseButton: | |
return Input.GetMouseButton(0) | |
|| Input.GetMouseButton(1); | |
// true if both the left and right mouse button are held: | |
case MouseButton.LeftAndRightMouseButton: | |
return Input.GetMouseButton(0) | |
&& Input.GetMouseButton(1); | |
// false if unrecognized mouse button configuration: | |
default: return false; | |
} | |
} | |
} | |
#endregion Camera Orientation & Pan Settings | |
#region Mouse Pointer Locking Settings | |
/// <summary>Controls whether or not the mouse pointer should be locked while panning.</summary> | |
[Header("Mouse Lock Settings"), Tooltip("Should the mouse pointer be locked while panning?")] | |
public bool lockWhilePanning = true; | |
/// <summary> | |
/// Controls whether or not the mouse pointer should be centered when released after being locked. | |
/// <para>If false, only restores the mouse to its former position on windows.</para> | |
/// </summary> | |
[Tooltip("Should the mouse pointer be placed at the center of the game window when released after being locked?")] | |
public bool centerMouseAfterLock = false; | |
#endregion Mouse Pointer Locking Settings | |
#region Camera Distance Raycast Settings | |
/// <summary>Controls the distance the camera should be away from the surface of a raycast hit.</summary> | |
[Header("Camera Distance Raycast Settings"), Tooltip("Controls the distance the camera should be away from the surface of a raycast hit.")] | |
public float minSurfaceDistance = 0.3f; | |
/// <summary>Controls which layers are valid for a raycast hit (Default: -1, Everything)</summary> | |
[Tooltip("Controls which layers are valid for a raycast hit.")] | |
public LayerMask raycastLayerMask = -1; | |
/// <summary>If enabled, excludes the target's layer from the raycast at runtime.</summary> | |
[Tooltip("Should the camera exclude the target's layer from the raycast automatically?")] | |
public bool excludeTargetLayer = false; | |
#endregion Camera Distance Raycast Settings | |
#region Camera Controller Methods | |
/// <summary>Updates the camera's position values given a delta of time.</summary> | |
/// <param name="deltaTime"> | |
/// A parameter that represents how much time has passed since the last call. | |
/// <para>Used to render the position the camera should be at immediately if 1.0f</para> | |
/// </param> | |
private void MoveCamera(float deltaTime) { | |
// store the target's position (or Vector3.zero if no target is configured): | |
Vector3 targetPosition = target ? target.position : Vector3.zero; | |
// store the target's layer (or none if no target is configured): | |
LayerMask targetLayer = target ? 1 << target.gameObject.layer : 0; | |
// store the target's rotation (or a neutral rotation if no target is configured or if not configured to inherit the target's rotation): | |
Quaternion targetRotation = inheritTargetRotation && target ? target.rotation : Quaternion.identity; | |
// update the target offset: | |
targetPosition += targetRotation * targetOffset; | |
// normalize and constrain both the desired and true orientation's x and y axis: | |
orientation = new Vector2(LoopAngle(orientation.x), ClampAngle(orientation.y, orientationYMin, orientationYMax)); | |
_orientation = new Vector2(LoopAngle(_orientation.x), ClampAngle(_orientation.y, orientationYMin, orientationYMax)); | |
// constrain both the desired and true distance: | |
distance = Mathf.Clamp(distance, distanceMin, distanceMax); | |
_distance = Mathf.Clamp(_distance, distanceMin, distanceMax); | |
// store current distance for later during logic handling the movement after a raycast hits something: | |
float prevDistance = _distance; | |
// lerp the true orientation towards the desired orientation: | |
_orientation = new Vector2( | |
Mathf.LerpAngle(_orientation.x, orientation.x, Mathf.Clamp01(deltaTime * panLerpSpeed)), | |
Mathf.LerpAngle(_orientation.y, orientation.y, Mathf.Clamp01(deltaTime * panLerpSpeed)) | |
); | |
// lerp the true distance towards the desired distance: | |
_distance = Mathf.Lerp(_distance, distance, Mathf.Clamp01(deltaTime * zoomLerpSpeed)); | |
// store the desired orientation as a quaternion: | |
Quaternion rotation = Quaternion.Euler(_orientation.y, _orientation.x, 0); | |
// create a ray that is pointing back and away from the target: | |
Vector3 ray = targetRotation * rotation * Vector3.back; | |
// prepare to capture a raycast hit: | |
RaycastHit hit; | |
// store the layermask of what counts as a valid raycast hit (and exclude the target's layer if exclude target layer is configured): | |
LayerMask layerMask = raycastLayerMask & (excludeTargetLayer ? ~targetLayer : -1); | |
// spherecast (which is a raycast but with a minimum surface distance) away from the target along the ray | |
// to prevent the target from being obstructed and the camera from clipping into a surface: | |
if (Physics.SphereCast(targetPosition, minSurfaceDistance, ray, out hit, distance, layerMask)) { | |
// recalculate the lerping of the true distance towards the desired distance, this time accounting for the raycast hit: | |
_distance = Mathf.Lerp(prevDistance, hit.distance, Mathf.Clamp01(deltaTime * zoomLerpClipSpeed)); | |
} | |
// update the camera positoin and rotation: | |
transform.position = targetPosition + (ray * _distance); | |
transform.rotation = targetRotation * rotation; | |
//// draw debug rays to show the desired distance (red) and the true distance (white) of the camera in the scene view: | |
//Debug.DrawRay(targetPosition, ray * distance, Color.red); | |
//Debug.DrawRay(targetPosition, ray * _distance, Color.white); | |
} | |
/// <summary> | |
/// Called by Unity when the component values in the inspector are changed. | |
/// <para>Used to reposition the Camera while editing the scene.</para> | |
/// </summary> | |
private void OnValidate() { | |
// force the true orientation and distance to their desired values: | |
_orientation = orientation; | |
_distance = distance; | |
// immediately move the camera to where it should be without lerping: | |
MoveCamera(1f); | |
} | |
/// <summary>Awake is called once when script instance is being loaded. See Unity3D docs.</summary> | |
private void Awake() { | |
// setup the true orientation and distance: | |
_orientation = orientation; | |
_distance = distance; | |
} | |
/// <summary>LateUpdate is called every frame, if the component is enabled. See Unity3D docs.</summary> | |
private void LateUpdate() { | |
// store true distance and orientation for delta checking: | |
float prevDistance = _distance; | |
Vector2 prevOrientation = _orientation; | |
// if we should lock the mouse cursor while panning: | |
if (lockWhilePanning) { | |
// if the mouse shouldn't be centered after the pan is complete: | |
if (!centerMouseAfterLock) { | |
// using the MousePointer class means the mouse position will be restored on release: | |
MousePointer.lockState = IsMouseButtonHeld ? CursorLockMode.Locked : CursorLockMode.None; | |
MousePointer.visible = !IsMouseButtonHeld; | |
} else { | |
// using the default Unity3D cursor class means the mouse will be centered on release: | |
Cursor.lockState = IsMouseButtonHeld ? CursorLockMode.Locked : CursorLockMode.None; | |
Cursor.visible = !IsMouseButtonHeld; | |
} | |
} | |
// if the configured mouse button combination is pressed, then pan the camera's desired orientation by the mouse's movement deltas: | |
if (IsMouseButtonHeld) { | |
orientation += new Vector2( | |
Input.GetAxis("Mouse X") * panSpeed.x, | |
-Input.GetAxis("Mouse Y") * panSpeed.y) * Time.deltaTime; | |
} | |
// change the camera's desired distance by the mouses's scrollwheel delta: | |
distance -= Input.GetAxis("Mouse ScrollWheel") * zoomSpeed; | |
// move the camera's real orientation and distance towards their desired values given the delta of time since the last frame: | |
MoveCamera(Time.deltaTime); | |
} | |
/// <summary>A helper function that takes an angle and returns what it would be if it was looped between 0 and 360.</summary> | |
private static float LoopAngle(float angle) { | |
return Mathf.Repeat(angle, 360); | |
} | |
/// <summary>A helper function that takes an angle and returns it clamped between a minimum and maximum.</summary> | |
private static float ClampAngle(float angle, float min, float max) { | |
if (angle < -360F) angle += 360F; | |
if (angle > 360F) angle -= 360F; | |
return Mathf.Clamp(angle, min, max); | |
} | |
#endregion Camera Controller Methods | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Runtime.InteropServices; | |
using UnityEngine; | |
/// <summary> | |
/// A Wrapper API for UnityEngine.Cursor. | |
/// <para>Use this the same as you would the UnityEngine.Cursor API, but enjoy the fact that locking | |
/// and unlocking the cursor will restore the mouse pointer to the position before it was locked.</para> | |
/// <para>Also provides a static property for reading and writing the mouse position.</para> | |
/// <para>See MSDN docs' GetCursorPos and SetCursorPos, as well as Unity3D docs' UnityEngine.Cursor</para> | |
/// </summary> | |
internal class MousePointer { | |
#region User32 Cursor Position Manipulation | |
/// <summary> | |
/// (Private) A structure that defines an x- and y- coordinate on the screen. | |
/// Used exclusively to store a mouse's position coordinates. | |
/// <para>See MSDN docs' POINT structure</para> | |
/// </summary> | |
[StructLayout(LayoutKind.Sequential)] | |
private struct MousePosition { | |
public int x; | |
public int y; | |
public MousePosition(int x, int y) { | |
this.x = x; | |
this.y = y; | |
} | |
} | |
/// <summary> | |
/// (Private) Moves the cursor to the specified screen coordinates. | |
/// <para>See MSDN docs' SetCursorPos()</para> | |
/// </summary> | |
/// <param name="x">The new x-coordinate of the cursor.</param> | |
/// <param name="y">The new y-coordinate of the cursor.</param> | |
/// <returns>Returns true if successful, false otherwise.</returns> | |
[DllImport("User32.dll")] | |
private static extern bool SetCursorPos(int x, int y); | |
/// <summary> | |
/// (Private) Moves the cursor to the specified screen coordinates. | |
/// </summary> | |
/// <param name="position">The new position of the cursor.</param> | |
/// <returns>Returns true if successful, false otherwise.</returns> | |
private static bool SetCursorPos(MousePosition position) { | |
return SetCursorPos(position.x, position.y); | |
} | |
/// <summary> | |
/// Retrieves the position of the mouse cursor, in screen coordinates. | |
/// <para>See MSDN docs' GetCursorPos()</para> | |
/// </summary> | |
/// <param name="position">A pointer to a POINT structure that receives the screen coordinates of the cursor.</param> | |
/// <returns>Returns true if successful or false otherwise.</returns> | |
[DllImport("User32.dll")] | |
private static extern bool GetCursorPos(out MousePosition position); | |
#endregion User32 Cursor Position Manipulation | |
#region Wrapper API for UnityEngine.Cursor | |
/// <summary> | |
/// (Private) Represents the last position the mouse was at before the cursor was locked. | |
/// <para>Used for restoring the mouse to its former position when unlocking the cursor.</para> | |
/// </summary> | |
private static MousePosition lastMousePosition = new MousePosition { x = 0, y = 0 }; | |
/// <summary>Represents and controls the mouse's position on the screen as a Vector2.</summary> | |
public static Vector2 position { | |
get { | |
MousePosition p; | |
if (GetCursorPos(out p)) return new Vector2(p.x, p.y); | |
return Vector2.zero; | |
} | |
set { | |
MousePosition p = new MousePosition((int)value.x, (int)value.y); | |
SetCursorPos(p); | |
} | |
} | |
/// <summary> | |
/// Determines whether the hardware pointer is visible or not. | |
/// <para>See Unity3D docs' UnityEngine.Cursor.visible</para> | |
/// </summary> | |
public static bool visible { | |
get { return Cursor.visible; } | |
set { Cursor.visible = value; } | |
} | |
/// <summary> | |
/// Determines whether the hardware pointer is locked to the center of the view, constrained to the window, or not constrained at all. | |
/// <para>Restores the cursor to its position prior to being locked when you set the lockState to CursorLockMode.None.</para> | |
/// <para>See Unity3D docs' UnityEngine.Cursor.lockState</para> | |
/// </summary> | |
public static CursorLockMode lockState { | |
get { return Cursor.lockState; } | |
set { | |
switch (value) { | |
case CursorLockMode.Locked: | |
if (Cursor.lockState == CursorLockMode.Locked) return; | |
Cursor.visible = false; | |
MousePosition position; | |
if (GetCursorPos(out position)) | |
lastMousePosition = position; | |
Cursor.lockState = CursorLockMode.Locked; | |
break; | |
default: | |
if (Cursor.lockState == CursorLockMode.Locked) { | |
Cursor.lockState = value; | |
Cursor.visible = false; | |
//lastMousePosition.x += (int)Input.GetAxisRaw("Mouse X"); | |
//lastMousePosition.y += (int)-Input.GetAxisRaw("Mouse Y"); | |
SetCursorPos(lastMousePosition); | |
Cursor.visible = true; | |
} else { | |
Cursor.lockState = value; | |
} | |
break; | |
} | |
} | |
} | |
/// <summary> | |
/// Specify a custom cursor that you wish to use as a cursor. | |
/// <para>See Unity3D docs' UnityEngine.Cursor.SetCursor()</para> | |
/// </summary> | |
public static void SetCursor(Texture2D texture, Vector3 hotspot, CursorMode mode) { | |
Cursor.SetCursor(texture, hotspot, mode); | |
} | |
#endregion Wrapper API for UnityEngine.Cursor | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
/// <summary> | |
/// Bonus Script: Fades MeshRenderers in or out depending on how close the mouse orbit camera is. | |
/// </summary> | |
public class PlayerFadeOnOrbitCameraNear : MonoBehaviour { | |
private void Update() { | |
float targetAlpha = 1f; | |
float distance = Camera.main.GetComponent<BetterMouseOrbit>()._distance; | |
if (distance < 1.3f) | |
targetAlpha = 0f; | |
else if (distance < 2) | |
targetAlpha = .5f; | |
var renderers = GetComponentsInChildren<MeshRenderer>(); | |
foreach (var renderer in renderers) { | |
var color = renderer.material.color; | |
color.a = Mathf.MoveTowards(color.a, targetAlpha, 10f * Time.deltaTime); | |
renderer.material.color = color; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment