Unity轻量观察相机

一、脚本功能简介

ObserveCamera 是一个可直接挂载到任意 GameObject 上的通用摄像机控制脚本,支持以下功能:

  • 鼠标右键控制摄像机绕自身旋转(俯仰、水平)

  • 鼠标左键拖拽目标对象进行平移(局部 XY 平面移动)

  • 鼠标滚轮实现前后缩放(沿局部 Z 轴)

  • 支持 DOTween 动画过渡:旋转和缩放

  • 可分别启用或禁用旋转、拖拽、缩放三项功能


二、节点结构要求

该脚本依赖子节点来实现旋转、平移、缩放的分离控制,节点结构如下:

cs 复制代码
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 屏蔽交互)

  • 当前版本支持 桌面端鼠标输入(未支持触控或手柄)


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
}
相关推荐
南無忘码至尊2 小时前
Unity编辑器工具:一键为场景中所有MeshRenderer对象添加指定脚本
unity·c#·游戏引擎·游戏开发
枯萎穿心攻击3 小时前
算法入门第一篇:算法核心:复杂度分析与数组基础
算法·unity·矩阵·c#·游戏引擎
DoomGT1 天前
Physics Simulation - UE中Projectile相关事项
ue5·游戏引擎·虚幻·虚幻引擎·unreal engine
★YUI★1 天前
学习游戏制作记录(将各种属性应用于战斗以及实体的死亡)8.5
学习·游戏·unity·c#
污领巾1 天前
虚幻GAS底层原理解剖一(开篇)
游戏引擎·虚幻
污领巾1 天前
虚幻GAS底层原理解剖三 (GA)
java·游戏引擎·虚幻
伽蓝_游戏1 天前
Unity UI的未来之路:从UGUI到UI Toolkit的架构演进与特性剖析(7)
游戏·ui·unity·架构·c#·游戏引擎·.net
BuHuaX2 天前
Unity_数据持久化_IXmlSerializable接口
xml·unity·c#·游戏引擎·游戏策划
lihongli0002 天前
消息系统技术文档
网络·unity·游戏引擎