using System; using NWH.Common.Input; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Serialization; #if UNITY_EDITOR using NWH.NUI; using UnityEditor; #endif namespace NWH.Common.Cameras { /// /// Camera that can be dragged with the mouse. /// public class CameraMouseDrag : VehicleCamera { public enum POVType { FirstPerson, ThirdPerson } /// /// Camera POV type. First person camera will invert controls. /// Zoom is not available in 1st person. /// [UnityEngine.Tooltip("Camera POV type. First person camera will invert controls.\r\nZoom is not available in 1st person.")] public POVType povType = POVType.ThirdPerson; /// /// Can the camera be rotated by the user? /// [UnityEngine.Tooltip(" Can the camera be rotated by the user?")] public bool allowRotation = true; /// /// Can the camera be panned by the user? /// [UnityEngine.Tooltip(" Can the camera be panned by the user?")] public bool allowPanning = true; /// /// Distance from target at which camera will be positioned. Might vary depending on smoothing. /// [Range(0, 100f)] [Tooltip(" Distance from target at which camera will be positioned. Might vary depending on smoothing.")] public float distance = 5f; /// /// If true the camera will rotate with the vehicle along the X and Y axis. /// [FormerlySerializedAs("followTargetsRotation")] [UnityEngine.Tooltip(" If true the camera will rotate with the vehicle along the X and Y axis.")] public bool followTargetPitchAndYaw = true; /// /// If true the camera will rotate with the vehicle along the Z axis. /// [UnityEngine.Tooltip(" If true the camera will rotate with the vehicle along the Z axis.")] public bool followTargetRoll = false; /// /// Maximum distance that will be reached when zooming out. /// [Range(0, 100f)] [Tooltip(" Maximum distance that will be reached when zooming out.")] public float maxDistance = 13.0f; /// /// Minimum distance that will be reached when zooming in. /// [Range(0, 100f)] [Tooltip(" Minimum distance that will be reached when zooming in.")] public float minDistance = 3.0f; /// /// Sensitivity of the middle mouse button / wheel. /// [Range(0, 15)] [Tooltip(" Sensitivity of the middle mouse button / wheel.")] public float zoomSensitivity = 1f; /// /// Smoothing of the camera rotation. /// [Range(0, 1)] [Tooltip(" Smoothing of the camera rotation.")] public float rotationSmoothing = 0.02f; /// /// Maximum vertical angle the camera can achieve. /// [Range(-90, 90)] [UnityEngine.Tooltip(" Maximum vertical angle the camera can achieve.")] public float verticalMaxAngle = 80.0f; /// /// Minimum vertical angle the camera can achieve. /// [Range(-90, 90)] [UnityEngine.Tooltip(" Minimum vertical angle the camera can achieve.")] public float verticalMinAngle = -40.0f; /// /// Sensitivity of rotation input. /// [UnityEngine.Tooltip(" Sensitivity of rotation input.")] public Vector2 rotationSensitivity = new Vector2(3f, 3f); /// /// Sensitivity of panning input. /// [UnityEngine.Tooltip(" Sensitivity of panning input.")] public Vector2 panningSensitivity = new Vector2(0.06f, 0.06f); /// /// Initial rotation around the X axis (up/down) /// [UnityEngine.Tooltip(" Initial rotation around the X axis (up/down)")] public float initXRotation; /// /// Initial rotation around the Y axis (left/right) /// [UnityEngine.Tooltip(" Initial rotation around the Y axis (left/right)")] public float initYRotation; /// /// Look position offset from the target center. /// [UnityEngine.Tooltip(" Look position offset from the target center.")] public Vector3 targetPositionOffset = Vector3.zero; /// /// Should camera movement on acceleration be used? /// [UnityEngine.Tooltip("Should camera movement on acceleration be used?")] public bool useShake = true; /// /// Maximum head movement from the initial position. /// [Range(0f, 1f)] [Tooltip(" Maximum head movement from the initial position.")] public float shakeMaxOffset = 0.2f; /// /// How much will the head move around for the given g-force. /// [Range(0f, 1f)] [Tooltip(" How much will the head move around for the given g-force.")] public float shakeIntensity = 0.125f; /// /// Smoothing of the head movement. /// [Range(0f, 1f)] [Tooltip(" Smoothing of the head movement.")] public float shakeSmoothing = 0.3f; /// /// Movement intensity per axis. Set to 0 to disable movement on that axis or negative to reverse it. /// [UnityEngine.Tooltip(" Movement intensity per axis. Set to 0 to disable movement on that axis or negative to reverse it.")] public Vector3 shakeAxisIntensity = new Vector3(1f, 0.5f, 1f); private Vector3 _lookDir; private Vector3 _newLookDir; private Vector3 _lookDirVel; private Vector3 _lookAtPosition; private Vector2 _rot; private Vector3 _pan; private bool _isFirstFrame; private bool _rotationModifier; private bool _panningModifier; private Vector2 _rotationInput; private Vector2 _panningInput; private float _zoomInput; private Vector3 _acceleration; private Vector3 _prevAcceleration; private Vector3 _accelerationChangeVelocity; private Vector3 _initialPosition; private Vector3 _localAcceleration; private Vector3 _newPositionOffset; private Vector3 _offsetChangeVelocity; private Vector3 _positionOffset; private Rigidbody _rigidbody; private float _rbSpeed; private Vector3 _rbLocalAcceleration; private Vector3 _rbLocalVelocity; private Vector3 _rbPrevLocalVelocity; private bool PointerOverUI { get { return EventSystem.current != null && EventSystem.current.IsPointerOverGameObject(); } } private void Start() { _initialPosition = transform.localPosition; _rigidbody = target?.GetComponent(); distance = Mathf.Clamp(distance, minDistance, maxDistance); _rot.x = initXRotation; _rot.y = initYRotation; _isFirstFrame = true; } private void FixedUpdate() { if (_rigidbody == null) { return; } _rbPrevLocalVelocity = _rbLocalVelocity; _rbLocalVelocity = transform.InverseTransformDirection(_rigidbody.velocity); _rbLocalAcceleration = (_rbLocalVelocity - _rbPrevLocalVelocity) / Time.fixedDeltaTime; _rbSpeed = _rbLocalVelocity.z < 0 ? -_rbLocalVelocity.z : _rbLocalVelocity.z; } private void LateUpdate() { if (target == null) { return; } // Handle input if (!PointerOverUI) { _rotationInput = InputProvider.CombinedInput(i => i.CameraRotation()); _panningInput = InputProvider.CombinedInput(i => i.CameraPanning()); _zoomInput = InputProvider.CombinedInput(i => i.CameraZoom()); _rotationModifier = InputProvider.CombinedInput(i => i.CameraRotationModifier()); _panningModifier = InputProvider.CombinedInput(i => i.CameraPanningModifier()); if (allowRotation && _rotationModifier) { _rot.y += _rotationInput.x * rotationSensitivity.x; _rot.x -= _rotationInput.y * rotationSensitivity.y; } if (allowPanning && _panningModifier) { float pX = _panningInput.x * panningSensitivity.x; float pY = _panningInput.y * panningSensitivity.y; _pan -= target.InverseTransformDirection(transform.right * pX); _pan -= target.InverseTransformDirection(transform.up * pY); } _rot.x = ClampAngle(_rot.x, verticalMinAngle, verticalMaxAngle); if (povType == POVType.ThirdPerson && (_zoomInput > 0.0001f || _zoomInput < -0.0001f)) { distance -= _zoomInput * zoomSensitivity; } } Vector3 forwardVector = followTargetPitchAndYaw ? target.forward : Vector3.forward; Vector3 rightVector = followTargetPitchAndYaw ? target.right : Vector3.right; Vector3 upVector = followTargetPitchAndYaw ? target.up : Vector3.up; _lookAtPosition = target.position + target.TransformDirection(targetPositionOffset + _pan); _newLookDir = Quaternion.AngleAxis(_rot.x, rightVector) * forwardVector; _newLookDir = Quaternion.AngleAxis(_rot.y, upVector) * _newLookDir; _lookDir = _isFirstFrame ? _newLookDir : Vector3.SmoothDamp(_lookDir, _newLookDir, ref _lookDirVel, rotationSmoothing); _lookDir = Vector3.Normalize(_lookDir); if (povType == POVType.ThirdPerson) { distance = povType == POVType.FirstPerson ? 0 : Mathf.Clamp(distance, minDistance, maxDistance); Vector3 targetPosition = _lookAtPosition - _lookDir * distance; transform.position = targetPosition; transform.forward = _lookDir; // Check for ground if (Physics.Raycast(transform.position, -Vector3.up, out RaycastHit hit, 0.5f)) { transform.position = hit.point + Vector3.up * 0.5f; } transform.rotation = Quaternion.LookRotation(_lookDir, followTargetRoll ? target.up : Vector3.up); } else { transform.localPosition = _initialPosition + _pan; transform.rotation = Quaternion.LookRotation(_lookDir, followTargetRoll ? target.up : Vector3.up); } // Movement effect _prevAcceleration = _acceleration; _acceleration = _rbLocalAcceleration; _localAcceleration = Vector3.zero; if (target != null) { _localAcceleration = target.TransformDirection(_acceleration); } if (!_isFirstFrame) { _newPositionOffset = Vector3.SmoothDamp(_prevAcceleration, _localAcceleration, ref _accelerationChangeVelocity, shakeSmoothing) / 100f * shakeIntensity; _newPositionOffset = Vector3.Scale(_newPositionOffset, shakeAxisIntensity); _positionOffset = Vector3.SmoothDamp(_positionOffset, _newPositionOffset, ref _offsetChangeVelocity, shakeSmoothing); _positionOffset = Vector3.ClampMagnitude(_positionOffset, shakeMaxOffset); transform.position -= target.TransformDirection(_positionOffset) * Mathf.Clamp01(_rbSpeed * 0.5f); } _isFirstFrame = false; } public void OnDrawGizmosSelected() { Gizmos.DrawWireSphere(_lookAtPosition, 0.1f); Gizmos.DrawRay(_lookAtPosition, _lookDir); } private void OnEnable() { _isFirstFrame = true; } public float ClampAngle(float angle, float min, float max) { while (angle < -360 || angle > 360) { if (angle < -360) { angle += 360; } if (angle > 360) { angle -= 360; } } return Mathf.Clamp(angle, min, max); } } } #if UNITY_EDITOR namespace NWH.Common.Cameras { [CustomEditor(typeof(CameraMouseDrag))] [CanEditMultipleObjects] public class CameraMouseDragEditor : NUIEditor { public override bool OnInspectorNUI() { if (!base.OnInspectorNUI()) { return false; } CameraMouseDrag.POVType povType = ((CameraMouseDrag)target).povType; drawer.Field("target"); drawer.BeginSubsection("POV"); drawer.Field("povType"); drawer.EndSubsection(); if (povType == CameraMouseDrag.POVType.ThirdPerson) { drawer.BeginSubsection("Distance & Position"); drawer.Field("distance"); drawer.Field("minDistance"); drawer.Field("maxDistance"); drawer.Field("zoomSensitivity"); drawer.Field("targetPositionOffset"); drawer.EndSubsection(); } drawer.BeginSubsection("Rotation"); drawer.Field("allowRotation"); drawer.Field("followTargetPitchAndYaw"); drawer.Field("followTargetRoll"); drawer.Field("rotationSensitivity"); drawer.Field("verticalMaxAngle"); drawer.Field("verticalMinAngle"); drawer.Field("initXRotation"); drawer.Field("initYRotation"); drawer.Field("rotationSmoothing"); drawer.EndSubsection(); drawer.BeginSubsection("Panning"); if (drawer.Field("allowPanning").boolValue) { drawer.Field("panningSensitivity"); } drawer.EndSubsection(); drawer.BeginSubsection("Camera Shake"); drawer.Info("Movement introduced as a result of acceleration."); if (drawer.Field("useShake").boolValue) { drawer.Field("shakeMaxOffset"); drawer.Field("shakeIntensity"); drawer.Field("shakeSmoothing"); drawer.Field("shakeAxisIntensity"); } drawer.EndSubsection(); drawer.EndEditor(this); return true; } public override bool UseDefaultMargins() { return false; } } } #endif