using UnityEngine; using System.Collections; using UnityEngine.Events; namespace MoreMountains.Tools { /// /// A class used to make a camera orbit around a target /// [AddComponentMenu("More Mountains/Tools/Camera/MMOrbitalCamera")] public class MMOrbitalCamera : MonoBehaviour { /// the possible input modes for this camera public enum Modes { Mouse, Touch } [Header("Setup")] /// the selected input mode public Modes Mode = Modes.Touch; /// the object to orbit around public Transform Target; /// the offset to apply while orbiting public Vector3 TargetOffset; /// the current distance to target [MMReadOnly] public float DistanceToTarget = 5f; [Header("Rotation")] /// whether or not rotation is enabled public bool RotationEnabled = true; /// the speed of the rotation public Vector2 RotationSpeed = new Vector2(200f, 200f); /// the minimum vertical angle limit public int MinVerticalAngleLimit = -80; /// the maximum vertical angle limit public int MaxVerticalAngleLimit = 80; [Header("Zoom")] /// whether or not zoom is enabled public bool ZoomEnabled = true; /// the minimum distance at which the user can zoom in public float MinimumZoomDistance = 0.6f; /// the max distance at which the user can zoom out public float MaximumZoomDistance = 20; /// the speed of the zoom interpolation public int ZoomSpeed = 40; /// the dampening to apply to the zoom public float ZoomDampening = 5f; [Header("Mouse Zoom")] /// the speed at which scrolling the mouse wheel will zoom public float MouseWheelSpeed = 10f; /// the max value at which to clamp the mouse wheel public float MaxMouseWheelClamp = 10f; [Header("Steps")] /// the distance after which to trigger a step public float StepThreshold = 1; /// an event to trigger when a step is met public UnityEvent StepFeedback; protected float _angleX = 0f; protected float _angleY = 0f; protected float _currentDistance; protected float _desiredDistance; protected Quaternion _currentRotation; protected Quaternion _desiredRotation; protected Quaternion _rotation; protected Vector3 _position; protected float _scrollWheelAmount = 0; protected float _stepBuffer = 0f; /// /// On Start we initialize our orbital camera /// protected virtual void Start() { Initialization(); } /// /// On init we store our positions and rotations /// public virtual void Initialization() { // if no target is set, we throw an error and exit if (Target == null) { Debug.LogError(this.gameObject.name + " : the MMOrbitalCamera doesn't have a target."); return; } DistanceToTarget = Vector3.Distance(Target.position, transform.position); _currentDistance = DistanceToTarget; _desiredDistance = DistanceToTarget; _position = transform.position; _rotation = transform.rotation; _currentRotation = transform.rotation; _desiredRotation = transform.rotation; _angleX = Vector3.Angle(Vector3.right, transform.right); _angleY = Vector3.Angle(Vector3.up, transform.up); } /// /// On late update we rotate, zoom, detect steps and finally apply our movement /// protected virtual void LateUpdate() { if (Target == null) { return; } Rotation(); Zoom(); StepDetection(); ApplyMovement(); } /// /// Rotates the camera around the object /// protected virtual void Rotation() { if (!RotationEnabled) { return; } if (Mode == Modes.Touch && (Input.touchCount > 0)) { if ((Input.touches[0].phase == TouchPhase.Moved) && (Input.touchCount == 1)) { float screenHeight = Screen.currentResolution.height; if (Input.touches[0].position.y < screenHeight/4) { return; } float swipeSpeed = Input.touches[0].deltaPosition.magnitude / Input.touches[0].deltaTime; _angleX += Input.touches[0].deltaPosition.x * RotationSpeed.x * Time.deltaTime * swipeSpeed * 0.00001f; _angleY -= Input.touches[0].deltaPosition.y * RotationSpeed.y * Time.deltaTime * swipeSpeed * 0.00001f; _stepBuffer += Input.touches[0].deltaPosition.x; _angleY = MMMaths.ClampAngle(_angleY, MinVerticalAngleLimit, MaxVerticalAngleLimit); _desiredRotation = Quaternion.Euler(_angleY, _angleX, 0); _currentRotation = transform.rotation; _rotation = Quaternion.Lerp(_currentRotation, _desiredRotation, Time.deltaTime * ZoomDampening); transform.rotation = _rotation; } else if (Input.touchCount == 1 && Input.touches[0].phase == TouchPhase.Began) { _desiredRotation = transform.rotation; } if (transform.rotation != _desiredRotation) { _rotation = Quaternion.Lerp(transform.rotation, _desiredRotation, Time.deltaTime * ZoomDampening); transform.rotation = _rotation; } } else if (Mode == Modes.Mouse) { _angleX += Input.GetAxis("Mouse X") * RotationSpeed.x * Time.deltaTime; _angleY += -Input.GetAxis("Mouse Y") * RotationSpeed.y * Time.deltaTime; _angleY = Mathf.Clamp(_angleY, MinVerticalAngleLimit, MaxVerticalAngleLimit); _desiredRotation = Quaternion.Euler(new Vector3(_angleY, _angleX, 0)); _currentRotation = transform.rotation; _rotation = Quaternion.Lerp(_currentRotation, _desiredRotation, Time.deltaTime * ZoomDampening); transform.rotation = _rotation; } } /// /// Detects steps /// protected virtual void StepDetection() { if (Mathf.Abs(_stepBuffer) > StepThreshold) { StepFeedback?.Invoke(); _stepBuffer = 0f; } } /// /// Zooms /// protected virtual void Zoom() { if (!ZoomEnabled) { return; } if (Mode == Modes.Touch && (Input.touchCount > 0)) { if (Input.touchCount == 2) { Touch firstTouch = Input.GetTouch(0); Touch secondTouch = Input.GetTouch(1); Vector2 firstTouchPreviousPosition = firstTouch.position - firstTouch.deltaPosition; Vector2 secondTouchPreviousPosition = secondTouch.position - secondTouch.deltaPosition; float previousTouchDeltaMagnitude = (firstTouchPreviousPosition - secondTouchPreviousPosition).magnitude; float thisTouchDeltaMagnitude = (firstTouch.position - secondTouch.position).magnitude; float deltaMagnitudeDifference = previousTouchDeltaMagnitude - thisTouchDeltaMagnitude; _desiredDistance += deltaMagnitudeDifference * Time.deltaTime * ZoomSpeed * Mathf.Abs(_desiredDistance) * 0.001f; _desiredDistance = Mathf.Clamp(_desiredDistance, MinimumZoomDistance, MaximumZoomDistance); _currentDistance = Mathf.Lerp(_currentDistance, _desiredDistance, Time.deltaTime * ZoomDampening); } } else if (Mode == Modes.Mouse) { _scrollWheelAmount += - Input.GetAxis("Mouse ScrollWheel") * MouseWheelSpeed; _scrollWheelAmount = Mathf.Clamp(_scrollWheelAmount, -MaxMouseWheelClamp, MaxMouseWheelClamp); float deltaMagnitudeDifference = _scrollWheelAmount; _desiredDistance += deltaMagnitudeDifference * Time.deltaTime * ZoomSpeed * Mathf.Abs(_desiredDistance) * 0.001f; _desiredDistance = Mathf.Clamp(_desiredDistance, MinimumZoomDistance, MaximumZoomDistance); _currentDistance = Mathf.Lerp(_currentDistance, _desiredDistance, Time.deltaTime * ZoomDampening); } } /// /// Moves the transform /// protected virtual void ApplyMovement() { _position = Target.position - (_rotation * Vector3.forward * _currentDistance + TargetOffset); transform.position = _position; } } }