You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何简化2D动画状态机过渡并实现完整攻击动画播放

解决Unity动画过渡繁杂+攻击动画被覆盖问题

一、先搞定攻击动画被打断的问题

核心思路是加一个攻击状态锁,让FixedUpdate里的idle/walk逻辑只在未攻击时生效:

  1. 定义布尔变量控制攻击状态:
private bool isAttacking;
  1. 修改FixedUpdate,先判断是否在攻击,不在才处理移动/待机动画:
private void FixedUpdate()
{
    if (isAttacking) return; // 攻击时直接跳过,不播放idle/walk

    bool isMoving = moveInput.x != 0 || moveInput.y != 0;
    if (isMoving)
    {
        if (moveInput.y == 0)
        {
            if (moveInput.x < 0)
            {
                animator.SetInteger(Direction, (int)MovingDirection.Left);
                animator.Play("player_walk_right");
                spriteRenderer.flipX = true;
            }
            else if (moveInput.x > 0)
            {
                animator.SetInteger(Direction, (int)MovingDirection.Right);
                animator.Play("player_walk_right");
                spriteRenderer.flipX = false;
            }
        }
        else
        {
            if (moveInput.y < 0)
            {
                animator.SetInteger(Direction, (int)MovingDirection.Down);
                animator.Play("player_walk_front");
            }
            else if (moveInput.y > 0)
            {
                animator.SetInteger(Direction, (int)MovingDirection.Up);
                animator.Play("player_walk_back");
            }
        }
    }
    else
    {
        // 根据当前方向播放对应待机动画
        switch(animator.GetInteger(Direction))
        {
            case (int)MovingDirection.Up:
                animator.Play("player_idle_back");
                break;
            case (int)MovingDirection.Down:
                animator.Play("player_idle_front");
                break;
            case (int)MovingDirection.Left:
                animator.Play("player_idle_right");
                spriteRenderer.flipX = true;
                break;
            case (int)MovingDirection.Right:
                animator.Play("player_idle_right");
                spriteRenderer.flipX = false;
                break;
        }
    }
}
  1. 修改OnFire方法,启动攻击时锁定状态:
void OnFire()
{
    if (isAttacking) return; // 防止重复触发攻击
    isAttacking = true;

    switch (animator.GetInteger(Direction))
    {
        case (int)MovingDirection.Up:
            animator.Play("player_attack_back");
            break;
        case (int)MovingDirection.Down:
            animator.Play("player_attack_front");
            break;
        case (int)MovingDirection.Left:
            animator.Play("player_attack_right");
            spriteRenderer.flipX = true;
            break;
        case (int)MovingDirection.Right:
            animator.Play("player_attack_right");
            spriteRenderer.flipX = false;
            break;
    }
}
  1. 添加攻击结束回调,通过动画事件触发:
void OnAttackEnd()
{
    isAttacking = false;
    // 攻击结束后自动回到对应方向的idle或walk(若此时有移动输入)
}

在Unity编辑器中,选中攻击动画的最后一帧,添加动画事件并调用OnAttackEnd()方法。

二、简化Animator过渡的方法

用**子状态机(Sub-State Machine)**打包同类型状态,大幅减少过渡连线:

  1. 在Animator窗口右键创建3个子状态机:IdleStates、WalkStates、AttackStates
  2. 将所有idle方向状态拖入IdleStates,walk拖入WalkStates,attack拖入AttackStates
  3. 给Animator添加Integer参数ActionType,对应:
    • 0 = 待机
    • 1 = 行走
    • 2 = 攻击
  4. 仅在三个子状态机间设置过渡:
    • IdleStates ↔ WalkStates:条件为ActionType等于对应值,可结合Direction参数判断方向
    • IdleStates/WalkStates → AttackStates:条件为ActionType等于2(或用Trigger参数AttackTrigger)
    • AttackStates → IdleStates/WalkStates:勾选过渡的Has Exit Time,动画播放完成后,根据当前移动输入设置ActionType为0或1

这样不用每个单独状态互相连线,仅需子状态机间的几条过渡,逻辑瞬间清晰。

内容的提问来源于stack exchange,提问作者hbprotoss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 16:01:28