Unity輕量觀察相機

一、腳本功能簡介

ObserveCamera 是一個可直接掛載到任意 GameObject 上的通用攝像機控制腳本,支持以下功能:

  • 鼠標右鍵控制攝像機繞自身旋轉(俯仰、水平)

  • 鼠標左鍵拖拽目標對象進行平移(局部 XY 平面移動)

  • 鼠標滾輪實現前后縮放(沿局部 Z 軸)

  • 支持 DOTween 動畫過渡:旋轉和縮放

  • 可分別啟用或禁用旋轉、拖拽、縮放三項功能


二、節點結構要求

該腳本依賴子節點來實現旋轉、平移、縮放的分離控制,節點結構如下:

GameObject(掛載 ObserveCamera 腳本)
└── Move(控制平移)└── Zoom(控制縮放)

說明:

  • 腳本節點用于響應鼠標旋轉

  • Move 節點用于響應鼠標拖拽

  • Zoom 節點(通常是相機)用于響應滾輪縮放(Z 軸前后移動)


三、基本使用方式

  1. ObserveCamera.cs 腳本掛載到一個空物體上(例如名為 ObserveRoot

  2. 為其手動創建兩個子節點 MoveZoom(攝像機)

  3. 在 Inspector 中設置旋轉范圍、速度、縮放范圍等參數

  4. 場景中必須存在一個 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
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/92057.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/92057.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/92057.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

1深度學習Pytorch-pytorch、tensor的創建、屬性、設備和類型轉換、數據轉換、常見操作(獲取元素、元素運算、形狀改變、相乘、廣播)

文章目錄PyTorchTensor1 Tensor 的創建1.torch.tensor2.torch.Tensor3. 線性張量4. 隨機張量5. 特定數值的張量2 Tensor 常見屬性1 屬性2 設備切換3 類型轉換torch.Tensor.to(dtype)類型專用方法創建張量時直接指定類型與 NumPy 數組的類型互轉4 數據轉換&#xff08;淺拷貝與深…

五、Istio管理網格外部服務

因語雀與csdn markdown 格式有區別&#xff0c;請查看原文&#xff1a; https://www.yuque.com/dycloud/pss8ys 一、Egress Listener 流量策略 前面學習了 sidecar 自動注入原理、inbound Listener、outbound Listener 等概念&#xff0c;也知道了 EgressListener 的流量策略…

Ubuntu20.04 離線安裝 FFmpeg 靜態編譯包

系統版本 Ubuntu20.04 去現場部署項目&#xff0c;發現現場的設備連接的內網&#xff0c;無法使用apt直接安裝ffmpeg &#xff0c;想解決也簡單&#xff0c;數據線連接手機使用共享網絡&#xff0c;再使用命令sudo apt install ffmpeg安裝即可&#xff0c;奈何現場百多臺設備&a…

C語言高級編程技巧與最佳實踐

C語言高級編程技巧與最佳實踐 - 完整版 目錄 宏定義與預處理技巧內存管理高級技巧函數指針與回調機制數據結構設計并發與多線程錯誤處理與異常機制性能優化技巧調試與測試技巧跨平臺編程安全編程實踐綜合演示示例 宏定義與預處理技巧 1. 條件編譯與平臺檢測 /*** 平臺和編譯…

cygwin+php教程(swoole擴展+redis擴展)

cygwin 1.下載cygwin安裝程序 &#xff1a;在Windows上獲得Linux的感覺 ? 2. 打開安裝包&#xff1a;setup-x86_64.exe 3.選擇安裝類型 從互聯網安裝首次安裝下載而不安裝僅下載軟件包不安裝從本地目錄安裝遷移程序時使用 4.選擇安裝目錄 5.選擇本地軟件包目錄&#xff…

Ethereum: Uniswap V3核心”Tick”如何引爆DEX的流動性革命?

大家好&#xff0c;今天&#xff0c;我們來聊聊一個在去中心化交易所&#xff08;DEX&#xff09;領域&#xff0c;尤其是自Uniswap V3問世以來&#xff0c;變得至關重要的概念——Tick&#xff08;流動性邊界&#xff09;。 如果大家接觸過DeFi&#xff0c;可能聽說過Uniswap …

【概念學習】什么是深度學習

人工智能 人工智能的簡潔定義如下&#xff1a;努力將通常由人類完成的智力任務自動化。 因此&#xff0c;人工智能是一個綜合性的領域&#xff0c;不僅包括機器學習與深度學習&#xff0c;還包括更多不涉及學習的方法。 在相當長的時間內&#xff0c;許多專家相信&#xff0c;只…

【MATLAB】(八)矩陣

一.矩陣的定義MATLAB 以矩陣作為數據操作的基本單位&#xff0c;這使得矩陣運算變得非常簡捷、方便、高效。矩陣是由m*n個數q(i1,2,…,m&#xff1b;j1,2,…,n)&#xff0c;排成的m行n列數表&#xff0c;記成稱為 mxn 矩陣&#xff0c;也可以記成aij或Am*n。其中,i表示行數,j表…

python的高校考研交流系統

前端開發框架:vue.js 數據庫 mysql 版本不限 后端語言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 數據庫工具&#xff1a;Navicat/SQLyog等都可以 在當今社…

從零開始部署Qwen3-8b大模型到本地

一、方法一&#xff08;使用docker鏡像進行部署&#xff09; 安裝Linux服務器&#xff0c;本機測試系統為Ubuntu系統&#xff1b;(帶有2張A100的GPU服務器) 思路為&#xff1a;使用docker部署python環境鏡像在此基礎上安裝vllm拉取Qwen3-8b模型 docker-compose.yml文件部分配…

AI產品經理如何理解和應用Transformer架構,以提升產品的技術能力和用戶體驗?

?你好&#xff0c;我是 ?三橋君? 助你邁向AGI時代&#xff01;&#xff01;&#xff01; &#x1f4cc;本文介紹&#x1f4cc; >> 一、引言 在當今的AI浪潮中&#xff0c;Transformer架構已不再是一個陌生的技術名詞。從OpenAI的GPT系列到Google的BERT&#xff0c;再…

數據結構(四)內核鏈表、棧與隊列

一、內核鏈表基礎1. 什么是 Linux 內核鏈表&#xff1f;Linux 內核鏈表是一種高效的 雙向循環鏈表&#xff0c;廣泛應用于內核模塊開發中&#xff0c;用于管理數據結構。每個節點通過指針連接前一個和后一個元素&#xff0c;實現插入和刪除的高性能。2. 鏈表的定義與初始化在 L…

軟考信息安全工程師11月備考

目前是在職備考&#xff0c;主業是移動端開發工程師。第一個月(8.4-9.6)&#xff0c;將分享完下面所有章節內容&#xff0c;平均不到兩天更新一節1.網絡信息安全概述2.網絡攻擊原理與常用方法3.密碼學基本理論4.網絡安全體系與網絡安全模型5.物理與環境安全技術6.認證技術與原理…

使用DrissionPage實現xhs筆記自動翻頁并爬取筆記視頻、圖片

使用DrissionPage實現xhs筆記自動翻頁并爬取筆記視頻、圖片 聲明: 本文章中所有內容僅供學習交流使用,不用于其他任何目的,不提供完整代碼,抓包內容、敏感網址、數據接口等均已做脫敏處理,嚴禁用于商業用途和非法用途,否則由此產生的一切后果均與作者無關! 本文章未經…

使用 input 上傳文件, 選擇文件后再次修改文件再上傳失敗( <input type=“file“ /> 自定義上傳)

業務實際需求&#xff1a;點擊【選擇】按鈕先選擇文件&#xff0c;展示文件的詳情&#xff1a;類型&#xff0c;大小&#xff0c;日期......點擊【上傳】按鈕這個時候才去上傳文件如圖&#xff1a;BUG復現&#xff1a;點擊上傳文件后發現xlsx文件有些數據沒填寫&#xff0c;然后…

Win11 下解決 VScode/Trae 插件加載慢, 整個 VScode/Trae 很卡

最近在使用 Trae 寫代碼, 突然變得很卡, 尤其是插件系統, 比如我打開插件的面板, 以及比如我想預覽一下寫好的 .md 文件 (已安裝了 Markdown Preview Enhanced 插件), 這些都要好幾分鐘才能打開. 最初以為是 Trae 壞掉了, 然后重啟 Trae 不管用, 再重啟電腦居然也不管用, 接著…

微型導軌:智能家居抽屜的智能化應用

當智能家居從“功能堆砌”轉向“體驗升級”&#xff0c;微型導軌憑借超薄結構、靜音運行與精準定位能力&#xff0c;成為隱藏式設計、自動化交互的核心部件&#xff0c;讓家具“動”得優雅且可靠。智能掃地機器人&#xff1a;微型導軌被應用于邊刷的伸縮調節機構&#xff0c;能…

百套易語言教程、易語言視頻教程【易語言編程入門教程】

百套易語言教程、易語言視頻教程【易語言編程入門教程】 易語言輔助教程&#xff08;愛易編程論壇講師 24課講師&#xff1a;遠航 9課愛易編程論壇講師&#xff1a;愛易、小Call 8課&#xff09;.rar 時光論壇易語言全套教程【易語言零基礎易語言抓包易語言填表】完整版.rar 易…

nlp-詞匯分析

目錄 一、語言中的詞匯 1、詞的形態學 2、詞的詞性 二、詞語規范化 1、詞語切分 2、詞形還原 3、詞干提取 三、中文分詞 1、概述 2、基于最大匹配的中文分詞 3、基于線性鏈條件隨機場的中文分詞 4、基于感知器的中文分詞 詞序列預測 模型參數學習 特征定義 5、…

Kafka ISR機制和Raft區別:副本數優化的秘密

Kafka的ISR機制和像Raft這樣的傳統基于Quorum&#xff08;法定人數&#xff09;的協議之間的區別確實很微妙&#xff0c;但也非常重要。讓我們來分析一下為什么ISR可以減少所需的副本數量。在采用ISR模型和&#xff08;f1&#xff09;個副本數的配置下&#xff0c;一個Kafka分區…