一、脚本功能简介
ObserveCamera
是一个可直接挂载到任意 GameObject 上的通用摄像机控制脚本,支持以下功能:
-
鼠标右键控制摄像机绕自身旋转(俯仰、水平)
-
鼠标左键拖拽目标对象进行平移(局部 XY 平面移动)
-
鼠标滚轮实现前后缩放(沿局部 Z 轴)
-
支持 DOTween 动画过渡:旋转和缩放
-
可分别启用或禁用旋转、拖拽、缩放三项功能
二、节点结构要求
该脚本依赖子节点来实现旋转、平移、缩放的分离控制,节点结构如下:
cs
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 屏蔽交互)
-
当前版本支持 桌面端鼠标输入(未支持触控或手柄)
cs
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; // 控制旋转的Transform
private 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 Custom
public 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
}