Unity中如何让重写的Attack函数等待协程完成后返回int值?
蓄力攻击协程等待问题解决方案
问题场景
弓箭武器的蓄力攻击实现中,Attack方法启动协程后立刻返回原始的attackMove值,无法等待蓄力完成后返回修改后的数值。同时Attack是重写的抽象方法,必须返回int类型,且仅有弓箭武器需要该蓄力逻辑;调用端的后续动画等逻辑必须依赖Attack的返回值才能执行。
原始代码
弓箭武器类(W_02_Bow)
public class W_02_Bow : WeaponDefault { public override int Attack(int attackMove) { StartCoroutine(ChargeAttack(attackMove)); return attackMove; } private IEnumerator ChargeAttack(int attackMove) { float chargeTime = 0f; while (chargeTime <= 5f && Input.GetMouseButton(0)) { Debug.Log("Charging..."); chargeTime += Time.deltaTime; yield return null; } if (chargeTime >= 5f) Debug.Log("Charged!"); chargeTime = 0; yield return new WaitUntil(() => Input.GetMouseButtonUp(0)); attackMove = attackMove % 3 + 1; } }
调用端Attack方法
public void Attack() { if (Input.GetMouseButton(0) && isLand) { if (IsCanAct((int)weaponType + 5) && !isAttack) { isAttack = true; playerNowStamina -= (int)weaponType + 5; playerAnim.SetInteger(_triggerNum, (int)AnimState.Attack); attackMove = weapons[(int)weaponState].GetComponent<WeaponDefault>().Attack(attackMove); playerAnim.SetInteger(_action, attackMove); playerAnim.SetTrigger(_trigger, () => { isAttack = false; Variables.Instance?.WeaponVfx?[0]?.gameObject.SetActive(false); }, (float)weaponType * 0.2f + 0.6f ); } } }
问题分析
- Unity协程是异步执行的,
StartCoroutine调用后会立刻继续执行后续代码,导致Attack直接返回原始值,无法等待蓄力逻辑完成。 - 抽象方法要求
Attack同步返回int,不能直接将其改为异步方法(如async Task<int>),否则会破坏抽象类的契约。
解决方案
通过回调机制重构逻辑,既满足抽象类的约束,又能等待蓄力完成后执行后续依赖逻辑:
1. 扩展武器抽象类
在WeaponDefault中新增带回调的异步攻击方法,其他武器可直接使用默认实现:
public abstract class WeaponDefault : MonoBehaviour { // 原同步攻击方法,保持抽象约束 public abstract int Attack(int attackMove); // 新增异步攻击方法,带结果回调 public virtual void AttackAsync(int attackMove, Action<int> onComplete) { // 默认同步返回原始值,兼容非蓄力武器 onComplete?.Invoke(attackMove); } }
2. 重构弓箭武器类
重写AttackAsync方法,在蓄力协程完成后通过回调传递最终的attackMove;原同步Attack方法可抛出异常避免误用:
public class W_02_Bow : WeaponDefault { public override int Attack(int attackMove) { throw new InvalidOperationException("弓箭武器请使用AttackAsync方法执行蓄力攻击"); } public override void AttackAsync(int attackMove, Action<int> onComplete) { StartCoroutine(ChargeAttack(attackMove, onComplete)); } private IEnumerator ChargeAttack(int initialAttackMove, Action<int> onComplete) { float chargeTime = 0f; while (chargeTime <= 5f && Input.GetMouseButton(0)) { Debug.Log("Charging..."); chargeTime += Time.deltaTime; yield return null; } if (chargeTime >= 5f) Debug.Log("Charged!"); chargeTime = 0; yield return new WaitUntil(() => Input.GetMouseButtonUp(0)); int finalAttackMove = initialAttackMove % 3 + 1; // 蓄力完成后通过回调返回结果 onComplete?.Invoke(finalAttackMove); } }
3. 修改调用端逻辑
判断当前武器类型,对弓箭使用异步回调逻辑,其他武器保持原有同步逻辑:
public void Attack() { if (Input.GetMouseButton(0) && isLand) { if (IsCanAct((int)weaponType + 5) && !isAttack) { isAttack = true; playerNowStamina -= (int)weaponType + 5; playerAnim.SetInteger(_triggerNum, (int)AnimState.Attack); WeaponDefault currentWeapon = weapons[(int)weaponState].GetComponent<WeaponDefault>(); // 区分弓箭和其他武器 if (currentWeapon is W_02_Bow bowWeapon) { bowWeapon.AttackAsync(attackMove, (finalAttackMove) => { attackMove = finalAttackMove; // 蓄力完成后执行后续动画逻辑 playerAnim.SetInteger(_action, attackMove); playerAnim.SetTrigger(_trigger, () => { isAttack = false; Variables.Instance?.WeaponVfx?[0]?.gameObject.SetActive(false); }, (float)weaponType * 0.2f + 0.6f); }); } else { // 非蓄力武器保持原有同步逻辑 attackMove = currentWeapon.Attack(attackMove); playerAnim.SetInteger(_action, attackMove); playerAnim.SetTrigger(_trigger, () => { isAttack = false; Variables.Instance?.WeaponVfx?[0]?.gameObject.SetActive(false); }, (float)weaponType * 0.2f + 0.6f); } } } }
方案说明
- 回调机制避免了主线程阻塞,同时满足了调用端必须等待蓄力完成后执行后续逻辑的需求。
- 通过扩展抽象类的方式,保持了与其他武器的兼容性,无需修改非蓄力武器的代码。
- 类型判断确保只有弓箭武器触发异步蓄力逻辑,符合业务场景要求。
内容的提问来源于stack exchange,提问作者EarthLEE
相关产品推荐
相关产品推荐

