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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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分区…