Unity动画系统实现人物基本动画、跳远、下蹲

一、素材准备

首先图集切割,sprite mode为Multiple;设置每个格子的像素数量为16(图片下载的像素);选择Point模式不添加任何过滤。

image

点击Sprite Editor,切割方式选择按行列切割,再将锚点设置为人物脚底中心。点击apply保存切割后的图片

image

 二、创建人物基本动画

基本动画的创建与使用

创建animator动画控制器 --> 创建clip帧动画

人物Player上添加Animator组件,并创建Animator Controller。

创建Idle状态,添加对应的idle动画片段。素材图拖入时轴,设置采样率为10,预览动画效果。跑步和走路动画同理。

image

 状态机的转换如图。idle到walk的转换条件为变量velocityX大于0.1,walk到run的转换条件是变量velocityX大于2.5。在设置中取消勾选“Has Exit Time”,Transition Duration为0实现立即切换。

image

image

 混合树实现跳跃动画

创建跳跃动画的五个阶段:起跳、空中飞行、最高点、下落和着陆。通过使用animate中的混合树(Blend Tree)工具,将跳跃动画分为不同的阶段进行管理。

1.在animator窗口中创建新的混合树Blend Tree,命名为jump。

2.选择flow类型,Parameter使用velocity y作为参数。

3.添加动画片段到混合树中,设置切换条件。可以打印出跳跃高度,更清楚的看到各阶段用velocity.y显示的跳跃高度

4.PlayerAnimation脚本中将velocity.y值传递给动画播放器实现动画切换

fa3cdc9e-426f-4d05-b15b-301a53d7b0e7

跳跃状态的进入与退出

1.创建is ground变量,用于检测玩家是否在地面上。

2.设置跳跃状态的进入条件为is ground=false,退出条件为is ground=true。

3.Any State连接Jump,实现跳跃状态对其他状态的打断功能。
 
落地动画的处理
1.将land动画添加到animator窗口中。
2.设置落地动画的退出条件为is ground=true,并在动画播放完毕后切换到idle状态。
3.落地动画Exit的条件是velocity.x大于0.1,以打断落地动画,使角色在落地后立即跑步或走路。

6011683c-ff4e-4243-955c-03c746ac71b3

 使用Sub State Machine次级状态机实现人物下蹲动画

1.在Animator窗口空白处右键,创建Sub StateMachine。

2.命名为Crouch,用于管理下蹲动画的状态转换。

3.双击打开子状态机,可以看到与Base Layer相似的结构。

  • 配置下蹲动画转换条件
    •  从Idle状态连接到Crouch状态,设置条件为isCrunch=true。
    • 下蹲起始动画播放完成后自动过渡到下蹲保持状态
    • 设置退出条件为isCrouch=false,确保动画完整播放后进行状态转换。可通过Exit节点返回上级状态机,或直接连接到特定动画状态
    • 次级状态机如图e9bb4bdd-94f2-4066-bd46-240b437bd1be

4.完整的状态机如图

989e2fc9-94b6-4aa3-8eb7-fc11bbdaddf0

5.编写代码控制下蹲状态

  • PlayerController脚本,添加isCrunch布尔值变量。检测条件:inputDirection.y < -0.5f && physicsCheck.isGround。即在Move函数中添加下蹲状态判断逻辑。检测输入方向键的Y轴值,小于一定数值时判断为下蹲。结合地面检测,确保人物在地上时才能下蹲。
  • PlayerAnimation脚本中,获取PlayerController组件。传递isCrunch布尔值到Animator,控制下蹲动画的播

6.由于下蹲时碰撞器的位置并不会刷新,故需要代码控制。在PlayerController脚本move函数中

  • 获取CapsuleCollider2D组件,调整其size和offset。
  • 记录原始size和offset值,以便在退出下蹲状态时恢复。
  • 设置下蹲时的size和offset值,确保碰撞体与动画匹配。

三、动画切换代码编写

1、PlayerController完整代码

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
    private Rigidbody2D rb;
    private PhysicsCheck physicsCheck;
    private CapsuleCollider2D collider;
    
    public PlayerInputControl inputControl;
    public Vector2 inputDirection;
    [Header("基本参数")]
    public float speed;
    public float runSpeed;
    public float walkSpeed;
    public float jumpForce;

    public bool isCrounch;
    private Vector2 originOffest;
    private Vector2 originSize;
    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        inputControl = new PlayerInputControl();
        physicsCheck = GetComponent<PhysicsCheck>();
        collider = GetComponent<CapsuleCollider2D>();
        originSize = collider.size;
        originOffest = collider.offset;
        
        //跳跃按钮事件注册
        inputControl.GamePlay.Jump.started += JumpOnstarted;

        #region 强制走路
        runSpeed = speed;
        walkSpeed = runSpeed/2;
        //持续按下时走路形态,取消时跑步
        inputControl.GamePlay.WalkButton.performed += ctx =>
        {
            if (physicsCheck.isGround)
                speed = walkSpeed;
        };
        
        inputControl.GamePlay.WalkButton.canceled += ctx =>
        {
            if (physicsCheck.isGround)
                speed = runSpeed;
        };
        #endregion
       
    }

    private void OnEnable()
    {
        inputControl.Enable();
    }

    private void OnDisable()
    {
        inputControl.Disable();
    }

    private void Update()
    {
        inputDirection = inputControl.GamePlay.Move.ReadValue<Vector2>();
    }

    private void FixedUpdate()
    {
        Move();
    }

    public void Move()
    {
        if(!isCrounch)
            //人物移动
            rb.velocity = new Vector2(inputDirection.x * speed * Time.deltaTime, rb.velocity.y);
        
        //由于手柄操作inputDirection的值不一定只有-1和1,所以需要处理
        int faceDir =  (int) transform.localScale.x;
        if (inputDirection.x > 0)
            faceDir = 1;
        if (inputDirection.x < 0)
            faceDir = -1;
            
        //人物翻转
        transform.localScale = new Vector3(faceDir,1,1);
        
        //人物下蹲
        isCrounch = inputDirection.y < -0.5f && physicsCheck.isGround;
        if (isCrounch)
        {
            //修改碰撞体参数
            collider.offset = new Vector2(collider.offset.x,0.85f);
            collider.size = new Vector2(collider.size.x,1.7f) ;
        }
        else
        {
            //还原
            collider.offset = originOffest;
            collider.size = originSize;
        }
    }
    
    private void JumpOnstarted(InputAction.CallbackContext obj)
    {
        if (physicsCheck.isGround)
            rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
    }

}

 

2、、PlayerAnimation脚本专门管理动画逻辑

using System;
using UnityEngine;
public class PLayerAnimation : MonoBehaviour
{
private Animator animator;
private Rigidbody2D rb;
private PhysicsCheck physicsCheck;
private PlayerController player;

private void Awake()
{
animator = GetComponent<Animator>();
rb = GetComponent<Rigidbody2D>();
physicsCheck = GetComponent<PhysicsCheck>();
player = GetComponent<PlayerController>();

}

private void Update()
{
SetAnimation();
}

public void SetAnimation()
{
animator.SetFloat("velocityX",Math.Abs(rb.velocity.x));
animator.SetFloat("velocityY",rb.velocity.y);
animator.SetBool("isGround",physicsCheck.isGround);
animator.SetBool("isCrounch",player.isCrounch);
}
}

posted on 2025-08-24 23:55  juaa  阅读(77)  评论(0)    收藏  举报