You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Cinemachine虚拟相机设置与自定义脚本冲突问题求助

问题描述

我将Cinemachine Virtual Camera挂载在玩家对象上,通过Inspector面板设置实现了相机跟随模型但不旋转的效果,这正是我需要的。但添加以下脚本后,原有设置被覆盖,相机开始随玩家旋转,我尝试修改代码但未成功,希望实现脚本不覆盖原有设置的效果,同时保留缩放功能。

添加的脚本代码:

using Cinemachine;
using UnityEngine;

public class OrbitalTest : CinemachineExtension {
    private const float RotateSpeed = 2f;

    private Vector2 _currentRotate;
    private Vector3 _distanceVector;
    private float _currentDistance = 3f;
    private Transform _lookAt;

    private CinemachineVirtualCamera _virtualCamera;

    private void Update() {
        HandleInput();

        // Rotate the camera
        var centerMatrix = Matrix4x4.Rotate(Quaternion.Euler(_currentRotate.y, _currentRotate.x, 0f));
        _distanceVector.z = -_currentDistance;
        var pos = centerMatrix.MultiplyPoint3x4(_distanceVector);

        // Update camera position and orientation
        _virtualCamera.transform.position = _lookAt.position + pos;
        _virtualCamera.transform.LookAt(_lookAt.position);
    }

    private void HandleInput() {
        if (Input.GetKey(KeyCode.LeftArrow)) Rotate(new Vector2(1f, 0f));
        if (Input.GetKey(KeyCode.RightArrow)) Rotate(new Vector2(-1f, 0f));
        if (Input.GetKey(KeyCode.UpArrow)) Rotate(new Vector2(0f, 1f));
        if (Input.GetKey(KeyCode.DownArrow)) Rotate(new Vector2(0f, -1f));
        if (Input.GetKey(KeyCode.W)) Scale(-0.1f);
        if (Input.GetKey(KeyCode.S)) Scale(0.1f);
    }

    private void Rotate(Vector2 diffDelta) {
        diffDelta *= RotateSpeed * Time.deltaTime * 30;
        _currentRotate.x += diffDelta.y;
        _currentRotate.y += diffDelta.x;
    }

    protected override void ConnectToVcam(bool connect) {
        base.ConnectToVcam(connect);
        if (!connect) return;

        _lookAt = GameObject.Find("LookAt")?.transform;
        if (_lookAt == null) {
            _lookAt = new GameObject("LookAt").transform;
            _lookAt.position = new Vector3(0f, 0.5f, 0f);
        }

        _virtualCamera = VirtualCamera as CinemachineVirtualCamera;
        _virtualCamera.LookAt = _lookAt;
    }

    protected override void PostPipelineStageCallback(CinemachineVirtualCameraBase vcam,
        CinemachineCore.Stage stage, ref CameraState state, float deltaTime) {
        var (pos, rotate) = GetTransform(_virtualCamera.LookAt.localToWorldMatrix);
        state.RawPosition = pos;
        state.RawOrientation = rotate;

        // Look at the player's position
        _virtualCamera.transform.LookAt(_lookAt.position);
    }

    private (Vector3 pos, Quaternion rotate) GetTransform(Matrix4x4 baseCenterMatrix) {
        var centerMatrix = baseCenterMatrix * Matrix4x4.Rotate(Quaternion.AngleAxis(_currentRotate.y, Vector3.up) *
                                                               Quaternion.AngleAxis(_currentRotate.x, Vector3.right));

        _distanceVector.z = -_currentDistance;
        var pos = centerMatrix.MultiplyPoint3x4(_distanceVector);

        return (pos, centerMatrix.rotation);
    }

    void Scale(float delta) {
        _currentDistance += delta;
    }
}

更新:经排查,以下代码是导致旋转问题的根源,但移除这段代码后,缩放功能无法正常工作:

protected override void PostPipelineStageCallback(CinemachineVirtualCameraBase vcam,
        CinemachineCore.Stage stage, ref CameraState state, float deltaTime) {
        var (pos, rotate) = GetTransform(_lookAt.localToWorldMatrix);
        state.RawPosition = pos;
        state.RawOrientation = rotate;
    }

解决方案

核心思路是只让脚本处理手动旋转、缩放逻辑,完全保留相机原有跟随但不旋转的设置,具体修改如下:

1. 移除Update中直接修改相机位置/旋转的代码

原Update方法里直接修改_virtualCamera.transform.position和LookAt会干扰Cinemachine的原有逻辑,删掉这部分,改为通过Cinemachine的回调来调整位置,同时不修改旋转。

2. 修改PostPipelineStageCallback回调

只调整相机的位置以实现缩放和手动旋转,不覆盖原有旋转状态,保留Inspector设置的“不随玩家旋转”效果:

protected override void PostPipelineStageCallback(CinemachineVirtualCameraBase vcam,
    CinemachineCore.Stage stage, ref CameraState state, float deltaTime) {
    // 只在Body阶段之后调整位置,避免干扰其他阶段
    if (stage != CinemachineCore.Stage.Body) return;

    // 计算基于LookAt的偏移位置(包含手动旋转和缩放)
    var rotation = Quaternion.AngleAxis(_currentRotate.y, Vector3.up) * Quaternion.AngleAxis(_currentRotate.x, Vector3.right);
    var offset = rotation * new Vector3(0, 0, -_currentDistance);
    
    // 只修改位置,保留原有旋转状态
    state.RawPosition = _lookAt.position + offset;
}

3. 调整ConnectToVcam逻辑

不要强制覆盖_virtualCamera.LookAt,而是优先使用原有设置,避免破坏Inspector配置:

protected override void ConnectToVcam(bool connect) {
    base.ConnectToVcam(connect);
    if (!connect) return;

    _virtualCamera = VirtualCamera as CinemachineVirtualCamera;
    // 优先使用原有LookAt设置,没有再创建
    _lookAt = _virtualCamera.LookAt;
    if (_lookAt == null) {
        _lookAt = new GameObject("LookAt").transform;
        _lookAt.position = new Vector3(0f, 0.5f, 0f);
        _virtualCamera.LookAt = _lookAt;
    }
}

4. 简化Update方法

只保留输入处理,移除直接操作相机的代码:

private void Update() {
    HandleInput();
}

完整修改后的脚本

using Cinemachine;
using UnityEngine;

public class OrbitalTest : CinemachineExtension {
    private const float RotateSpeed = 2f;

    private Vector2 _currentRotate;
    private float _currentDistance = 3f;
    private Transform _lookAt;

    private CinemachineVirtualCamera _virtualCamera;

    private void Update() {
        HandleInput();
    }

    private void HandleInput() {
        if (Input.GetKey(KeyCode.LeftArrow)) Rotate(new Vector2(1f, 0f));
        if (Input.GetKey(KeyCode.RightArrow)) Rotate(new Vector2(-1f, 0f));
        if (Input.GetKey(KeyCode.UpArrow)) Rotate(new Vector2(0f, 1f));
        if (Input.GetKey(KeyCode.DownArrow)) Rotate(new Vector2(0f, -1f));
        if (Input.GetKey(KeyCode.W)) Scale(-0.1f);
        if (Input.GetKey(KeyCode.S)) Scale(0.1f);
    }

    private void Rotate(Vector2 diffDelta) {
        diffDelta *= RotateSpeed * Time.deltaTime * 30;
        _currentRotate.x += diffDelta.y;
        _currentRotate.y += diffDelta.x;
    }

    protected override void ConnectToVcam(bool connect) {
        base.ConnectToVcam(connect);
        if (!connect) return;

        _virtualCamera = VirtualCamera as CinemachineVirtualCamera;
        _lookAt = _virtualCamera.LookAt;
        if (_lookAt == null) {
            _lookAt = new GameObject("LookAt").transform;
            _lookAt.position = new Vector3(0f, 0.5f, 0f);
            _virtualCamera.LookAt = _lookAt;
        }
    }

    protected override void PostPipelineStageCallback(CinemachineVirtualCameraBase vcam,
        CinemachineCore.Stage stage, ref CameraState state, float deltaTime) {
        if (stage != CinemachineCore.Stage.Body) return;

        var rotation = Quaternion.AngleAxis(_currentRotate.y, Vector3.up) * Quaternion.AngleAxis(_currentRotate.x, Vector3.right);
        var offset = rotation * new Vector3(0, 0, -_currentDistance);
        
        state.RawPosition = _lookAt.position + offset;
    }

    void Scale(float delta) {
        // 限制最小距离,避免相机穿模
        _currentDistance = Mathf.Clamp(_currentDistance + delta, 1f, 10f);
    }
}

原理说明

  • 只在Cinemachine的Body阶段后调整位置,既实现了手动旋转和缩放,又完全保留了原有相机的旋转设置(不随玩家旋转)。
  • 不再强制覆盖LookAt,优先使用Inspector的配置,避免破坏原有设置。
  • 移除了Update中直接操作相机Transform的代码,让Cinemachine的管线处理旋转逻辑,保证原有设置生效。

内容的提问来源于stack exchange,提问作者Paul Edward Pagente

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 17:44:53