Unity第三人称的实现思路(二)

标签: Unity第三人称  unity3d  unity

在这里插入图片描述


这一次我们在上一篇自由视角的基础上加上锁定视角的功能,没有看过上一篇的点下方链接。
Unity第三人称的实现思路(一)

锁定视角动画设置

锁定视角的动画分为两种情况,一种是不带方向的一种是带方向的:

单一方向:
在这里插入图片描述
在这里插入图片描述

8方向:
在这里插入图片描述

在这里插入图片描述


比如黑魂里的锁定视角,只推左摇杆正常移动时模型永远面朝目标,但如果按住x奔跑时模型就会朝向移动的方向,但摄像机依旧是对准目标的。而在这个Demo中我用的是拔出剑时模型永远面朝目标,剑收起来时模型朝向移动方向。 锁定视角只需要理清 角色碰撞器,模型,目标 三者之间的相对位置就能知道融合树中的数值应该怎么给。

锁定视角摄像机

Cinemachine组件很多参数都是公开的,只需要引用Cinemachine命名空间然后获取组件就能修改其中的参数。锁定视角需要更改的参数其实只有两三处,可以在锁定视角时用代码修改,这里为了减少代码量没有采用这种方法,毕竟现在需要修改的只有两三处,但以后就不一定了。在上一篇中提到的Priority(优先级)参数就起到作用了,主摄像机会渲染处于激活状态的更高优先级的虚拟相机,我们只需要复制一份自由视角的相机然后修改其中的参数,再用代码动态激活和关闭这个相机即可。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Axis Control中两个轴的speed设置成0是为了让鼠标两个轴的输入变为0,相当于禁止移动视角。
Binding Mode原本是World Space,如果不移动视角的话位置就不会改变,锁定视角时会出现相机在目标与角色中间,看不到角色的问题。设置成Lock To Target后相机位置会锁定在角色身后,只要让角色时刻朝向目标,摄像机就永远能看见角色并对准目标。

角色朝向控制

		//如果按下锁定键
        if(Input.GetKeyDown(keys.Lock))
        {
        	//锁定状态改变
            state.isLocking = !state.isLocking;
            //锁定为真打开锁定视角摄像机,锁定为假时关闭
            lockCamera.SetActive(state.isLocking);
            //设置动画状态机锁定状态的参数
            animController.SetBool("LockState", state.isLocking);
        }
	//锁定状态时每一帧调用
	void LockLook()
    {
        if (!state.isLocking)
            return;
        //不移动时不旋转角色
        if (moveDir.sqrMagnitude < 0.1f)
            return;
		
		//相机朝向目标,旋转值赋给角色后角色也朝向目标
        Vector3 cameraEuler = Camera.main.transform.eulerAngles;
        transform.rotation = Quaternion.Euler(0, cameraEuler.y, 0);

        //非战斗状态(空手)
        if(!state.isEquiped)
        {
        	//键盘输入的方向
            Vector3 dir = moveDir.normalized;
            float angle = Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg;
            Quaternion targetDir = Quaternion.Euler(0, angle, 0);
            //模型朝向键盘输入方向
            model.localRotation = Quaternion.Lerp(model.localRotation, targetDir, 12 * Time.deltaTime);
        }
        //战斗状态(拔剑)
        else
        {
            //模型本地旋转设置为0
            model.localRotation = Quaternion.Lerp(model.localRotation, Quaternion.Euler(0, 0, 0), 12 * Time.deltaTime);
        }

    }

上面修改模型朝向时用的是本地旋转值。空手状态时,因为模型的父级朝向目标,移动时应该是相对于父级的本地方向而不是真实的方向。拔剑状态时模型应该朝向目标,因为父级朝向目标,只需要让模型旋转归零就能与父级同样朝向。
在这里插入图片描述

主要代码

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public Transform target;
    /// <summary>
    /// 角色控制器
    /// </summary>
    CharacterController characterController;

    /// <summary>
    /// 动画控制器
    /// </summary>
    PlayerAnimController animController;

    /// <summary>
    /// 角色状态
    /// </summary>
    CharacterState state;

    /// <summary>
    /// 按键
    /// </summary>
    [SerializeField] InputKeys keys;

    /// <summary>
    /// 模型
    /// </summary>
    Transform model;

    /// <summary>
    /// 锁定状态下的摄像机
    /// </summary>
    GameObject lockCamera;

    /// <summary>
    /// 移动欲望
    /// </summary>
    Vector3 moveDir;

    private void Awake()
    {
        Cursor.visible = false;
        Cursor.lockState = CursorLockMode.Locked;
        characterController = GetComponent<CharacterController>();
        animController = GetComponentInChildren<PlayerAnimController>();
        animController.PlayerController = this;
        state = new CharacterState();
        animController.State = state;
        model = transform.Find("Model");
        lockCamera = GameObject.Find("Virtual Camera Group").transform.Find("LockLook").gameObject;
    }
    
    private void Update()
    {
        InputCheck();
        
    }

    private void FixedUpdate()
    {
        FreeLook();
        LockLook();

        Vector2 movement = SquareToCircle(moveDir);
        if(state.isLocking && state.isEquiped)
        {
            animController.SetFloat("SpeedZ", movement.y);
            animController.SetFloat("SpeedX", movement.x);
        }
        else
        {
            animController.SetFloat("SpeedZ", movement.sqrMagnitude);
        }

    }

    /// <summary>
    /// 自由视角
    /// </summary>
    void FreeLook()
    {
        if (state.isLocking)
            return;
        if (moveDir.sqrMagnitude < 0.1f)
            return;

        model.localRotation = Quaternion.Lerp(model.localRotation, Quaternion.Euler(0 ,0 ,0), 12 * Time.deltaTime);

        Vector3 dir = moveDir.normalized;
        float angle = Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg + Camera.main.transform.eulerAngles.y;
        Quaternion targetDir = Quaternion.Euler(0, angle, 0);
        transform.rotation = Quaternion.Lerp(transform.rotation, targetDir, 12 * Time.deltaTime);
    }

    /// <summary>
    /// 锁定视角
    /// </summary>
    void LockLook()
    {
        if (!state.isLocking)
            return;
        if (moveDir.sqrMagnitude < 0.1f)
            return;

        Vector3 cameraEuler = Camera.main.transform.eulerAngles;
        transform.rotation = Quaternion.Euler(0, cameraEuler.y, 0);

        //非战斗状态
        if(!state.isEquiped)
        {
            Vector3 dir = moveDir.normalized;
            float angle = Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg;
            Quaternion targetDir = Quaternion.Euler(0, angle, 0);
            model.localRotation = Quaternion.Lerp(model.localRotation, targetDir, 12 * Time.deltaTime);
        }
        //战斗状态
        else
        {
            model.localRotation = Quaternion.Lerp(model.localRotation, Quaternion.Euler(0, 0, 0), 12 * Time.deltaTime);
        }

    }

    /// <summary>
    /// 输入检测
    /// </summary>
    void InputCheck()
    {
        float fwd = 0;
        float right = 0;
        fwd = Input.GetKey(keys.fwd) ? 1 : fwd;
        fwd = Input.GetKey(keys.bwd) ? -1 : fwd;
        right = Input.GetKey(keys.right) ? 1 : right;
        right = Input.GetKey(keys.left) ? -1 : right;

        moveDir.z = Mathf.Lerp(moveDir.z, fwd, 24 * Time.deltaTime);
        moveDir.x = Mathf.Lerp(moveDir.x, right, 24 * Time.deltaTime);

        //拔出武器
        if (Input.GetKeyDown(keys.equip))
        {
            state.isEquiped = !state.isEquiped;
            animController.SetTrigger("Equip");
        }

        //锁定视角
        if(Input.GetKeyDown(keys.Lock))
        {
            state.isLocking = !state.isLocking;
            lockCamera.SetActive(state.isLocking);
            animController.SetBool("LockState", state.isLocking);
        }

    }

    /// <summary>
    /// 椭圆映射
    /// </summary>
    /// <param name="oldVec"></param>
    /// <returns></returns>
    Vector2 SquareToCircle(Vector3 oldVec)
    {
        Vector2 newVec;
        newVec.x = oldVec.x * Mathf.Sqrt(1 - (oldVec.z * oldVec.z) / 2);
        newVec.y = oldVec.z * Mathf.Sqrt(1 - (oldVec.x * oldVec.x) / 2);
        return newVec;
    }

    /// <summary>
    /// 动画根节点速度作为移动速度
    /// </summary>
    /// <param name="velocity"></param>
    public void SetCharacterVelocity(Vector3 velocity)
    {
        characterController.SimpleMove(new Vector3(velocity.x, characterController.velocity.y, velocity.z));
    }
    

}

using UnityEngine;

public class PlayerAnimController : MonoBehaviour
{
    /// <summary>
    /// 角色逻辑控制器
    /// </summary>
    PlayerController playerController;
    public PlayerController PlayerController { set { playerController = value; } }

    /// <summary>
    /// 角色状态
    /// </summary>
    CharacterState state;
    public CharacterState State { set { state = value; } }

    /// <summary>
    /// 动画状态机
    /// </summary>
    Animator anim;

    private void Awake()
    {
        anim = GetComponent<Animator>();
    }

    /// <summary>
    /// 设置Float参数
    /// </summary>
    /// <param name="name"></param>
    /// <param name="value"></param>
    public void SetFloat(string name, float value)
    {
        anim.SetFloat(name, value);
    }

    /// <summary>
    /// 触发器
    /// </summary>
    /// <param name="name"></param>
    public void SetTrigger(string name)
    {
        anim.SetTrigger(name);
    }

    public void SetBool(string name, bool value)
    {
        anim.SetBool(name, value);
    }

    /// <summary>
    /// 动画执行时每一帧调用
    /// </summary>
    private void OnAnimatorMove()
    {
        playerController.SetCharacterVelocity(anim.velocity);
    }

    #region 帧事件
    /// <summary>
    /// 拔出武器调用
    /// </summary>
    public void OnEquipStateEnter()
    {
        SetBool("IsEquiped", true);
    }

    /// <summary>
    /// 收起武器调用
    /// </summary>
    public void OnUnEquipStateEnter()
    {
        SetBool("IsEquiped", false);
    }
    #endregion

}

工程文件下载

版权声明:本文为zzy15627867866原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zzy15627867866/article/details/109429009