摄像机围绕人物旋转

本文介绍了一个Unity脚本,该脚本实现了第三人称视角下相机对目标对象的平滑跟随与缩放功能。通过调整参数,可以实现鼠标控制相机旋转、滚轮缩放距离以及碰撞检测等功能。

用法:先在你的Player里新建一个空的Object重命名为CameraTarget(你起什么名字都行),然后将把脚本挂载到主摄像机上,将你新建的 CameraTarget 拖动到脚本的Target属性,运行你就可以看到效果了




using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class cc : MonoBehaviour 
{

    public bool HideAndShowCursor = true;
    public bool LockRotationWhenRightClick = false;
    public bool UseBlurEffect = true;
    public bool UseFogEffect = true;
    public Transform target;//要控制的对象

    public float targetHeight = 1.0f;
    public float distance = 5.0f;

    public float maxDistance = 20;
    public float minDistance = 0.6f;

    public float xSpeed = 250.0f;
    public float ySpeed = 120.0f;

    public int yMinLimit = -80;
    public int yMaxLimit = 80;

    public int zoomRate = 40;

    public float rotationDampening = 3.0f;
    public float zoomDampening = 10.0f;

    private float x = 0.0f;
    private float y = 0.0f;
    private float currentDistance;
    private float desiredDistance;
    private float correctedDistance;
    private bool grounded = false;

    void Start()
    {
        Screen.lockCursor = true;
        Vector3 angles = transform.eulerAngles;
        x = angles.x;
        y = angles.y;

        currentDistance = distance;
        desiredDistance = distance;
        correctedDistance = distance - 0.2f;

        // Make the rigid body not change rotation
        if (GetComponent<Rigidbody>())
            GetComponent<Rigidbody>().freezeRotation = true;
    }

    void LateUpdate()
    {
        // Don't do anything if target is not defined
        if (!target)
        {
            GameObject go = GameObject.Find("Player");
            target = go.transform;
            transform.LookAt(target);
            return;
        }
        // If either mouse buttons are down, let the mouse govern camera position
        if (LockRotationWhenRightClick == false)
        {
            x += Input.GetAxis("Mouse X") * xSpeed * 0.02f;
            y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02f;
        }
        if (Input.GetMouseButton(0))
        {
            if (LockRotationWhenRightClick == false)
            {
                x += Input.GetAxis("Mouse X") * xSpeed * 0.02f;
                y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02f;
            }
        }
        y = ClampAngle(y, yMinLimit, yMaxLimit);

        // set camera rotation
        Quaternion rotation = Quaternion.Euler(y, x, 0);

        // calculate the desired distance
        desiredDistance -= Input.GetAxis("Mouse ScrollWheel") * Time.deltaTime * zoomRate * Mathf.Abs(desiredDistance);
        desiredDistance = Mathf.Clamp(desiredDistance, minDistance, maxDistance);
        correctedDistance = desiredDistance;

        // calculate desired camera position
        Vector3 position = target.position - (rotation * Vector3.forward * desiredDistance + new Vector3(0, -targetHeight, 0));

        // check for collision using the true target's desired registration point as set by user using height
        RaycastHit collisionHit;
        Vector3 trueTargetPosition = new Vector3(target.position.x, target.position.y + targetHeight, target.position.z);

        // if there was a collision, correct the camera position and calculate the corrected distance
        bool isCorrected = false;
        if (Physics.Linecast(trueTargetPosition, position, out collisionHit))
        {
            if (collisionHit.transform.name != target.name)
            {
                position = collisionHit.point;
                correctedDistance = Vector3.Distance(trueTargetPosition, position);
                isCorrected = true;
            }
        }

        // For smoothing, lerp distance only if either distance wasn't corrected, or correctedDistance is more than currentDistance
        currentDistance = !isCorrected || correctedDistance > currentDistance ? Mathf.Lerp(currentDistance, correctedDistance, Time.deltaTime * zoomDampening) : correctedDistance;

        // recalculate position based on the new currentDistance
        position = target.position - (rotation * Vector3.forward * currentDistance + new Vector3(0, -targetHeight - 0.05f, 0));

        transform.rotation = rotation;
        transform.position = position;

    }

    private static float ClampAngle(float angle, float min, float max)
    {
        if (angle < -360)
            angle += 360;
        if (angle > 360)
            angle -= 360;
        return Mathf.Clamp(angle, min, max);
    }
}


<think>我们想要实现摄像机跟随玩家角色转向。根据引用[3]和引用[5]中的思路,我们可以通过获取鼠标输入来控制摄像机围绕玩家旋转,同时保持一定的距离和高度。 基本思路: 1. 摄像机的位置由玩家位置、距离、高度和旋转角度决定。 2. 通过鼠标的移动(水平移动和垂直移动)来改变摄像机旋转角度(水平方向绕Y轴旋转,垂直方向绕X轴旋转,但注意垂直旋转有角度限制)。 3. 摄像机始终看向玩家。 我们将使用以下变量: - `target`:跟随的目标(玩家) - `distance`:摄像机与目标之间的距离 - `currentX`和`currentY`:当前摄像机绕目标旋转的水平角度和垂直角度(初始值可以设为默认视角) - `sensitivityX`和`sensitivityY`:鼠标旋转的灵敏度 - `minVerticalAngle`和`maxVerticalAngle`:限制垂直旋转的角度,避免摄像机翻转 在LateUpdate中更新摄像机的位置和旋转。 步骤: 1. 获取鼠标输入,更新currentX和currentY。 2. 根据currentX和currentY计算摄像机的位置。 3. 处理碰撞检测(可选,防止穿墙)。 4. 设置摄像机的位置和旋转(看向目标)。 注意:我们将使用球面坐标(角度和距离)来计算摄像机相对于目标的位置。 位置计算公式: direction = (cos(currentY) * sin(currentX), sin(currentY), cos(currentY) * cos(currentX)) position = target.position + direction * distance 但是注意:我们通常希望摄像机在玩家后方一定高度,所以初始角度可以设置为:水平角度为玩家当前旋转角度,垂直角度为一定俯角(如30度)。另外,我们也可以将垂直角度限制在0到80度之间(避免过度俯视或仰视)。 然而,引用[5]中使用了四元数旋转的方法,我们可以这样: rotation = Quaternion.Euler(currentY, currentX, 0); position = target.position + rotation * (new Vector3(0, 0, -distance)); 但是这样计算的位置是在目标的正后方,没有高度差。我们可以通过初始设定一个高度偏移,或者通过垂直旋转来自然形成高度差(第二种方法更自然)。 我们采用第二种方法:通过垂直旋转来同时控制高度和距离。因为垂直旋转(绕X轴)会使摄像机在垂直面上移动,从而改变高度和水平距离。 具体实现: 1. 在脚本中定义变量: public Transform target; // 跟随目标 public float distance = 5.0f; // 初始距离 public float minDistance = 1.0f; public float maxDistance = 10.0f; public float scrollSpeed = 5.0f; // 滚轮缩放速度 public float rotationSensitivityX = 4.0f; // 水平旋转灵敏度 public float rotationSensitivityY = 1.0f; // 垂直旋转灵敏度 public float minVerticalAngle = -20.0f; // 最小垂直角度(度) public float maxVerticalAngle = 80.0f; // 最大垂直角度(度) private float currentX = 0.0f; // 当前水平旋转角度 private float currentY = 0.0f; // 当前垂直旋转角度 2. 在Start中初始化:将currentX和currentY设置为初始角度(例如,currentX=0, currentY=30,这样初始是俯角30度)。 3. 在Update中获取鼠标输入(注意:我们使用Input.GetAxis("Mouse X")和Input.GetAxis("Mouse Y"),并且通常只在按下鼠标右键时旋转,如引用[3]和[4]中提到的,但也可以不按右键一直有效,根据需求)。 4. 在LateUpdate中更新摄像机位置和旋转。 5. 添加滚轮缩放功能(调整distance)。 6. 碰撞检测(可选):如果摄像机与目标之间有障碍物,则将摄像机拉近到障碍物位置。 由于用户要求跟随玩家角色转向,这里我们有两种方式: - 方式1:摄像机独立旋转,玩家角色不跟随摄像机旋转(即摄像机围绕玩家转,但玩家依然通过键盘控制朝向,如很多RPG游戏)。 - 方式2:摄像机旋转时,玩家角色也跟随摄像机的前方方向旋转(如很多TPS游戏,鼠标控制角色朝向)。 根据引用[3],他们实现了角色跟随鼠标旋转(即方式2)。我们可以根据需求选择。 本脚本将实现: - 摄像机围绕目标旋转(通过鼠标控制) - 可选的玩家角色朝向控制(即当摄像机旋转时,玩家角色也转向摄像机面对的方向?或者让玩家角色独立控制?) 注意:用户的问题中并没有明确玩家角色是否跟随摄像机旋转,但引用[3]中实现了角色跟随鼠标旋转(即角色转向摄像机面对的方向)。因此,我们可以提供两种模式。 但是,根据问题“摄像机跟随玩家角色转向”,我们可能理解为:摄像机始终在玩家后方,当玩家转向时,摄像机也跟着转向(保持相对位置)。但这里用户的要求是“控制摄像机跟随玩家角色转向”,结合引用,我们理解为:通过鼠标控制摄像机旋转,同时玩家角色也跟随摄像机旋转(即角色总是面朝摄像机的前方方向?不一定,角色可以独立转向,但摄像机始终在角色后方)。 这里我们分开: - 摄像机旋转控制:由鼠标控制,摄像机围绕玩家旋转。 - 玩家转向控制:由键盘控制(WASD控制角色移动方向),或者由鼠标控制(角色总是面向摄像机的前方?)。 引用[3]中,角色是跟随鼠标旋转的(即鼠标左右移动控制角色水平旋转)。所以,我们可以这样设计: - 水平旋转摄像机时,同时旋转玩家(使玩家面向摄像机的前方方向在水平面上的投影)。 因此,我们可以在摄像机旋转后,将玩家的旋转设置为摄像机旋转在水平方向上的旋转(即Y轴旋转摄像机相同,X和Z为0)。 但是,用户可能希望玩家独立控制,所以我们可以提供一个选项。 我们先实现摄像机旋转,然后可选地设置玩家朝向。 步骤: 1. 获取鼠标输入,更新currentX和currentY。 2. 限制currentY在[minVerticalAngle, maxVerticalAngle]之间。 3. 计算摄像机的期望位置:通过球面坐标计算。 4. 进行碰撞检测,调整实际距离(避免穿墙)。 5. 设置摄像机的位置。 6. 摄像机看向目标。 7. (可选)如果开启了玩家跟随摄像机旋转,则将玩家的Y轴旋转设置为与摄像机相同的Y轴旋转(即玩家始终面向摄像机的前方方向在水平面上的投影)。 代码实现: 注意:我们使用欧拉角,垂直旋转(currentY)是绕X轴,水平旋转(currentX)是绕Y轴。 位置计算: 旋转顺序:先绕Y轴水平旋转currentX,再绕X轴垂直旋转currentY(注意Unity的旋转顺序,这里使用四元数组合旋转)。 方向向量:初始为(0,0,-distance),然后应用旋转(先水平旋转,再垂直旋转)。 位置 = 目标位置 + 旋转后的方向向量。 但是,我们也可以使用: Quaternion rotation = Quaternion.Euler(currentY, currentX, 0); Vector3 dir = rotation * Vector3.forward; // 得到旋转后的前方方向 // 但是注意,我们想要的是从目标指向摄像机的方向,所以应该是负的前方方向?不对。 实际上,我们希望摄像机在目标的后方,所以应该是目标位置加上一个在目标后方一定距离的点。这个点可以通过旋转一个初始向量(0,0,-distance)得到,但注意旋转顺序。 更简单的方法:使用球坐标转直角坐标: x = distance * sin(horizontal) * cos(vertical) y = distance * sin(vertical) z = distance * cos(horizontal) * cos(vertical) 但是注意,在Unity中,Y轴向上,所以: pos.x = target.position.x + distance * Mathf.Cos(currentX) * Mathf.Cos(currentY); pos.y = target.position.y + distance * Mathf.Sin(currentY); pos.z = target.position.z + distance * Mathf.Sin(currentX) * Mathf.Cos(currentY); 但是这样计算比较复杂,我们使用四元数旋转: 另一种方法(推荐): Vector3 direction = new Vector3(0, 0, -distance); Quaternion rotation = Quaternion.Euler(currentY, currentX, 0); Vector3 desiredPosition = target.position + rotation * direction; 这样计算更直观。 下面我们写代码: 注意:我们使用两个脚本来分离控制,但为了简单,我们写在一个脚本中。 我们将脚本挂载到摄像机上。 另外,为了处理碰撞,我们使用Physics.Linecast从目标位置到摄像机期望位置,如果碰撞到物体,则调整摄像机位置到碰撞点。 实现: 完整脚本如下:</think># Unity 摄像机跟随玩家角色转向实现 以下是实现摄像机跟随玩家角色转向的完整解决方案,结合了平滑跟随、碰撞检测和视角控制功能: ```csharp using UnityEngine; public class PlayerCameraController : MonoBehaviour { [Header("目标设置")] public Transform player; // 玩家角色 public Transform cameraPivot; // 摄像机旋转支点(通常放在角色肩部位置) [Header("位置参数")] public float distance = 5.0f; // 摄像机与角色的距离 public float height = 1.5f; // 摄像机高度 public float minDistance = 1.0f; // 最小距离 public float maxDistance = 10.0f; // 最大距离 [Header("旋转参数")] public float rotationSpeed = 100f; // 旋转速度 public float minVerticalAngle = -30f; // 垂直旋转最小角度 public float maxVerticalAngle = 70f; // 垂直旋转最大角度 [Header("平滑参数")] public float positionSmooth = 5.0f; // 位置平滑 public float rotationSmooth = 3.0f; // 旋转平滑 [Header("碰撞检测")] public LayerMask collisionLayers; // 碰撞检测层 public float collisionOffset = 0.2f; // 碰撞偏移量 private float currentX = 0.0f; // 当前水平旋转角度 private float currentY = 0.0f; // 当前垂直旋转角度 private Vector3 cameraOffset; // 摄像机偏移向量 private float targetDistance; // 目标距离 void Start() { if (player == null) { Debug.LogError("未指定玩家对象!"); return; } if (cameraPivot == null) { // 如果没有指定支点,使用玩家位置 cameraPivot = player; } // 初始化摄像机位置 currentX = player.eulerAngles.y; currentY = 15f; // 默认俯视角 targetDistance = distance; // 计算初始偏移 UpdateCameraOffset(); // 初始位置 transform.position = cameraPivot.position + cameraOffset; transform.LookAt(cameraPivot.position); } void Update() { if (player == null || cameraPivot == null) return; // 鼠标控制摄像机旋转 HandleCameraRotation(); // 滚轮控制摄像机距离 HandleZoom(); } void LateUpdate() { if (player == null || cameraPivot == null) return; // 更新摄像机偏移 UpdateCameraOffset(); // 处理碰撞检测 HandleCollision(); // 平滑移动摄像机 SmoothCameraMovement(); // 确保摄像机始终看向角色 transform.LookAt(cameraPivot.position); } // 处理摄像机旋转 void HandleCameraRotation() { if (Input.GetMouseButton(1)) // 右键旋转摄像机 { currentX += Input.GetAxis("Mouse X") * rotationSpeed * Time.deltaTime; currentY -= Input.GetAxis("Mouse Y") * rotationSpeed * Time.deltaTime; // 限制垂直角度 currentY = Mathf.Clamp(currentY, minVerticalAngle, maxVerticalAngle); } } // 处理摄像机缩放 void HandleZoom() { float scroll = Input.GetAxis("Mouse ScrollWheel"); if (scroll != 0) { targetDistance -= scroll * 5f; targetDistance = Mathf.Clamp(targetDistance, minDistance, maxDistance); } } // 更新摄像机偏移向量 void UpdateCameraOffset() { // 计算方向向量 Vector3 direction = new Vector3(0, 0, -targetDistance); // 应用旋转 Quaternion rotation = Quaternion.Euler(currentY, currentX, 0); cameraOffset = rotation * direction; // 添加高度偏移 cameraOffset.y += height; } // 处理碰撞检测 void HandleCollision() { RaycastHit hit; Vector3 cameraTargetPos = cameraPivot.position + cameraOffset; Vector3 direction = (cameraTargetPos - cameraPivot.position).normalized; float actualDistance = targetDistance; // 从角色位置向摄像机位置发射射线 if (Physics.Linecast(cameraPivot.position, cameraTargetPos, out hit, collisionLayers)) { // 计算碰撞点偏移 actualDistance = Vector3.Distance(cameraPivot.position, hit.point) - collisionOffset; actualDistance = Mathf.Clamp(actualDistance, minDistance, targetDistance); // 更新偏移向量 Vector3 adjustedOffset = direction * actualDistance; adjustedOffset.y += height; cameraOffset = adjustedOffset; } } // 平滑移动摄像机 void SmoothCameraMovement() { Vector3 targetPosition = cameraPivot.position + cameraOffset; // 位置平滑 transform.position = Vector3.Lerp( transform.position, targetPosition, positionSmooth * Time.deltaTime ); // 旋转平滑(可选) Quaternion targetRotation = Quaternion.LookRotation( cameraPivot.position - transform.position ); transform.rotation = Quaternion.Slerp( transform.rotation, targetRotation, rotationSmooth * Time.deltaTime ); } } ``` ## 实现原理说明 ### 1. 摄像机位置计算 摄像机位置通过以下公式计算: $$ \text{position} = \text{pivot} + \text{rotation} \times \begin{pmatrix} 0 \\ \text{height} \\ -\text{distance} \end{pmatrix} $$ 其中: - `pivot` 是摄像机旋转支点位置(通常位于角色肩部) - `rotation` 是由鼠标输入控制的四元数旋转 - `height` 控制摄像机高度 - `distance` 控制摄像机与角色的距离 ### 2. 鼠标控制旋转 - **水平旋转**:通过 `Mouse X` 输入控制摄像机绕Y轴旋转 - **垂直旋转**:通过 `Mouse Y` 输入控制摄像机绕X轴旋转,并限制在 `minVerticalAngle` 和 `maxVerticalAngle` 之间 ### 3. 碰撞检测 使用 `Physics.Linecast` 检测摄像机与角色之间的障碍物: - 当检测到碰撞时,自动调整摄像机距离 - `collisionOffset` 参数确保摄像机不会嵌入墙壁 ### 4. 平滑过渡 使用 `Vector3.Lerp` 和 `Quaternion.Slerp` 实现位置和旋转的平滑过渡,避免镜头抖动 ## 使用指南 1. **场景设置**: - 将脚本挂载到主摄像机上 - 将玩家角色拖拽到 `Player` 字段 - 创建一个空对象作为 `Camera Pivot`(放在角色肩部位置) 2. **参数调整**: - `Distance`:初始摄像机距离(建议值:3-5) - `Height`:摄像机高度(建议值:1.5-2) - `Rotation Speed`:鼠标旋转灵敏度(建议值:100-200) - `Position Smooth`:位置跟随平滑度(值越大越紧贴) - `Rotation Smooth`:旋转跟随平滑度 3. **操作方式**: - 按住鼠标右键并移动:旋转摄像机视角 - 鼠标滚轮:调整摄像机距离 - 摄像机自动避开障碍物 ## 优化建议 ### 添加角色朝向跟随 在玩家控制脚本中添加以下代码,使角色跟随摄像机方向移动: ```csharp // 在玩家移动脚本中 public float rotationSmooth = 10f; void Update() { // 获取摄像机前方向量(忽略Y轴) Vector3 cameraForward = Camera.main.transform.forward; cameraForward.y = 0; cameraForward.Normalize(); // 获取输入方向 float horizontal = Input.GetAxis("Horizontal"); float vertical = Input.GetAxis("Vertical"); Vector3 direction = new Vector3(horizontal, 0, vertical).normalized; if (direction.magnitude > 0.1f) { // 计算目标方向(基于摄像机方向) Vector3 targetDirection = cameraForward * vertical + Camera.main.transform.right * horizontal; targetDirection.Normalize(); // 平滑旋转角色 Quaternion targetRotation = Quaternion.LookRotation(targetDirection); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSmooth * Time.deltaTime); } } ``` ### 添加动态高度调整 在摄像机脚本中添加动态高度调整功能,使摄像机在角色跳跃时更自然: ```csharp [Header("动态高度")] public float heightDamping = 0.3f; // 高度变化阻尼 public float maxHeightOffset = 2.0f; // 最大高度偏移 private float currentHeight; private float velocityY; void Start() { currentHeight = height; } void UpdateCameraOffset() { // 计算目标高度(角色跳跃时提高摄像机) float targetHeight = height; if (!player.GetComponent<CharacterController>().isGrounded) { targetHeight += Mathf.Clamp(player.velocity.y, 0, maxHeightOffset); } // 平滑高度变化 currentHeight = Mathf.SmoothDamp(currentHeight, targetHeight, ref velocityY, heightDamping); // 更新偏移向量... cameraOffset.y += currentHeight; } ``` ## 常见问题解决 1. **摄像机穿墙问题**: - 确保正确设置 `collisionLayers`(包含墙壁等障碍物层) - 调整 `collisionOffset` 值(通常0.1-0.3) - 如果仍有问题,增加碰撞检测射线数量: ```csharp // 在HandleCollision()中替换为多射线检测 Vector3[] rayDirections = new Vector3[] { Vector3.forward, (Vector3.forward + Vector3.up).normalized, (Vector3.forward + Vector3.down).normalized, (Vector3.forward + Vector3.left).normalized, (Vector3.forward + Vector3.right).normalized }; float minDistance = targetDistance; foreach (Vector3 dir in rayDirections) { Vector3 rayDirection = rotation * dir; if (Physics.Raycast(cameraPivot.position, rayDirection, out hit, targetDistance, collisionLayers)) { float hitDistance = Vector3.Distance(cameraPivot.position, hit.point); if (hitDistance < minDistance) minDistance = hitDistance; } } actualDistance = minDistance - collisionOffset; ``` 2. **旋转不自然问题**: - 调整 `rotationSmooth` 值(值越大旋转越平滑) - 确保 `cameraPivot` 位置正确(通常放在角色肩部) - 限制垂直旋转角度避免视角翻转
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值