【unity实战】unity使用最少代码最少的animator连线实现角色平滑的动画切换和动画动作完美匹配(InputSystem+Animator+RootMotion)

王者杯·14天创作挑战营·第5期 10w+人浏览 646人参与

最终效果

unity实现角色平滑的动画切换和动画动作完美匹配


前言

注意,本文不会带你一步步去实现,省略一些基础性的介绍,因为这和我之前实现的类PUBG吃鸡游戏的角色控制器的思路大同小异,算是在这个基础上进一步进行优化,建议可以先去前往查看该文章:【unity实战】InputSystem+Cinemachine+Animator实现一个第三人称控制器,类PUBG吃鸡游戏的角色控制器

使用的关键技术思路

1、模型

https://www.aplaybox.com/details/model/CLCDE3lCEAbR
在这里插入图片描述

2、PMX模型转FBX模型

参考:【unity小技巧】下载原神模型,在Blender中PMX模型转FBX模型,并导入到Unity中使用,实现基于光照模型的内置和URP卡通渲染

3、lucy动画文件

https://assetstore.unity.com/packages/3d/animations/lucy-238874
在这里插入图片描述

免费学习版
https://pan.baidu.com/s/1vks3DNE2PLhesSOo1-gCdA?pwd=es5h
提取码: es5h

4、InputSystem输入控制

对InputSystem还不熟悉的小伙伴可以先看:【unity游戏开发——InputSystem】

5、使用Character Controller实现人物移动

这里我们使用Character Controller实现第三人称控制,如果你还不知道具体如何使用CharacterController,可以参考:

6、添加虚拟相机Cinemachine跟随

如果你还不知道如何使用Cinemachine,可以参考:【unity知识】最新的虚拟相机Cinemachine3简单使用介绍

7、分享两种移动旋转模式(重点)

7.1 人物一直面向移动方向(自由视角)

private float targetRot;
private float rotationVelocity;
[SerializeField] private float rotationSmoothTime = 0.1f;
    
//一直面向移动方向
void SetPlayerRotationMove()
{
    //计算输入方向,将其转换为世界空间
    Vector3 inputDir = new Vector3(inputController.moveVector2.x, 0.0f, inputController.moveVector2.y).normalized;
    //计算目标旋转角度,Atan2函数:计算点 (x, z) 与原点连线相对于正Z轴的弧度制角度
    targetRot = Mathf.Atan2(inputDir.x, inputDir.z) * Mathf.Rad2Deg + mainCamera.transform.eulerAngles.y;
    //平稳地旋转玩家,使其面向他们移动的方向。
    float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRot, ref rotationVelocity, rotationSmoothTime);
    //旋转玩家
    transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
}

Mathf.SmoothDampAngle参数说明:

  • current: 当前角度
  • target: 目标角度
  • ref currentVelocity: 引用参数,存储当前的角速度(需要声明为类字段)
  • smoothTime: 达到目标大致所需的时间(秒)

7.2 人物一直面向相机方向(锁定视角)

private float targetRot;
private float rotationVelocity;
[SerializeField] private float rotationSmoothTime = 0.1f;

//一直面向相机方向
void SetPlayerRotation()
{
    // 获取相机的旋转(只考虑 Y 轴的旋转)
    targetRot = Camera.main.transform.eulerAngles.y;
    //平稳地旋转玩家,使其面向他们移动的方向。
    float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRot, ref rotationVelocity, rotationSmoothTime);
    //旋转玩家
    transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
}

8、使用Root Motion实现动画和位移同步

如果你还了解Root Motion动画混合树的知识,可以先去查看:【unity实战】OnAnimatorMove+Root Motion+Blend Tree+CharacterController解决Animator动画和位移不同步问题,完美实现移动动画动作匹配

private void OnAnimatorMove()
{
    Vector3 deltaPosition = animator.deltaPosition;//当前帧相对于上一帧的位移增量

    // 移动 CharacterController
    characterController.Move(deltaPosition);
}

开启animator动画物理
在这里插入图片描述

9、添加BlendTree混合动画

如果你还了解Blend Tree动画混合树的知识,可以先去查看:【unity游戏开发入门到精通——动画篇】动画混合(Blend Tree)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10、添加Animator状态机行为脚本控制动画切换

参考:【unity游戏开发入门到精通——动画篇】Animator状态机行为脚本

状态基类

using UnityEngine;

public abstract class BaseStateMachine : StateMachineBehaviour
{
    protected PlayerController playerController;
    protected InputController inputController;
    protected CharacterController characterController;
    
    protected bool isMove;
    protected bool isBeforeStopMove;//停止之前是否在移动

    // 进入状态时,第一个 Update 中调用
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        inputController = animator.GetComponent<InputController>();
        characterController = animator.GetComponent<CharacterController>();
        playerController = animator.GetComponent<PlayerController>();
        isBeforeStopMove = false;
        animator.SetFloat(AnimatorController.StopSpeed, 0);
    }

    // 除第一帧和最后一帧,每个 Update 上调用
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        isMove = inputController.moveVector2.magnitude > 0.1f;
        if (isMove)
        {
            isBeforeStopMove = true;
            animator.SetFloat(AnimatorController.StopSpeed, animator.GetFloat(AnimatorController.InputY));
        }
    }

    // 退出状态时,最后一个 Update 中调用
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {

    }

    // OnAnimatorMove 后调用
    override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        // Implement code that processes and affects root motion
    }

    // OnAnimatorIK 后调用
    //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    // Implement code that sets up animation IK (inverse kinematics)
    //}
}

蹲下移动状态

using UnityEngine;

public class CrouchMove : BaseStateMachine
{
    // 进入状态时,第一个 Update 中调用
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateEnter(animator, stateInfo, layerIndex);
    }

    // 除第一帧和最后一帧,每个 Update 上调用
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateUpdate(animator, stateInfo, layerIndex);
        if (!inputController.isCrouch && !animator.IsInTransition(0)) animator.CrossFade(AnimatorController.StandMove, 0.25f);
    }

    // 退出状态时,最后一个 Update 中调用
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
       base.OnStateExit(animator, stateInfo, layerIndex);
    }
}

站立移动状态

using UnityEngine;

public class StandMove : BaseStateMachine
{
    // 进入状态时,第一个 Update 中调用
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateEnter(animator, stateInfo, layerIndex);
    }

    // 除第一帧和最后一帧,每个 Update 上调用
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateUpdate(animator, stateInfo, layerIndex);
        if (inputController.isCrouch && !animator.IsInTransition(0)) animator.CrossFade(AnimatorController.CrouchMove, 0.25f);

        if (inputController.isJump && !animator.IsInTransition(0))
        {
            animator.CrossFade(AnimatorController.Slide, 0.25f);
        }

        if (!isMove && !animator.IsInTransition(0) && isBeforeStopMove) animator.CrossFade(AnimatorController.Stop, 0.25f);

    }

    // 退出状态时,最后一个 Update 中调用
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateExit(animator, stateInfo, layerIndex);
    }
}

源码

https://gitee.com/unity_data/unity-third-person-control
在这里插入图片描述


专栏推荐

地址
【unity游戏开发入门到精通——C#篇】
【unity游戏开发入门到精通——unity通用篇】
【unity游戏开发入门到精通——unity3D篇】
【unity游戏开发入门到精通——unity2D篇】
【unity实战】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架/工具集开发】
【unity游戏开发——模型篇】
【unity游戏开发——InputSystem】
【unity游戏开发——Animator动画】
【unity游戏开发——UGUI】
【unity游戏开发——联网篇】
【unity游戏开发——优化篇】
【unity游戏开发——shader篇】
【unity游戏开发——编辑器扩展】
【unity游戏开发——热更新】
【unity游戏开发——网络】

完结

好了,我是向宇,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

向宇it

创作不易,感谢你的鼓励

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

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

打赏作者

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

抵扣说明:

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

余额充值