657 lines
30 KiB
C#
657 lines
30 KiB
C#
// ╔════════════════════════════════════════════════════════════════╗
|
|
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
|
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
|
// ║ https://unity.com/legal/as-terms ║
|
|
// ║ Use permitted only in compliance with the License. ║
|
|
// ║ Distributed "AS IS", without warranty of any kind. ║
|
|
// ╚════════════════════════════════════════════════════════════════╝
|
|
|
|
#region
|
|
|
|
using NWH.Common.Input;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.Serialization;
|
|
#if UNITY_EDITOR
|
|
using NWH.NUI;
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
namespace NWH.Common.Cameras
|
|
{
|
|
/// <summary>
|
|
/// Camera that can be dragged with the mouse. Supports Forza-style dynamics.
|
|
/// </summary>
|
|
public class CameraMouseDrag : VehicleCamera
|
|
{
|
|
public enum POVType
|
|
{
|
|
FirstPerson,
|
|
ThirdPerson,
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// POV & Basic Settings
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
[Tooltip("Camera POV type. First person camera will invert controls.\r\nZoom is not available in 1st person.")]
|
|
public POVType povType = POVType.ThirdPerson;
|
|
|
|
[Tooltip("Can the camera be rotated by the user?")]
|
|
public bool allowRotation = true;
|
|
|
|
[Tooltip("Can the camera be panned by the user?")]
|
|
public bool allowPanning = true;
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Distance & Position
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
[Range(0, 100f)]
|
|
[Tooltip("Base distance from target at which camera will be positioned.")]
|
|
public float distance = 6f;
|
|
|
|
[Range(0, 100f)]
|
|
[Tooltip("Minimum distance that will be reached when zooming in.")]
|
|
public float minDistance = 3.0f;
|
|
|
|
[Range(0, 100f)]
|
|
[Tooltip("Maximum distance that will be reached when zooming out.")]
|
|
public float maxDistance = 13.0f;
|
|
|
|
[Range(0, 15)]
|
|
[Tooltip("Sensitivity of the middle mouse button / wheel.")]
|
|
public float zoomSensitivity = 1f;
|
|
|
|
[Tooltip("Look position offset from the target center.")]
|
|
public Vector3 targetPositionOffset = Vector3.zero;
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Rotation
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
[FormerlySerializedAs("followTargetsRotation")]
|
|
[Tooltip("If true the camera will rotate with the vehicle along the X and Y axis.")]
|
|
public bool followTargetPitchAndYaw = true;
|
|
|
|
[Tooltip("If true the camera will rotate with the vehicle along the Z axis.")]
|
|
public bool followTargetRoll;
|
|
|
|
[Tooltip("Sensitivity of rotation input.")]
|
|
public Vector2 rotationSensitivity = new(3f, 3f);
|
|
|
|
[Range(-90, 90)]
|
|
[Tooltip("Maximum vertical angle the camera can achieve.")]
|
|
public float verticalMaxAngle = 80.0f;
|
|
|
|
[Range(-90, 90)]
|
|
[Tooltip("Minimum vertical angle the camera can achieve.")]
|
|
public float verticalMinAngle = -20.0f;
|
|
|
|
[Tooltip("Initial rotation around the X axis (up/down)")]
|
|
public float initXRotation = 10f;
|
|
|
|
[Tooltip("Initial rotation around the Y axis (left/right)")]
|
|
public float initYRotation;
|
|
|
|
[Range(0, 1)]
|
|
[Tooltip("Smoothing of the camera rotation.")]
|
|
public float rotationSmoothing = 0.08f;
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Panning
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
[Tooltip("Sensitivity of panning input.")]
|
|
public Vector2 panningSensitivity = new(0.06f, 0.06f);
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Auto-Centering (Third-Person Only)
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
[Tooltip("Camera gradually returns behind vehicle after manual rotation stops.")]
|
|
public bool useAutoCenter = true;
|
|
|
|
[Range(0f, 5f)]
|
|
[Tooltip("Seconds of no rotation input before auto-centering starts.")]
|
|
public float autoCenterDelay = 1.5f;
|
|
|
|
[Range(0.5f, 10f)]
|
|
[Tooltip("Speed at which camera returns to center. Higher = faster.")]
|
|
public float autoCenterSpeed = 2f;
|
|
|
|
[Range(0f, 10f)]
|
|
[Tooltip("Minimum vehicle speed (m/s) required for auto-centering. Prevents centering when stationary.")]
|
|
public float autoCenterMinSpeed = 2f;
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Speed-Based FOV (Third-Person Only)
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
[Tooltip("Dynamically adjust FOV based on vehicle speed.")]
|
|
public bool useSpeedFOV = true;
|
|
|
|
[Range(30f, 90f)]
|
|
[Tooltip("Field of view when stationary.")]
|
|
public float baseFOV = 60f;
|
|
|
|
[Range(30f, 120f)]
|
|
[Tooltip("Maximum field of view at high speed.")]
|
|
public float maxFOV = 75f;
|
|
|
|
[Range(10f, 200f)]
|
|
[Tooltip("Speed (m/s) at which maximum FOV is reached.")]
|
|
public float fovSpeedRange = 50f;
|
|
|
|
[Range(0f, 1f)]
|
|
[Tooltip("Smoothing applied to FOV transitions.")]
|
|
public float fovSmoothing = 0.3f;
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Speed-Based Distance (Third-Person Only)
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
[Tooltip("Camera pulls back at higher speeds.")]
|
|
public bool useSpeedDistance = true;
|
|
|
|
[Range(0f, 0.2f)]
|
|
[Tooltip("Extra distance added per m/s of speed.")]
|
|
public float speedDistanceMultiplier = 0.05f;
|
|
|
|
[Range(0f, 10f)]
|
|
[Tooltip("Maximum additional distance from speed.")]
|
|
public float maxSpeedDistance = 3f;
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Speed-Based Height (Third-Person Only)
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
[Tooltip("Camera rises at higher speeds.")]
|
|
public bool useSpeedHeight = true;
|
|
|
|
[Range(0f, 0.1f)]
|
|
[Tooltip("Extra height added per m/s of speed.")]
|
|
public float speedHeightMultiplier = 0.02f;
|
|
|
|
[Range(0f, 5f)]
|
|
[Tooltip("Maximum additional height from speed.")]
|
|
public float maxSpeedHeight = 1f;
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Look-Ahead (Third-Person Only)
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
[Tooltip("Camera anticipates turns by rotating into the direction of steering.")]
|
|
public bool useLookAhead = true;
|
|
|
|
[Range(0f, 30f)]
|
|
[Tooltip("Maximum degrees of yaw offset when turning.")]
|
|
public float lookAheadIntensity = 15f;
|
|
|
|
[Range(0f, 1f)]
|
|
[Tooltip("Smoothing applied to look-ahead. Higher = slower response.")]
|
|
public float lookAheadSmoothing = 0.2f;
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Camera Shake
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
[Tooltip("Should camera movement on acceleration be used?")]
|
|
public bool useShake = true;
|
|
|
|
[Range(0f, 1f)]
|
|
[Tooltip("Maximum head movement from the initial position.")]
|
|
public float shakeMaxOffset = 0.2f;
|
|
|
|
[Range(0f, 1f)]
|
|
[Tooltip("How much will the head move around for the given g-force.")]
|
|
public float shakeIntensity = 0.125f;
|
|
|
|
[Range(0f, 1f)]
|
|
[Tooltip("Smoothing of the head movement.")]
|
|
public float shakeSmoothing = 0.3f;
|
|
|
|
[Tooltip("Movement intensity per axis. Set to 0 to disable movement on that axis or negative to reverse it.")]
|
|
public Vector3 shakeAxisIntensity = new(1f, 0.5f, 1f);
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Private Fields
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
// Core
|
|
private Vector3 _initialPosition;
|
|
private bool _isFirstFrame;
|
|
private Rigidbody _rigidbody;
|
|
private Camera _camera;
|
|
|
|
// Rotation
|
|
private Vector2 _rot;
|
|
private Vector3 _lookDir;
|
|
private Vector3 _lookDirVel;
|
|
private Vector3 _newLookDir;
|
|
private Vector3 _lookAtPosition;
|
|
private Vector3 _pan;
|
|
|
|
// Velocity tracking
|
|
private Vector3 _rbLocalVelocity;
|
|
private Vector3 _rbPrevLocalVelocity;
|
|
private Vector3 _rbLocalAcceleration;
|
|
private float _rbSpeed;
|
|
|
|
// Shake
|
|
private Vector3 _acceleration;
|
|
private Vector3 _accelerationChangeVelocity;
|
|
private Vector3 _localAcceleration;
|
|
private Vector3 _newPositionOffset;
|
|
private Vector3 _offsetChangeVelocity;
|
|
private Vector3 _positionOffset;
|
|
private Vector3 _prevAcceleration;
|
|
|
|
// Auto-center
|
|
private float _timeSinceLastRotationInput;
|
|
|
|
// Speed dynamics
|
|
private float _currentFOV;
|
|
private float _fovVelocity;
|
|
private float _speedDistanceOffset;
|
|
private float _speedDistanceVelocity;
|
|
private float _speedHeightOffset;
|
|
private float _speedHeightVelocity;
|
|
|
|
// Look-ahead
|
|
private float _lookAheadOffset;
|
|
private float _lookAheadVelocity;
|
|
|
|
private bool PointerOverUI
|
|
{
|
|
get
|
|
{
|
|
return EventSystem.current != null &&
|
|
EventSystem.current.IsPointerOverGameObject();
|
|
}
|
|
}
|
|
|
|
|
|
private void Start()
|
|
{
|
|
_initialPosition = transform.localPosition;
|
|
_rigidbody = target?.GetComponent<Rigidbody>();
|
|
_camera = GetComponent<Camera>();
|
|
|
|
distance = Mathf.Clamp(distance, minDistance, maxDistance);
|
|
|
|
_rot.x = initXRotation;
|
|
_rot.y = initYRotation;
|
|
_isFirstFrame = true;
|
|
|
|
// Initialize FOV
|
|
_currentFOV = baseFOV;
|
|
if (_camera != null)
|
|
{
|
|
_camera.fieldOfView = baseFOV;
|
|
}
|
|
}
|
|
|
|
|
|
private void FixedUpdate()
|
|
{
|
|
if (_rigidbody == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_rbPrevLocalVelocity = _rbLocalVelocity;
|
|
_rbLocalVelocity = transform.InverseTransformDirection(_rigidbody.linearVelocity);
|
|
if (Time.fixedDeltaTime > 0f)
|
|
{
|
|
_rbLocalAcceleration = (_rbLocalVelocity - _rbPrevLocalVelocity) / Time.fixedDeltaTime;
|
|
}
|
|
_rbSpeed = _rbLocalVelocity.z < 0 ? -_rbLocalVelocity.z : _rbLocalVelocity.z;
|
|
}
|
|
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (target == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool isThirdPerson = povType == POVType.ThirdPerson;
|
|
bool hadRotationInput = false;
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Input Phase
|
|
// ═══════════════════════════════════════════════════════════════
|
|
bool pointerOverUI = PointerOverUI;
|
|
if (!pointerOverUI)
|
|
{
|
|
Vector2 rotationInput = InputProvider.CombinedInput<SceneInputProviderBase>(i => i.CameraRotation());
|
|
Vector2 panningInput = InputProvider.CombinedInput<SceneInputProviderBase>(i => i.CameraPanning());
|
|
float zoomInput = InputProvider.CombinedInput<SceneInputProviderBase>(i => i.CameraZoom());
|
|
bool rotationModifier =
|
|
InputProvider.CombinedInput<SceneInputProviderBase>(i => i.CameraRotationModifier());
|
|
bool panningModifier = InputProvider.CombinedInput<SceneInputProviderBase>(i => i.CameraPanningModifier());
|
|
|
|
if (allowRotation && rotationModifier)
|
|
{
|
|
float rotMagnitude = rotationInput.sqrMagnitude;
|
|
if (rotMagnitude > 0.001f)
|
|
{
|
|
_rot.y += rotationInput.x * rotationSensitivity.x;
|
|
_rot.x -= rotationInput.y * rotationSensitivity.y;
|
|
hadRotationInput = true;
|
|
_timeSinceLastRotationInput = 0f;
|
|
}
|
|
}
|
|
|
|
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 (isThirdPerson && (zoomInput > 0.0001f || zoomInput < -0.0001f))
|
|
{
|
|
distance -= zoomInput * zoomSensitivity;
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Auto-Center Phase (Third-Person Only)
|
|
// ═══════════════════════════════════════════════════════════════
|
|
if (isThirdPerson && useAutoCenter && !hadRotationInput)
|
|
{
|
|
_timeSinceLastRotationInput += Time.deltaTime;
|
|
|
|
if (_timeSinceLastRotationInput > autoCenterDelay && _rbSpeed > autoCenterMinSpeed)
|
|
{
|
|
float centerLerp = autoCenterSpeed * Time.deltaTime;
|
|
_rot.x = Mathf.Lerp(_rot.x, initXRotation, centerLerp);
|
|
_rot.y = Mathf.Lerp(_rot.y, initYRotation, centerLerp);
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Look-Ahead Phase (Third-Person Only)
|
|
// ═══════════════════════════════════════════════════════════════
|
|
float effectiveLookAheadOffset = 0f;
|
|
if (isThirdPerson && useLookAhead && _rigidbody != null)
|
|
{
|
|
// Use angular velocity for turn anticipation
|
|
float angularVelY = _rigidbody.angularVelocity.y;
|
|
// Also factor in lateral velocity for drifts
|
|
float lateralVel = Vector3.Dot(_rigidbody.linearVelocity, target.right);
|
|
float turnFactor = angularVelY * 2f + lateralVel * 0.1f;
|
|
|
|
float targetLookAhead = Mathf.Clamp(turnFactor * lookAheadIntensity, -lookAheadIntensity, lookAheadIntensity);
|
|
_lookAheadOffset = Mathf.SmoothDamp(_lookAheadOffset, targetLookAhead, ref _lookAheadVelocity, lookAheadSmoothing);
|
|
effectiveLookAheadOffset = _lookAheadOffset;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Speed Dynamics Phase (Third-Person Only)
|
|
// ═══════════════════════════════════════════════════════════════
|
|
float effectiveDistance = distance;
|
|
Vector3 effectiveTargetOffset = targetPositionOffset;
|
|
|
|
if (isThirdPerson)
|
|
{
|
|
// Speed-based FOV
|
|
if (useSpeedFOV && _camera != null)
|
|
{
|
|
float speedNormalized = Mathf.Clamp01(_rbSpeed / fovSpeedRange);
|
|
float targetFOV = Mathf.Lerp(baseFOV, maxFOV, speedNormalized);
|
|
_currentFOV = Mathf.SmoothDamp(_currentFOV, targetFOV, ref _fovVelocity, fovSmoothing);
|
|
_camera.fieldOfView = _currentFOV;
|
|
}
|
|
|
|
// Speed-based distance
|
|
if (useSpeedDistance)
|
|
{
|
|
float targetSpeedDistance = Mathf.Min(_rbSpeed * speedDistanceMultiplier, maxSpeedDistance);
|
|
_speedDistanceOffset = Mathf.SmoothDamp(_speedDistanceOffset, targetSpeedDistance, ref _speedDistanceVelocity, 0.2f);
|
|
effectiveDistance = distance + _speedDistanceOffset;
|
|
}
|
|
|
|
// Speed-based height
|
|
if (useSpeedHeight)
|
|
{
|
|
float targetSpeedHeight = Mathf.Min(_rbSpeed * speedHeightMultiplier, maxSpeedHeight);
|
|
_speedHeightOffset = Mathf.SmoothDamp(_speedHeightOffset, targetSpeedHeight, ref _speedHeightVelocity, 0.2f);
|
|
effectiveTargetOffset.y += _speedHeightOffset;
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Position/Rotation Calculation
|
|
// ═══════════════════════════════════════════════════════════════
|
|
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(effectiveTargetOffset + _pan);
|
|
|
|
// Apply look-ahead to yaw
|
|
float effectiveYaw = _rot.y + effectiveLookAheadOffset;
|
|
|
|
_newLookDir = Quaternion.AngleAxis(_rot.x, rightVector) * forwardVector;
|
|
_newLookDir = Quaternion.AngleAxis(effectiveYaw, upVector) * _newLookDir;
|
|
|
|
_lookDir = _isFirstFrame
|
|
? _newLookDir
|
|
: Vector3.SmoothDamp(_lookDir, _newLookDir, ref _lookDirVel, rotationSmoothing);
|
|
_lookDir = Vector3.Normalize(_lookDir);
|
|
|
|
if (isThirdPerson)
|
|
{
|
|
effectiveDistance = Mathf.Clamp(effectiveDistance, minDistance, maxDistance + maxSpeedDistance);
|
|
|
|
Vector3 targetPosition = _lookAtPosition - _lookDir * effectiveDistance;
|
|
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);
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// Camera Shake Phase
|
|
// ═══════════════════════════════════════════════════════════════
|
|
if (useShake)
|
|
{
|
|
_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)
|
|
{
|
|
angle = Mathf.Repeat(angle + 180f, 360f) - 180f;
|
|
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 cam = (CameraMouseDrag)target;
|
|
bool isThirdPerson = cam.povType == CameraMouseDrag.POVType.ThirdPerson;
|
|
|
|
drawer.Field("target");
|
|
|
|
drawer.BeginSubsection("POV");
|
|
drawer.Field("povType");
|
|
drawer.EndSubsection();
|
|
|
|
if (isThirdPerson)
|
|
{
|
|
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();
|
|
|
|
// Third-person only features
|
|
if (isThirdPerson)
|
|
{
|
|
drawer.BeginSubsection("Auto-Centering");
|
|
drawer.Info("Camera returns behind vehicle after manual rotation stops.");
|
|
if (drawer.Field("useAutoCenter").boolValue)
|
|
{
|
|
drawer.Field("autoCenterDelay");
|
|
drawer.Field("autoCenterSpeed");
|
|
drawer.Field("autoCenterMinSpeed");
|
|
}
|
|
drawer.EndSubsection();
|
|
|
|
drawer.BeginSubsection("Speed-Based FOV");
|
|
drawer.Info("FOV increases at high speeds for sense of velocity.");
|
|
if (drawer.Field("useSpeedFOV").boolValue)
|
|
{
|
|
drawer.Field("baseFOV");
|
|
drawer.Field("maxFOV");
|
|
drawer.Field("fovSpeedRange");
|
|
drawer.Field("fovSmoothing");
|
|
}
|
|
drawer.EndSubsection();
|
|
|
|
drawer.BeginSubsection("Speed-Based Distance");
|
|
drawer.Info("Camera pulls back at higher speeds.");
|
|
if (drawer.Field("useSpeedDistance").boolValue)
|
|
{
|
|
drawer.Field("speedDistanceMultiplier");
|
|
drawer.Field("maxSpeedDistance");
|
|
}
|
|
drawer.EndSubsection();
|
|
|
|
drawer.BeginSubsection("Speed-Based Height");
|
|
drawer.Info("Camera rises at higher speeds.");
|
|
if (drawer.Field("useSpeedHeight").boolValue)
|
|
{
|
|
drawer.Field("speedHeightMultiplier");
|
|
drawer.Field("maxSpeedHeight");
|
|
}
|
|
drawer.EndSubsection();
|
|
|
|
drawer.BeginSubsection("Look-Ahead");
|
|
drawer.Info("Camera anticipates turns by looking into corners.");
|
|
if (drawer.Field("useLookAhead").boolValue)
|
|
{
|
|
drawer.Field("lookAheadIntensity");
|
|
drawer.Field("lookAheadSmoothing");
|
|
}
|
|
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 |