应用场景:共三个物体,a是飞行物体,b是固定物体,c是摄像机,需要实现a飞行过程中,c可以动态移动位置(改变位置和旋转),使得始终可以拍到物体a和b(除极端情况)

思路:计算a飞行过程中,在xz平面中相对于物体b的旋转角angle,在xz平面上的俯仰角x,使得物体c也相对b有旋转角angle,自身俯仰角调整x度

using UnityEngine;

public class CameraController : MonoBehaviour
{
    [Header("物体b位置")]
    public Transform attackTrans;
    [Header("物体a")]
    public Transform targetTrans;

    private Camera _camera;
    private bool _isView = false;//记录a是否被看见,当a进入视野,才开始打击和摄像机跟踪
    private bool _isTrack = false;//记录是否开启摄像机跟踪,开启条件是viewPos==0.7
    private Vector3 _line1;//b和a的上一帧向量
    private Vector3 _prevVector;
    private float _currentPitch;  // 当前俯仰角度值



    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        _camera = this.GetComponent<Camera>();
        
    }

    // Update is called once per frame
    void Update()
    {
        if(attackTrans != null)
        {
            Vector3 viewPos = _camera.WorldToViewportPoint(attackTrans.position);
            //Debug.Log($"viewPos:{viewPos}");
            if (_isView == false)
            {
                if ((viewPos.x > 0 && viewPos.x < 1) || (viewPos.y > 0 && viewPos.y < 1) || (viewPos.z > 0 && viewPos.z < 1))
                    _isView = true;
            }
            else
            {
                //当a被看见后才判断是否需要摄像机跟踪
                if(_isTrack == false)
                {
                    _isTrack = true;
                    _prevVector = GetXZVector(targetTrans.position - attackTrans.position);
                    _line1 = targetTrans.position - attackTrans.position;
                    _currentPitch = this.transform.eulerAngles.x;
                }
                else
                {
                    Track();
                }
            }
        }
    }

    /// <summary>
    /// 摄像机跟踪目标
    /// </summary>

    void Track()
    {
        //计算摄像机绕b旋转
        // 1. 计算当前a相对于b的向量(XZ平面)
        Vector3 currentVector = GetXZVector(targetTrans.position - attackTrans.position);

        // 2. 计算a绕b在XZ平面的旋转角度
        float rotationAngle = Vector3.SignedAngle(_prevVector, currentVector, Vector3.up);

        // 3. 若存在有效旋转,则让摄像机绕b旋转相同角度
        if (Mathf.Abs(rotationAngle) > 0.001f)
        {
            // 核心:绕b旋转摄像机(同时更新位置和朝向)
            this.transform.RotateAround(attackTrans.position, Vector3.up, rotationAngle);
        }

        // 4. 更新上一帧向量
        _prevVector = currentVector;

        //计算摄像机的俯仰角
        Vector3 line2 = targetTrans.position - attackTrans.position;
        //计算在a绕b在YZ面上的旋转向量
        Vector3 line1YZ = new Vector3(0, _line1.y, _line1.z).normalized;
        Vector3 line2YZ = new Vector3(0, line2.y, line2.z).normalized;

        // 计算俯仰角度变化量(绕X轴旋转)
        float pitchDelta = Vector3.SignedAngle(line1YZ, line2YZ, Vector3.right);

        // 更新并限制俯仰角度范围
        _currentPitch += pitchDelta;
        _currentPitch = Mathf.Clamp(_currentPitch, -45f, 10f); // 限制俯仰角范围
        Debug.Log($"pitchDelta:{pitchDelta}");


        // 应用俯仰旋转(只修改X轴)
        Vector3 euler = transform.eulerAngles;
        euler.x = _currentPitch;
        transform.eulerAngles = euler;
        _line1 = line2;
    }

    // 辅助方法:提取XZ平面向量(忽略Y轴)
    private Vector3 GetXZVector(Vector3 vec)
    {
        return new Vector3(vec.x, 0, vec.z).normalized;//为什么要标准化
    }
}

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐