Unity3D中第三人称视角的镜头跟随和目标锁定

从2D游戏到3D游戏的进化,最重要的就是游戏视角的控制。奠定了3D游戏操作模式的1998年的神话级游戏《塞尔达传说:时之笛》中,对3D下游戏视角设计的关键即在于“镜头跟随”与“视角锁定”。本文将在unity中实现该效果。

实现过程

实现的思路非常简单:

  • 在初始化时设定摄像机和人物之间的相对位置和相对旋转角;
  • 在需要锁定时找到人物最近的单位,使人物转向该单位,并将摄像机的水平面的旋转角设置至和人物相同;
  • 如果一定范围内没有单位,则人物不需转向,摄像机仍然执行;

这里设置了positionTarget和angleTarget两个变量,用来表示移动的目标状态,同时使用Lerp函数实现平滑运动。

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

public class CameraController : MonoBehaviour
{
    public GameObject player;
    Vector3 distanceToPlayer;
    Vector3 positionTarget,angleTarget;
    LayerMask enemyLayer;
    float temp;

    // Start is called before the first frame update
    void Start()
    {
        distanceToPlayer = this.transform.position - player.transform.position;
        enemyLayer = LayerMask.GetMask("Enemy");
        angleTarget = this.transform.eulerAngles;
    }

    // Update is called once per frame
    void Update()
    { 
        CameraFollow();
        if (Input.GetKey(KeyCode.Space))
        {
            CameraAngle();
        }
        this.transform.eulerAngles = Vector3.Lerp(this.transform.eulerAngles, angleTarget, 0.3f);
        this.transform.position = Vector3.Lerp(this.transform.position, positionTarget, 0.3f);
    }

    void CameraFollow()
    {
        positionTarget = player.transform.position + Quaternion.AngleAxis(player.transform.rotation.eulerAngles.y, new Vector3(0, 1, 0)) * (distanceToPlayer);
    }

    void CameraAngle()
    {
        //找到最近的单位
        Collider[] collidersAround = Physics.OverlapSphere(player.transform.position, 400);//Overlap一系的函数在检测总数不是太大的时候会优先获取更近的碰撞器
        if (collidersAround.Length == 0)
        {
        //搜索不到目标就指向正前方
            angleTarget = new Vector3(this.transform.eulerAngles.x, player.transform.eulerAngles.y, this.transform.eulerAngles.z); 
            return;
        }
        int closestEnemy = 0;
        float closestDistance = 401;
        for (int i = 0; i < collidersAround.Length; i++)
        {
            temp = (this.transform.position - collidersAround[i].gameObject.transform.position).sqrMagnitude;
            closestEnemy = temp < closestDistance ? i : closestEnemy;
            closestDistance = temp < closestDistance ? temp : closestDistance;
        }
		player.transform.LookAt(collidersAround[closestEnemy].gameObject.transform.position);
        positionTarget = player.transform.position + Quaternion.AngleAxis(player.transform.rotation.eulerAngles.y, new Vector3(0, 1, 0)) * (distanceToPlayer);
        angleTarget = new Vector3(this.transform.eulerAngles.x, player.transform.eulerAngles.y, this.transform.eulerAngles.z);   
    }

}

但是这样写完一运行,发现不对了:
在这里插入图片描述
每次转一圈到0°的时候视角会“刷”一下外翻一圈。

原因很好理解,就是因为越过360°(0°)的时候,获取的欧拉角会产生一个突变,此时Lerp运算后的变化方向会变化:
在这里插入图片描述
按照图中的变化,会在某一时刻发生角度突然变小,表现出的就是镜头“天旋地转”。

那怎么解决呢?这里要在突变时使两角度的差值减小360°,换句话说,只需要在设置angleTarget的时候保证从当前角度到目标角度的差值小于180°即可(此时一定为变化最小的过程)。
也就是把上文中的CameraAngle函数改成:

    void CameraAngle()
    {
        Collider[] collidersAround = Physics.OverlapSphere(player.transform.position, 400);
        if (collidersAround.Length == 0)
        {
        	angleTarget = new Vector3(this.transform.eulerAngles.x, player.transform.eulerAngles.y, this.transform.eulerAngles.z); 
            return;
        }
        int closestEnemy = 0;
        float closestDistance = 401;
        for (int i = 0; i < collidersAround.Length; i++)
        {
            temp = (this.transform.position - collidersAround[i].gameObject.transform.position).sqrMagnitude;
            closestEnemy = temp < closestDistance ? i : closestEnemy;
            closestDistance = temp < closestDistance ? temp : closestDistance;
        }
        player.transform.LookAt(collidersAround[closestEnemy].gameObject.transform.position);
        positionTarget = player.transform.position + Quaternion.AngleAxis(player.transform.rotation.eulerAngles.y, new Vector3(0, 1, 0)) * (distanceToPlayer);
        angleTarget = new Vector3(this.transform.eulerAngles.x, player.transform.eulerAngles.y, this.transform.eulerAngles.z);        
        //当越过0发生突变时(不是最短变化路径)加以矫正
        if (Mathf.Abs(player.transform.eulerAngles.y - this.transform.eulerAngles.y) > 180)
        {
            angleTarget = new Vector3(this.transform.eulerAngles.x, player.transform.eulerAngles.y + (player.transform.eulerAngles.y > this.transform.eulerAngles.y ? -360 : 360), this.transform.eulerAngles.z);
        }
    }

最终实现的效果如下:

在这里插入图片描述


本次分享的内容到这里就结束了,我写博文的目的最初是为了帮助自己掌握知识,如果这篇分享能够稍微帮助到你,那当然再好不过了。有什么问题也请各位多指正!谢谢大家的关注!

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页