可直接使用的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();
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章