可直接使用的unity第三人称自由视角相机脚本

使用方法:


将要控制的角色拖到TargetBody,将相机的焦点拖到CamerPivot,,建议CameraPivot是一个放在TargetBody下的子物体,并且位置应该是在TargetBody的头部.

注意:此脚本自动忽略"Ignore Raycast"层和"Mob"对相机视野的遮挡,也就是说,当相机被带有这两个层之一的物体遮挡时,相机不会自动移动到遮挡物之前,这是用于设置一些不应该触发相机防遮挡的物体用的,比如说怪物和玻璃等.




包含的功能有:

            1.控制人物转向,按下WASD按键时可直接控制目标物体(角色)插值转向.

            2.垂直视角限制,避免发生相机翻转的问题

            3.相机防遮挡


原理说明:

            1.通过四元数插值控制人物转向

            2.在unity中,水平视角的x欧拉角为0,向上直至垂直的欧拉角实际上为(0-90),而向下直至垂直的欧拉角实际上是(360-270).脚本中maxEvelation为最大仰角,maxDepression为最大俯角,假设此处最大俯角和最大仰角都为80,则在最大仰角达到80以上时,判断鼠标的输入,只有当鼠标输入为向下滑动时(此时鼠标的输入Input.getAxis("Mouse Y")>0)才允许滑动.同理,当最大俯角为80(360-80=280)时,此时实际上的欧拉角为280-270,所以当x<280时,则只允许向上滑动(此时鼠标的输入Input.getAxis("Mouse Y")<0))

如图所示:


代码:

 float eulerX = transform.localEulerAngles.x;//相机的x欧拉角,也就是垂直方向.
        float inputY = Input.GetAxis("Mouse Y");

        //垂直视野限制
        transform.RotateAround(CameraPivot.transform.position, CameraPivot.transform.up, rotateSpeed * Input.GetAxis("Mouse X"));
        if (eulerX > maxEvelation && eulerX < 90)
        {
            if (inputY > 0)
                transform.RotateAround(CameraPivot.transform.position, transform.right, -rotateSpeed * inputY);
        }
        else if (eulerX < 360-maxDepression && eulerX > 270)
        {
            if (inputY < 0)
                transform.RotateAround(CameraPivot.transform.position, transform.right, -rotateSpeed * inputY);
        }
        else
        {
            transform.RotateAround(CameraPivot.transform.position, transform.right, -rotateSpeed * inputY);

        }

            3.相机防遮挡


先提前检测(自己)相机的应该在位置,从相机目标向相机发射一条射线,如果碰撞了,说明即将碰撞.则直接把位置调到碰撞点位置,但此时的位置正好在平面里,相机的一部分视野会透视,所以应该在再向相机靠近一点.

transform.position = CameraPivot.transform.position + (wallHit - CameraPivot.transform.position) * 0.8f;

注意,这里是提前判断,如果是直接更新相机位置后发现被遮挡再把位置移动到碰撞点,相机就会在碰撞点和应在位置来回切换.效果自然是有问题的.

最后说明相机的位置更新方法:

        相机的旋转通过RotateAround()来以目标相机焦点为中心进行旋转.旋转后根据当前位置和相机焦点位置相减得到方向向量,然后用相机焦点位置+方向向量的单位向量*设置的距相机焦点的距离.

代码表示:

            offset = transform.position - CameraPivot.transform.position;
            offset = offset.normalized * freeDistance;
            transform.position = CameraPivot.transform.position + offset;

完整代码:

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

public class FreeTPSCamera : MonoBehaviour {
    [Header("相机距离")]
    public float freeDistance = 2;
    [Header("相机最近距离")]
    public float minDistance = 0.5f;
    [Header("相机最远距离")]
    public float maxDistance = 20;
    [Header("是否可控制相机距离(鼠标中键)")]
    public bool canControlDistance=true;
    [Header("更改相机距离的速度")]
    public float distanceSpeed = 1;

    [Header("视角灵敏度")]
    public float rotateSpeed = 1;
    [Header("物体转向插值(灵敏度,取值为0到1)")]
    public float TargetBodyRotateLerp = 0.3f;
    [Header("需要转向的物体")]
    public GameObject TargetBody;//此脚本能操作转向的物体
    [Header("相机焦点物体")]
    public GameObject CameraPivot;//相机焦点物体  
    [Header("===锁敌===")]
    public GameObject lockTarget=null;
    public float lockSlerp=1;
    public GameObject lockMark;
    private bool marked;

    [Header("是否可控制物体转向")]
    public bool CanControlDirection = true;
    [Header("俯角(0-89)")]
    public float maxDepression=80;
    [Header("仰角(0-89)")]
    public float maxEvelation=80;
    

    private Vector3 PredictCameraPosition;
    private Vector3 offset;
    private Vector3 wallHit;
    private GameObject tmpMark;
    // Use this for initialization
    void Start () {
        
        offset = transform.position - CameraPivot.transform.position;
        if (TargetBody == null)
        {
            TargetBody = GameObject.FindGameObjectWithTag("Player");
            Debug.Log("未绑定目标物体,默认替换为Player标签的物体");
        }
        if (!CameraPivot)
        {
            Debug.LogError("未绑定相机焦点物体");
        }

    }

    void LockTarget()
    {
        if(lockTarget)
        {
            lockTarget = null;
            marked = false;
            Destroy(tmpMark);
            return;
        }

        Vector3 top = transform.position + new Vector3(0, 1, 0)+transform.forward*5;
        LayerMask mask = (1 << LayerMask.NameToLayer("Mob")); //将物体的Layer设置为Ignore Raycast,Player和Mob来忽略相机的射线,不然相机将跳到某些物体前,比如怪物,玩家等,
      
        Collider[] cols = Physics.OverlapBox(top, new Vector3(0.5f,0.5f,5),transform.rotation,mask);
        foreach (var col in cols)
        {
            lockTarget = col.gameObject;
        }   
    }

    bool Inwall()
    {

        RaycastHit hit;
        LayerMask mask = (1 << LayerMask.NameToLayer("Player")) | (1 << LayerMask.NameToLayer("Ignore Raycast"))| (1 << LayerMask.NameToLayer("Mob"))|(1<<LayerMask.NameToLayer("Weapon")); //将物体的Layer设置为Ignore Raycast,Player和Mob来忽略相机的射线,不然相机将跳到某些物体前,比如怪物,玩家等,
        mask = ~mask;//将以上的mask取反,表示射线将会忽略以上的层
        //Debug.DrawLine(CameraPivot.transform.position, transform.position - transform.forward, Color.red);
        
        PredictCameraPosition = CameraPivot.transform.position + offset.normalized * freeDistance ;//预测的相机位置
        if (Physics.Linecast(CameraPivot.transform.position, PredictCameraPosition, out hit, mask))//碰撞到任意碰撞体,注意,因为相机没有碰撞器,所以是不会碰撞到相机的,也就是没有碰撞物时说明没有遮挡
        {//也就是说,这个if就是指被遮挡的情况


            wallHit = hit.point;//碰撞点位置
            //Debug.DrawLine(transform.position, wallHit, Color.green);
            return true;
        }
        else//没碰撞到,也就是说没有障碍物
        {
            return false;
        }


    }


    void FreeCamera()
    {
        offset = offset.normalized * freeDistance;
        transform.position = CameraPivot.transform.position + offset;//更新位置

        if (CanControlDirection)//控制角色方向开关
        {
            Quaternion TargetBodyCurrentRotation = TargetBody.transform.rotation;

            if (Input.GetKey(KeyCode.A))
            {
                if (Input.GetKey(KeyCode.W))
                {
                    TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y - 45, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
                }
                else if (Input.GetKey(KeyCode.S))
                {
                    TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y - 135, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
                }


                else if (!Input.GetKey(KeyCode.W) && !Input.GetKey(KeyCode.S))
                {
                    TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y - 90, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
                }
            }
            else if (Input.GetKey(KeyCode.D))
            {
                if (Input.GetKey(KeyCode.W))
                {
                    TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y + 45, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
                }
                else if (Input.GetKey(KeyCode.S))
                {
                    TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y + 135, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
                }

                else if (!Input.GetKey(KeyCode.W) && !Input.GetKey(KeyCode.S))
                {
                    TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y + 90, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
                }
            }
            else if (Input.GetKey(KeyCode.W))
            {
                TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);

            }
            else if (Input.GetKey(KeyCode.S))
            {
                TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y - 180, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);

            }
        }

        if(canControlDistance)//控制距离开关
        {
            freeDistance -= Input.GetAxis("Mouse ScrollWheel") * distanceSpeed;
        }
        
        freeDistance = Mathf.Clamp(freeDistance, minDistance, maxDistance);

        if(!lockTarget)
        {

        
            transform.LookAt(lockTarget ? (lockTarget.transform.position ): CameraPivot.transform.position);
        }
        else
        {
            Quaternion tmp = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(lockTarget.transform.position - transform.position), lockSlerp*Time.fixedDeltaTime);
            transform.rotation = tmp;

        }

        float eulerX = transform.localEulerAngles.x;//相机的x欧拉角,也就是垂直方向.
        float inputY = Input.GetAxis("Mouse Y");


        if (!lockTarget)
        {
                //垂直视野限制
                if (!lockTarget)
            {
                transform.RotateAround(CameraPivot.transform.position, Vector3.up, rotateSpeed * Input.GetAxis("Mouse X"));//x不用限制
            }
        
            if (eulerX > maxDepression && eulerX < 90)//当向上角度越界时
            {
                if (inputY > 0)//如果鼠标时在向下滑动
                    transform.RotateAround(CameraPivot.transform.position, Vector3.right, -rotateSpeed * inputY);//允许滑动
            }
            else if (eulerX < 360-maxEvelation && eulerX > 270)
            {
                if (inputY < 0)
                    transform.RotateAround(CameraPivot.transform.position, Vector3.right, -rotateSpeed * inputY);
            }
            else//角度正常时
            {
            
                    transform.RotateAround(CameraPivot.transform.position, Vector3.right, -rotateSpeed * inputY);
           

            }
        }
        if (lockTarget)
        {
            offset = CameraPivot.transform.position - (lockTarget.transform.position);
        }
        else
        {
            offset = transform.position - CameraPivot.transform.position;//以上方向发生了变化,记录新的方向向量
        }
       
        offset = offset.normalized * freeDistance;

        ///在一次FixedUpdate中,随时记录新的旋转后的位置,然后得到方向,然后判断是否即将被遮挡,如果要被遮挡,将相机移动到计算后的不会被遮挡的位置
        ///如果不会被遮挡,则更新位置为相机焦点位置+方向的单位向量*距离
        ///
        if (Inwall())//预测会被遮挡
        {
            //print("Inwall");

            transform.position = CameraPivot.transform.position + (wallHit - CameraPivot.transform.position) * 0.8f;

            return;


        }
        else
        {
            transform.position = CameraPivot.transform.position + offset;
           
        }
    
    }

    // Update is called once per frame
    void FixedUpdate () {
        FreeCamera();
        if(lockTarget)
        {
            
            if (!marked)
            {
                tmpMark = Instantiate(lockMark, lockTarget.transform.position + new Vector3(0, 2.5f, 0), transform.rotation);
                tmpMark.transform.forward = -Vector3.up;
                marked = true;
            }
            
            else
            {
                tmpMark.transform.position = lockTarget.transform.position + new Vector3(0, 2.5f, 0);
                //tmpMark.transform.forward= -transform.up;
                tmpMark.transform.Rotate(Vector3.up *30* Time.fixedDeltaTime,Space.World);
            }
        }
	}

    private void Update()
    {
        if(Input.GetKeyDown(KeyCode.F))
        {
            LockTarget();
        }
    }
}

  • 22
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值