Unity角色快速点击攻击时动画卡顿问题求助
问题描述
我编写了一个PlayerMovement脚本用于调用角色各类动画,需求是实现不同方向的攻击动画,并设置与动画时长匹配的延迟来防止玩家重复攻击。尝试过协程、Invoke方法、传入Lambda函数的外部Wait函数等多种延迟实现方式,但快速点击攻击按钮时动画始终出现卡顿。也调整过代码位置、简化布尔变量,怀疑是代码冗余导致问题,但并未解决。
原脚本代码
using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.AdaptivePerformance.Provider; using UnityEngine.EventSystems; public class PlayerMovement : MonoBehaviour { // Private variables private float moveHorizontal = 0; private float moveVertical = 0; private float attackDelay; // Public variables public float moveSpeed = 1.0f; public float movePenalty = 0.7f; // Action state flags private bool isAttacking = false; // Components public MobileJoystick mobileJoystick; private Rigidbody2D rb; private Animator animator; // Animation States private string currentState; const string IDLE = "HeroIdle"; const string WALK_X = "WalkHoriz"; const string WALK_Y = "WalkVertical"; const string WALK_DIAG = "WalkDiagonal"; const string ATTACK = "MeleeAttack"; const string BLOCK = "ShieldBlock"; const string THRUST = "Thrust"; const string UPSLASH = "UpSlash"; const string DOWNSLASH = "DownSlash"; void Start() { rb = GetComponent<Rigidbody2D>(); animator = GetComponent<Animator>(); ChangeAnimationState(IDLE); } void Update() { Vector2 movement = new Vector2(moveHorizontal, moveVertical); if (isAttacking) { moveHorizontal = Input.GetAxis("Horizontal") + mobileJoystick.InputDirection.x; moveVertical = Input.GetAxis("Vertical") + mobileJoystick.InputDirection.y; rb.velocity = movement * movePenalty; attackDelay = animator.GetCurrentAnimatorStateInfo(0).length; StartCoroutine(StopAttacking(attackDelay)); } else { moveHorizontal = Input.GetAxis("Horizontal") + mobileJoystick.InputDirection.x; moveVertical = Input.GetAxis("Vertical") + mobileJoystick.InputDirection.y; rb.velocity = movement * moveSpeed; if (moveHorizontal != 0 && Mathf.Abs(moveHorizontal) > Mathf.Abs(moveVertical)) { ChangeAnimationState(WALK_X); } else if (moveVertical != 0 && Mathf.Abs(moveVertical) > Mathf.Abs(moveHorizontal)) { ChangeAnimationState(WALK_Y); } else if (moveVertical != 0 && Mathf.Abs(moveVertical) == Mathf.Abs(moveHorizontal)) { ChangeAnimationState(WALK_DIAG); } else { ChangeAnimationState(IDLE); } } // Flip character based on direction if (moveHorizontal < 0 && transform.localScale.x > 0) { Vector3 flippedScale = transform.localScale; flippedScale.x = -0.05f; transform.localScale = flippedScale; } else if (moveHorizontal > 0 && transform.localScale.x < 0) { Vector3 unflippedScale = transform.localScale; unflippedScale.x = 0.05f; transform.localScale = unflippedScale; } // Attack input handling if (!isAttacking && Input.GetButtonDown("Fire1")) { isAttacking = true; if(moveHorizontal != 0 && Mathf.Abs(moveHorizontal) > Mathf.Abs(moveVertical)) { ChangeAnimationState(THRUST); } else if(moveVertical > 0 && Mathf.Abs(moveHorizontal) < Mathf.Abs(moveVertical)) { ChangeAnimationState(UPSLASH); } else if(moveVertical < 0 && Mathf.Abs(moveHorizontal) < Mathf.Abs(moveVertical)) { ChangeAnimationState(DOWNSLASH); } else { ChangeAnimationState(ATTACK); } } // Debug logs Debug.Log("IsAttacking: " + isAttacking); Debug.Log("currentState: " + currentState); Debug.Log("attackDelay: " + attackDelay); } void ChangeAnimationState(string newState) { if (currentState == newState) return; animator.Play(newState); currentState = newState; } IEnumerator StopAttacking(float waitTime) { yield return new WaitForSeconds(waitTime); isAttacking = false; } }
问题根源与修复方案
核心问题
在Update方法的isAttacking分支中,每帧都会启动一次StopAttacking协程,导致同一时间存在大量协程等待执行。当第一个协程结束将isAttacking设为false后,后续协程还会继续修改这个值,同时攻击动画也会被频繁干扰,最终引发卡顿。
修复步骤
- 将协程启动逻辑移到攻击触发点:只在玩家按下攻击按钮、切换攻击动画后,获取一次动画时长并启动协程,避免重复触发。
- 锁定攻击状态期间的动画切换:攻击过程中不再执行移动相关的动画切换逻辑,防止攻击动画被打断。
- 优化输入读取时机:统一在Update开头读取输入,避免重复代码。
修改后的脚本代码
using System.Collections; using UnityEngine; using UnityEngine.EventSystems; public class PlayerMovement : MonoBehaviour { // Private variables private float moveHorizontal = 0; private float moveVertical = 0; // Public variables public float moveSpeed = 1.0f; public float movePenalty = 0.7f; // Action state flags private bool isAttacking = false; // Components public MobileJoystick mobileJoystick; private Rigidbody2D rb; private Animator animator; // Animation States private string currentState; const string IDLE = "HeroIdle"; const string WALK_X = "WalkHoriz"; const string WALK_Y = "WalkVertical"; const string WALK_DIAG = "WalkDiagonal"; const string ATTACK = "MeleeAttack"; const string BLOCK = "ShieldBlock"; const string THRUST = "Thrust"; const string UPSLASH = "UpSlash"; const string DOWNSLASH = "DownSlash"; void Start() { rb = GetComponent<Rigidbody2D>(); animator = GetComponent<Animator>(); ChangeAnimationState(IDLE); } void Update() { // 统一读取输入,避免重复代码 moveHorizontal = Input.GetAxis("Horizontal") + mobileJoystick.InputDirection.x; moveVertical = Input.GetAxis("Vertical") + mobileJoystick.InputDirection.y; Vector2 movement = new Vector2(moveHorizontal, moveVertical); if (isAttacking) { rb.velocity = movement * movePenalty; // 移除每帧启动协程的逻辑 } else { rb.velocity = movement * moveSpeed; // 移动动画切换逻辑 if (moveHorizontal != 0 && Mathf.Abs(moveHorizontal) > Mathf.Abs(moveVertical)) { ChangeAnimationState(WALK_X); } else if (moveVertical != 0 && Mathf.Abs(moveVertical) > Mathf.Abs(moveHorizontal)) { ChangeAnimationState(WALK_Y); } else if (moveVertical != 0 && Mathf.Abs(moveVertical) == Mathf.Abs(moveHorizontal)) { ChangeAnimationState(WALK_DIAG); } else { ChangeAnimationState(IDLE); } } // 角色翻转逻辑 if (moveHorizontal < 0 && transform.localScale.x > 0) { Vector3 flippedScale = transform.localScale; flippedScale.x = -0.05f; transform.localScale = flippedScale; } else if (moveHorizontal > 0 && transform.localScale.x < 0) { Vector3 unflippedScale = transform.localScale; unflippedScale.x = 0.05f; transform.localScale = unflippedScale; } // 攻击输入处理 if (!isAttacking && Input.GetButtonDown("Fire1")) { isAttacking = true; string attackAnim = ATTACK; // 判定攻击方向动画 if(moveHorizontal != 0 && Mathf.Abs(moveHorizontal) > Mathf.Abs(moveVertical)) { attackAnim = THRUST; } else if(moveVertical > 0 && Mathf.Abs(moveHorizontal) < Mathf.Abs(moveVertical)) { attackAnim = UPSLASH; } else if(moveVertical < 0 && Mathf.Abs(moveHorizontal) < Mathf.Abs(moveVertical)) { attackAnim = DOWNSLASH; } ChangeAnimationState(attackAnim); // 仅在攻击触发时获取动画时长并启动一次协程 float attackDelay = animator.GetCurrentAnimatorStateInfo(0).length; StartCoroutine(StopAttacking(attackDelay)); } } void ChangeAnimationState(string newState) { if (currentState == newState) return; animator.Play(newState); currentState = newState; } IEnumerator StopAttacking(float waitTime) { yield return new WaitForSeconds(waitTime); isAttacking = false; // 攻击结束后切回/idle或移动动画 ChangeAnimationState(IDLE); } }
额外优化建议
- 可以在Animator中给攻击动画添加动画事件,在动画结束时直接调用
StopAttacking方法,比用WaitForSeconds更精准(避免因动画速度缩放导致的时间偏差)。 - 移除不必要的
using引用,比如System.Linq、UnityEngine.AdaptivePerformance.Provider,减少资源占用。
内容的提问来源于stack exchange,提问作者James Jones
相关产品推荐
相关产品推荐

