Unity 第三人称角色控制器:动画混合树、Cinemachine等功能或插件的使用

前言:

无论是RPGACTAVG还是TPS游戏,都有大量的以第三人称为人物视角的游戏作品,尤其是TPS,直接以第三人称为特点来命名

游戏类型:

  • RPG:角色扮演游戏
  • ACT:动作游戏
  • AVG:冒险游戏
  • SRPG:模拟类角色扮演游戏
  • TPS:第三人称设计游戏

而对于第三人称而言,人物角色的所有状态全部暴露在玩家的视角之下,关于角色的所有的动画、逻辑、相机表现都要能够让玩家感受到最完美的体验

1,角色模型导入:

在开始使用Unity设计开发第三人称游戏之前,我们需要先获得合适的角色模型。Unity支持多种格式的模型,而当我们使用具有动画的模型时,往往选择fbx格式的模型

选择好模型后,就可以将模型导入到Unity中,然后我们需要对模型进行一些参数调整,点击模型后,就可以在Inspector面板看到这些参数:
在这里插入图片描述

也许大家看到这些英文参数有些蒙,没关系,可以点击右上角的问号查阅官方文档来了解这些参数的意义,同时我也在下面列出了几个关键点来帮助大家完成设置:

1,如果角色模型导入场景过大:

  • 针对这个问题,大多数小伙伴可能会直接调整场景中角色的Transform组件的Scale来进行缩放,我们要尽量避免这么做,而是在模型的Scale Factor来调整你的模型的大小:
    在这里插入图片描述

2,对于Rig选项下的Animation Type选项的选择:

  • 由于我们的角色往往是人类,所以我们一般选择Humanoid作为Animation Type,关于这个选项还有更好的方法,选择一个可以让你动画正常播放的选项:
    在这里插入图片描述

3,关于第三个选项Animation

  • 后面会讲到
2,角色动画控制器

为了使得角色可以根据玩家的操作做出相应的动画,可以使用动画控制器来对一系列的状态动画进行管理,这里可以查看我写的关于角色动画的博客:

Unity人物动画:

但是本次案例,我们讲一个全新的动画配置效果:动画混合树

通过动画混合树,可以使得我们的角色动画过渡的更加平滑,使得玩家的游戏体验更加真实,而创建动画混合树的具体步骤为:

2.1,创建动画混合树:

1,在Animator面板右击选择Create state,并在其中选择From New Blend Tree即可完成创建,
在这里插入图片描述
创建完Blend Tree后,我们点击即可进入动画控制树配置面板:
在这里插入图片描述
可以看到,创建该State同时会自动创建一个参数,这个参数类似于我们添加一个float参数,可以通过调整这个值来平滑的切换几种 状态:下面我们就为该动画混合树添加几种状态:

右键刚刚创建Blend Tree,或者在右侧点击加号,即可为该树添加状态,也就是动画,
在这里插入图片描述
完成创建后,主要需要调整的几个地方在下面的红框里面:
在这里插入图片描述

关于Blend Type,即动画混合类型,通过点击倒角发现有下面 几种混合方式:
在这里插入图片描述

因为本案例采用速度控制动画切换的方式,因此直接选择1D来作为动画混合类型,即只需要通过最右边的一个数值框的参数来控制动画切换即可,当我们把动画拖入后,选择好三个状态对应的参数,当参数权值慢慢靠近某种状态的对应参数时,动画也会慢慢的转换,如图:
在这里插入图片描述
从图中可以看出,三种动画经过混合后,可以实现这种由慢到快的平滑的系列动画

2.2,如何通过脚本使用混合树:

和Unity人物动画配置那一章类似,我们可以直接使用Blend来实现动画平滑移动,比如说,人物从静止到移动的动画切换,可以通过一个插值来获取动画需要切换的速度:

	private float Blend;
 	private Animator ani;
	private void Start()
    {
        ani = this.GetComponent<Animator>();
    }    
    private void Update()
    { 
        if(Input.GetKey(KeyCode.W))
        {
            Blend = Mathf.Lerp(Blend, 0.5f, 0.01f);            
        }
        if (!Input.GetKey(KeyCode.W))
        {
            Blend = Mathf.Lerp(Blend, 0, 0.01f);
        }
        ani.SetFloat("Blend", Blend);
    }

这样当我们按下W时,控制动画混合树的参数就会插值增加,直到0.5(对应移动状态)而不按W时,动画插值回到0,人物慢慢停止移动

3,角色移动旋转控制

3.1,为角色添加组件

在进行脚本编写之前,我们需要为场景中模型添加一些组件来帮助我们实现角色移动、旋转,以及角色动画控制等功能:

添加角色控制器:

  • 为了实现动画控制,需要为角色添加一个Animator来承载我们刚刚创建的Player动画控制器,同时需要讲模型的骨骼拖入到Avater中,至于下面的参数可以根据需要调整
    在这里插入图片描述

添加刚体组件:

  • 运动的物体需要通过刚体来模拟其物理效果,我们需要控制角色的移动和旋转,因此需要该组件,但是同时,该组件的某些物体特性又是我们不需要的:
  • 因为我们控制物体时旋转时,仅仅需要其在Y轴上的效果,而X轴或者Z轴的旋转往往会导致我们的角色倾倒在地,这显然不是我们需要的,所以我们可以通过在Constraints中的Freeze Rotation中勾选X和Z来冻结其旋转功能
    在这里插入图片描述

添加碰撞体:

  • 为了确保角色可以尽量真实,而不是无敌到可以穿越墙体或地面,就需要给其添加合适的碰撞体,可以点击Edit ColliderScene面板调整其大小,也可以直接调整下面三行参数来调整:
    在这里插入图片描述

3.2、脚本控制角色运动

第一种方案:

在这里插入图片描述

移动:

  • 首先是角色的移动,通过系列计算来计算出角色移动的位置,然后使用rigidbody.MovePosition()方法来控制角色移动到该位置

旋转:

  • 判断角色的面对的方向与移动方向的角度来判断是否需要旋转,如果角度大于0,则可以通过Vector3.Slerp()方法来进行插值旋转

动画:

  • 监控玩家键盘的输入,插值改变控制动画播放的参数,并将该参数与移动旋转结合,这样动画就可以完美适配角色的状态的改变
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerControl : MonoBehaviour
{
    private Transform m_transform;
    private Rigidbody rigidbody;
    private Animator ani;

    //移动速度与旋转速度 
    private float transloatSpeed=2.5f;   
    private float rotateSpeed=5;

    private Vector3 targetPos;
    private float Blend;
	//获取前面添加的组件
    private void Start()
    {
        m_transform = this.transform;
        rigidbody = this.GetComponent<Rigidbody>();
        ani = this.GetComponent<Animator>();
    }  
    private void Update()
    {
        //引入动画参数
        ani.SetFloat("Blend", Blend);
        
        //通过监控用户输入,判定角色移动向量,并在FixUpdate中控制角色移动到目标位置
        Vector3 v3 = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")).normalized;
        targetPos = v3 * Time.deltaTime * transloatSpeed * Blend;

        //通过角色移动目标向量与角色自身的正方向插值决定旋转角度,并使用rotation进行旋转
        Vector3 targetDir = Vector3.Slerp(m_transform.forward, v3, rotateSpeed * Time.deltaTime);
        m_transform.rotation = Quaternion.LookRotation(targetDir);

        //监控用户输入,控制动画参数
        if (Input.GetKey(KeyCode.W)||Input.GetKey(KeyCode.S)|| Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D))
        {            
            if(Input.GetKey(KeyCode.LeftShift))
            {
                Blend = Mathf.Lerp(Blend, 1f, 0.03f);
                return;
            }
            Blend = Mathf.Lerp(Blend, 0.5f, 0.03f);
        }
        if (!(Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D)))
        {
            Blend = Mathf.Lerp(Blend, 0, 0.03f);
        }
    }
    private void FixedUpdate()
    {
        //通过目标向量与自身位置控制角色移动
        rigidbody.MovePosition(targetPos + m_transform.position);
    }
}

这样就可以有一个比较好的旋转与启动的效果,但是当我们启动后,发现角色的移动是基于世界坐标,我们按下W时,永远都是面向世界坐标的Z轴移动,而不是向着当前状态角色面对的方向移动,所以这种设计比较适合视角固定的第三人称游戏手游,比如说王者荣耀,可以得到一个较为顺滑的体验

产生 这种情况是因为:

  • rigidbody.MovePosition()是相对于世界坐标来计算的,而产生旋转后,角色的世界坐标方向不发生改变,所以再次移动时,依旧会以坐标轴计算

第二种方案:

大致的显示效果(类似于原神的第三人称控制效果):
在这里插入图片描述

角色控制脚本:

为了可以和相机有一个更好的融合,通过对于相机方向的判断,然后决定角色的正方向,这样可以直接伴随相机来进行设计,为了有一个更好的效果,可以使用一个相机插件Cinemachine来使得游戏体验更好:

由于我们相机镜头可以自由围绕Y轴自由旋转,所以可以让相机来承担旋转人物,角色的角度可以根据相机的角度来改变,这样可以有一更好的体验:当我们按下W时,角色就会背向相机移动,给玩家更真实的体验:

大概解释一下代码:

  • 1,首先当我们输入控制变量时(WASD),动画参数blend会插值增大,同时速度也会根据该参数的变化而变化,角色会产生向前移动的效果
  • 2,接下来要控制角色的转向,我们角色转向的参照物是依据与相机,当玩家按下W时,我们要保证角色背对相机来移动,要实现这样的效果,首先通过
    Vector3.ProjectOnPlane(cam.transform.forward, Vector3.up)方法来获取相机Z轴在世界坐标中X轴与Y轴的投影,即水平面,而如果角色forword在与相机的forwordX轴与Z轴的投影不一致,则插值旋转到相机Z轴的投影方向,这样就可以保证玩家按下W的时候,真的会有向前走的感觉,如图,让两个粉色向量方向保持一致
    在这里插入图片描述
  • 3,同理对于按下ASD键时,分别对应相机不同轴在水平面(X轴与Z轴组合的面)的投影,让角色分别旋转到对应的角度即可
  • 4,通过一个Vector3向量来控制角色移动,并加入影响速度的moveSpeedblend两个变量

下面就是第二个控制脚本的代码,需要注意,除了之前角色添加的组件,还需要获取要跟随的相机

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

public class PlayerTest : MonoBehaviour
{
    /// <summary>
    ///获取角色的跟随相机
    /// </summary>
    public Camera cam;
    /// <summary>
    /// 获取角色相关组件
    /// </summary>
    private Transform m_transform;
    private Rigidbody rig;
    private Animator anim;
    /// <summary>
    /// 定义几个移动参数
    /// </summary>
    private Vector3 targetPos;
    private Vector3 movePos;
    private Vector3 camPos;
    
    public float moveSpeed=10f;
    private float blend;

    private void Start()
    {
        m_transform = this.transform;
        rig = this.GetComponent<Rigidbody>();
        anim = GetComponent<Animator>();
    }
    private void Update()
    {
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
        anim.SetFloat("Blend", blend);
       // cam.transform.LookAt(m_transform);
       // camPos = m_transform.position + new Vector3(0, camy, camZ);
       // cam.transform.position = Vector3.Lerp(cam.transform.position, camPos, 0.1f);

        if (Input.GetKey(KeyCode.W))
        {
            blend = Mathf.Lerp(blend, 0.5f, 0.02f);
            targetPos.z = 1;           
            Vector3 targetDir = Vector3.Slerp(m_transform.forward, Vector3.ProjectOnPlane(cam.transform.forward, Vector3.up), 0.02f);
            m_transform.rotation = Quaternion.LookRotation(targetDir, Vector3.up);
        }
        if (Input.GetKey(KeyCode.S))
        {
            blend = Mathf.Lerp(blend, 0.5f, 0.02f);
            targetPos.z = 1;           
            Vector3 targetDir = Vector3.Slerp(m_transform.forward, -Vector3.ProjectOnPlane(cam.transform.forward, Vector3.up), 0.02f);
            m_transform.rotation = Quaternion.LookRotation(targetDir,Vector3.up);
        }
        if (Input.GetKey(KeyCode.A))
        {
            blend = Mathf.Lerp(blend, 0.5f, 0.02f);
            targetPos.z = 1;
           
            Vector3 targetDir = Vector3.Slerp(m_transform.forward, -Vector3.ProjectOnPlane(cam.transform.right, Vector3.up), 0.02f);
            m_transform.rotation = Quaternion.LookRotation(targetDir, Vector3.up);
        }
        if (Input.GetKey(KeyCode.D))
        {
            blend = Mathf.Lerp(blend, 0.5f, 0.02f);
            targetPos.z = 1;
            Vector3 targetDir = Vector3.Slerp(m_transform.forward, Vector3.ProjectOnPlane(cam.transform.right, Vector3.up), 0.02f);
            m_transform.rotation = Quaternion.LookRotation(targetDir, Vector3.up);
        }
        if (!Input.GetKey(KeyCode.W)&&!Input.GetKey(KeyCode.S)&& !Input.GetKey(KeyCode.A) && !Input.GetKey(KeyCode.D))
        {
            targetPos.z = 0;
            blend = Mathf.Lerp(blend, 0f, 0.02f);
        }

        movePos = targetPos * Time.deltaTime * moveSpeed* blend;
        transform.Translate(movePos);
    }

}

相机插件:

在完成角色控制脚本的编写后,需要为角色添加一个跟随相机,为了有一个比较好的效果,就不自己编写脚本来实现这种跟随功能, ,而是使用一个官方的相机组件Cinemachine来完成相机的跟随自由视角的效果:

首先点击Windows中选择Package Manager打开资源管理器,然后再输入框输入Cinemachine找到该相机插件,下载后导入到项目中,由于我已经下载导入了,所以显示为Up to date

在这里插入图片描述

当我们导入该插件后,就可以在上面的导航栏中看到Cinemachine的选项,点击后选择Create FreeLook Camera创建一个视角可以自由移动的相机,关于Cinemachine的具体细节大家可以查阅官方文档,这里我仅对要用的参数做一些说明:
Cinemachine

最先调整的是Look AtFollow参数,即相机看着谁与相机要跟随谁,很明显,都是我们的角色,所以可以将角色(或者角色的一部分,比如眼睛更合适)拖入其中

接下来我们观察人物的周围,发现有三个红色的圈,标明了相机可以围绕角色移动的范围,我们同样可以在虚拟相机上面调整其参数:
在这里插入图片描述

完成后,我们发现在移动相机时会有一些画面卡顿的问题,这个时候,我们可以点击我们的MainCamera,来修改其中一些参数和图片中一致即可:

在这里插入图片描述

总结

完成这些设置,你可能得到的结果并不尽人意,但是不要灰心,并不是你做的不好,而是最终的效果是受到多个参数影响的,你可以花一些时间来慢慢调整这些参数,打磨你的游戏,最终会有一个好的结果的

在花费一定时间后,如果你还是觉得效果不尽人意,那你可能需要换一种思路来设计你的第三人称控制器。没有最好的,只有最合适的,加油

  • 11
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心之凌儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值