最终效果
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状态机行为脚本控制动画切换
状态基类
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
专栏推荐
完结
好了,我是向宇
,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!