一、腳本功能簡介
ObserveCamera
是一個可直接掛載到任意 GameObject 上的通用攝像機控制腳本,支持以下功能:
鼠標右鍵控制攝像機繞自身旋轉(俯仰、水平)
鼠標左鍵拖拽目標對象進行平移(局部 XY 平面移動)
鼠標滾輪實現前后縮放(沿局部 Z 軸)
支持 DOTween 動畫過渡:旋轉和縮放
可分別啟用或禁用旋轉、拖拽、縮放三項功能
二、節點結構要求
該腳本依賴子節點來實現旋轉、平移、縮放的分離控制,節點結構如下:
GameObject(掛載 ObserveCamera 腳本)
└── Move(控制平移)└── Zoom(控制縮放)
說明:
腳本節點用于響應鼠標旋轉
Move
節點用于響應鼠標拖拽Zoom
節點(通常是相機)用于響應滾輪縮放(Z 軸前后移動)
三、基本使用方式
將
ObserveCamera.cs
腳本掛載到一個空物體上(例如名為ObserveRoot
)為其手動創建兩個子節點
Move
和Zoom(
攝像機)
在 Inspector 中設置旋轉范圍、速度、縮放范圍等參數
場景中必須存在一個
EventSystem
組件(用于 UI 檢測)
運行后即可通過鼠標進行旋轉、拖拽和縮放。
四、代碼控制示例
你可以通過腳本提供的接口動態控制攝像機行為:
// 設置攝像機初始位置
observeCamera.SetObservePostion(new Vector3(0, 2f, -5));// 禁用所有交互
observeCamera.SetAllControlsEnabled(false);// 單獨開啟旋轉功能
observeCamera.SetRotatable(true);// 使用動畫旋轉到指定角度
observeCamera.DoRotate(new Vector3(30f, 180f, 0f), 1.5f);// 使用動畫進行縮放
observeCamera.DoZoom(-10f, 1f);// 重置平移偏移(回到 Move 節點原點)
observeCamera.ResetDrag();
五、屬性說明
字段名 | 功能說明 |
---|---|
verticalRotationRange | 上下旋轉的角度范圍(x) |
verticalRotationSpeed | 鼠標上下移動時旋轉的速度 |
horizontalRotationSpeed | 鼠標左右移動時旋轉的速度 |
dampingTime | 旋轉的平滑阻尼時間 |
dragSpeed | 拖拽移動的響應速度 |
zoomSpeed | 鼠標滾輪縮放的速度 |
zoomRange | 縮放的 Z 軸范圍(負值) |
六、依賴說明
需要安裝 DOTween 插件(用于實現 DoRotate / DoZoom 動畫)
場景中必須存在 EventSystem(用于識別 UI 屏蔽交互)
當前版本支持 桌面端鼠標輸入(未支持觸控或手柄)
using System;
using DG.Tweening;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;/// <summary>
/// ObserveCamera 攝像機控制腳本
///
/// 支持功能:
/// - 鼠標右鍵旋轉攝像機(繞自身旋轉)
/// - 鼠標左鍵拖拽移動觀察目標(平移控制)
/// - 鼠標滾輪進行縮放(沿Z軸縮放)
/// - 支持動畫旋轉與縮放(使用 DOTween)
/// - 可通過代碼啟用/禁用旋轉、拖拽、縮放功能
///
/// 節點結構要求:
/// ObserveCamera(掛載本腳本)
/// └── Move(平移控制節點)
/// └── Zoom(縮放控制節點)應該是攝像機或者如果你使用了cinemachine間接聯系了攝像機也可以
///
/// 用法示例:
/// 1. 拖動攝像機對象到場景中,確保其子節點結構為 Move > Zoom
/// 2. 在 Inspector 中設置旋轉范圍、速度、縮放范圍等參數
/// 3. 在代碼中調用控制方法:
///
/// - camera.SetAllControlsEnabled(false); // 禁用一切交互
/// - camera.DoRotate(new Vector3(30, 45, 0), 1.5f); // 動畫旋轉
/// - camera.DoZoom(-10f, 1.2f); // 動畫縮放
/// - camera.ResetDrag(); // 重置平移偏移
///
/// 依賴項:
/// - 需要引用 DOTween 插件
/// - 需要場景中存在 EventSystem 組件
///
/// 注意事項:
/// - 控制節點名必須是 "Move" 和其子對象 "Zoom",否則會拋出異常
/// - 輸入檢測默認使用鼠標,僅在 PC 平臺生效
/// - UI 交互區域(如按鈕)上方將屏蔽控制(基于 EventSystem)
///
/// 作者:王維志
/// 日期:2025-08-06
/// </summary>public class ObserveCamera : MonoBehaviour
{[Header("上下旋轉范圍")] [SerializeField] private Vector2 verticalRotationRange = new Vector2(-45, 45);[Header("上下旋轉速度")] [SerializeField] private float verticalRotationSpeed = 1.5f;[Header("左右旋轉速度")] [SerializeField] private float horizontalRotationSpeed = 2f;[Header("到達目標角度的阻尼時間")] [SerializeField]private float dampingTime = 0.2f;[Header("拖拽速度")] [SerializeField] private float dragSpeed = 0.2f;[Header("鼠標滾輪縮放速度")] [SerializeField] private float zoomSpeed = 1f;[Header("縮放范圍")] [SerializeField] private Vector2 zoomRange = new Vector2(-20, -5);[Header("目標角度")] [SerializeField] private Vector3 targetAngles;[Header("當前角度")] [SerializeField] public Vector3 followAngles;[Header("當前速度")] [SerializeField] public Vector3 followVelocity;[Header("初始旋轉")] [SerializeField] public Quaternion originalRotation;[Header("是否正在拖拽子物體")] [SerializeField] private bool isDragging;[Header("移動節點")] public Transform moveTransform; // 控制移動的Transform[Header("縮放節點")] public Transform zoomTransform; // 控制旋轉的Transformprivate Vector3 lastMousePosition; // 上一幀鼠標位置public bool IsRotatable { get; private set; } = true;public bool IsDraggable { get; private set; } = true;public bool IsZoomable { get; private set; } = true;private void Awake(){InitializeRotation();if (!moveTransform) moveTransform = transform.Find("Move");if (!zoomTransform) zoomTransform = moveTransform.Find("Zoom");if (moveTransform == null || zoomTransform == null){throw new Exception("節點缺失");}if (EventSystem.current == null){throw new Exception("EventSystem is null");}}private void InitializeRotation(){originalRotation = transform.localRotation;var eulerAngles = originalRotation.eulerAngles;eulerAngles.x = -eulerAngles.x;targetAngles = followAngles = eulerAngles;originalRotation.eulerAngles = Vector3.zero;}/// <summary>/// 設置觀察位置/// </summary>/// <param name="originPosition"></param>public void SetObservePostion(Vector3 originPosition){transform.position = originPosition;}public void SetDraggable(bool canDrag){IsDraggable = canDrag;}public void SetZoomable(bool canZoom){IsZoomable = canZoom;}public void SetRotatable(bool isRotatable){IsRotatable = isRotatable;}public void SetAllControlsEnabled(bool isEnabled){IsDraggable = isEnabled;IsZoomable = isEnabled;IsRotatable = isEnabled;}/// <summary>/// 重置拖拽偏移/// </summary>public void ResetDrag(){moveTransform.localPosition = Vector3.zero;}private void Update(){HandleDrag();HandleRotate();Zoom();}#region 自用工具方法private bool IsPointerOverUI(){return EventSystem.current == null || EventSystem.current.IsPointerOverGameObject();}#endregion#region 拖拽private void HandleDrag(){if (Input.GetMouseButtonDown(0)) // 左鍵按下,開始拖拽{StartDragging();}if (Input.GetMouseButton(0)) // 左鍵拖拽過程中{DragObject();}if (Input.GetMouseButtonUp(0)) // 左鍵釋放,停止拖拽{StopDragging();}}private void StartDragging(){if (!IsDraggable){return;}if (IsPointerOverUI()){return;}isDragging = true;lastMousePosition = Input.mousePosition; // 記錄鼠標的初始位置}private void DragObject(){if (!isDragging){return;}Vector3 mouseDelta = Input.mousePosition - lastMousePosition; // 計算鼠標移動的差值// 根據鼠標移動更新子物體的位置(X、Y方向)Vector3 newPosition = moveTransform.localPosition;newPosition.x -= mouseDelta.x * dragSpeed; // 使用公開的拖拽速度newPosition.y -= mouseDelta.y * dragSpeed;moveTransform.localPosition = newPosition;lastMousePosition = Input.mousePosition; // 更新鼠標位置}private void StopDragging(){isDragging = false;}#endregion#region 旋轉private void HandleRotate(){if (Input.GetMouseButton(1)) // 右鍵旋轉{RotateControl();}}private void RotateControl(){//transform.localRotation = originalRotation;if (!IsRotatable){return;}// 獲取鼠標移動輸入float inputH = Input.GetAxis("Mouse X");float inputV = Input.GetAxis("Mouse Y");// 更新目標角度targetAngles.y += inputH * horizontalRotationSpeed; // 不限制左右旋轉targetAngles.x += inputV * verticalRotationSpeed; // 鼠標Y軸反向旋轉// 限制上下旋轉范圍targetAngles.x = Mathf.Clamp(targetAngles.x, verticalRotationRange.x, verticalRotationRange.y);// 平滑插值,避免旋轉過程卡頓followAngles = Vector3.SmoothDamp(followAngles, targetAngles, ref followVelocity, dampingTime);// 防止微小誤差導致的旋轉漂移if (Vector3.SqrMagnitude(followAngles - targetAngles) < 1e-3f)followAngles = targetAngles;// 應用旋轉transform.localRotation = originalRotation * Quaternion.Euler(-followAngles.x, followAngles.y, 0);}#endregion#region 縮放private void Zoom(){if (!IsZoomable) return;if (IsPointerOverUI()){return;}// 獲取鼠標滾輪輸入float scroll = Input.GetAxis("Mouse ScrollWheel");if (Mathf.Abs(scroll) > 0.01f) // 當滾輪移動時才進行操作{Vector3 newPosition = zoomTransform.localPosition;newPosition.z = Mathf.Clamp(newPosition.z + scroll * zoomSpeed,zoomRange.x, zoomRange.y); // 使用縮放速度并限制在范圍內zoomTransform.localPosition = newPosition;}}#endregion#region Custompublic void DoRotate(Vector3 target, float duration){IsRotatable = false;transform.DOLocalRotate(target, duration).OnComplete(() =>{var temp = transform.localRotation.eulerAngles;temp.x = -temp.x;targetAngles = followAngles = temp;IsRotatable = true;});}public void DoRotate(Vector3 target, float duration, Action callback){IsRotatable = false;transform.DOLocalRotate(target, duration).OnComplete(() =>{var temp = transform.localRotation.eulerAngles;temp.x = -temp.x;targetAngles = followAngles = temp;IsRotatable = true;callback?.Invoke();});}public void DoZoom(float z, float duration){IsZoomable = false;zoomTransform.DOLocalMoveZ(z, duration).OnComplete(() => { IsZoomable = true; });}public void DoZoom(float z, float duration, UnityAction callBack){IsZoomable = false;zoomTransform.DOLocalMoveZ(z, duration).OnComplete(() =>{IsZoomable = true;callBack?.Invoke();});}#endregion
}