216 lines
5.5 KiB
C#
216 lines
5.5 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
namespace SK.Framework
|
||
{
|
||
|
||
/// <summary>
|
||
/// 观察相机组件
|
||
/// </summary>
|
||
[DisallowMultipleComponent]
|
||
public class RoamCameraController : MonoBehaviour
|
||
{
|
||
#region Inspector
|
||
|
||
[Header("Target")]
|
||
[Tooltip("观察物体不可以为空")]
|
||
public Transform target;
|
||
public Vector3 targetOffset = Vector3.zero;
|
||
|
||
[Header("Distance")]
|
||
public float distance = 5f;
|
||
public float minDistance = 2f;
|
||
public float maxDistance = 15f;
|
||
public float zoomSpeed = 2f;
|
||
|
||
[Header("Rotation")]
|
||
public float xSpeed = 120f;
|
||
public float ySpeed = 80f;
|
||
public float minYAngle = -30f;
|
||
public float maxYAngle = 80f;
|
||
|
||
[Header("Damping")]
|
||
public float rotationDamping = 8f;
|
||
public float zoomDamping = 8f;
|
||
public float positionDamping = 10f;
|
||
|
||
[Header("Control")]
|
||
public bool enableControl = true;
|
||
[Header("鼠标的键值(0左键,1右键)")]
|
||
public int rotateMouseButton = 0; // 0 = Left Mouse Button
|
||
|
||
#endregion
|
||
|
||
#region Runtime State
|
||
|
||
private float currentX;
|
||
private float currentY;
|
||
|
||
private float targetDistance;
|
||
private float smoothX;
|
||
private float smoothY;
|
||
private float smoothDistance;
|
||
|
||
private bool hasReportedNullTargetError = false;
|
||
|
||
#endregion
|
||
|
||
#region Unity Lifecycle
|
||
|
||
void Start()
|
||
{
|
||
Vector3 angles = transform.eulerAngles;
|
||
currentX = angles.y;
|
||
currentY = angles.x;
|
||
|
||
targetDistance = Mathf.Clamp(distance, minDistance, maxDistance);
|
||
smoothDistance = targetDistance;
|
||
}
|
||
|
||
void LateUpdate()
|
||
{
|
||
if (!enableControl)
|
||
return;
|
||
|
||
if (target == null)
|
||
{
|
||
ReportNullTargetErrorOnce();
|
||
return; //停止
|
||
}
|
||
|
||
HandleInput();
|
||
UpdateCamera();
|
||
}
|
||
|
||
#if UNITY_EDITOR
|
||
void OnValidate()
|
||
{
|
||
if (target == null)
|
||
{
|
||
Debug.LogError(
|
||
"[RoamCameraController] Target 没有被设置!",
|
||
this
|
||
);
|
||
}
|
||
|
||
if (minDistance > maxDistance)
|
||
{
|
||
minDistance = maxDistance;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
#endregion
|
||
|
||
#region Core Logic
|
||
|
||
void HandleInput()
|
||
{
|
||
if (Input.GetMouseButton(rotateMouseButton))
|
||
{
|
||
currentX += Input.GetAxis("Mouse X") * xSpeed * 0.02f;
|
||
currentY -= Input.GetAxis("Mouse Y") * ySpeed * 0.02f;
|
||
}
|
||
|
||
currentY = ClampAngle(currentY, minYAngle, maxYAngle);
|
||
|
||
float scroll = Input.GetAxis("Mouse ScrollWheel");
|
||
if (Mathf.Abs(scroll) > 0.001f)
|
||
{
|
||
targetDistance -= scroll * zoomSpeed;
|
||
targetDistance = Mathf.Clamp(targetDistance, minDistance, maxDistance);
|
||
}
|
||
}
|
||
|
||
void UpdateCamera()
|
||
{
|
||
smoothX = Mathf.LerpAngle(smoothX, currentX, Time.deltaTime * rotationDamping);
|
||
smoothY = Mathf.LerpAngle(smoothY, currentY, Time.deltaTime * rotationDamping);
|
||
smoothDistance = Mathf.Lerp(
|
||
smoothDistance,
|
||
targetDistance,
|
||
Time.deltaTime * zoomDamping
|
||
);
|
||
|
||
Quaternion rotation = Quaternion.Euler(smoothY, smoothX, 0f);
|
||
Vector3 dir = rotation * Vector3.back * smoothDistance;
|
||
|
||
Vector3 targetPos = target.position + targetOffset;
|
||
Vector3 desiredPos = targetPos + dir;
|
||
|
||
transform.position = Vector3.Lerp(
|
||
transform.position,
|
||
desiredPos,
|
||
Time.deltaTime * positionDamping
|
||
);
|
||
|
||
transform.rotation = rotation;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Public API
|
||
|
||
/// <summary>
|
||
/// Switch observed target.
|
||
/// </summary>
|
||
public void SetTarget(Transform newTarget, bool instant = true)
|
||
{
|
||
target = newTarget;
|
||
|
||
if (target == null)
|
||
{
|
||
ReportNullTargetErrorOnce();
|
||
return;
|
||
}
|
||
|
||
// 恢复正常状态
|
||
hasReportedNullTargetError = false;
|
||
|
||
Vector3 targetPos = target.position + targetOffset;
|
||
Quaternion rot = Quaternion.Euler(currentY, currentX, 0f);
|
||
Vector3 dir = rot * Vector3.back * targetDistance;
|
||
|
||
if (instant)
|
||
{
|
||
transform.position = targetPos + dir;
|
||
smoothX = currentX;
|
||
smoothY = currentY;
|
||
smoothDistance = targetDistance;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Error Handling
|
||
|
||
void ReportNullTargetErrorOnce()
|
||
{
|
||
if (hasReportedNullTargetError)
|
||
return;
|
||
|
||
hasReportedNullTargetError = true;
|
||
|
||
Debug.LogError(
|
||
$"[RoamCameraController] Target为空,相机停止运行 " +
|
||
$"GameObject: {gameObject.name}",
|
||
this
|
||
);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Utility
|
||
|
||
static float ClampAngle(float angle, float min, float max)
|
||
{
|
||
angle = Mathf.Repeat(angle + 180f, 360f) - 180f;
|
||
return Mathf.Clamp(angle, min, max);
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
}
|