Unity协程实战:从yield return到WaitUntil的7个高频使用场景解析

张开发
2026/4/19 8:05:30 15 分钟阅读

分享文章

Unity协程实战:从yield return到WaitUntil的7个高频使用场景解析
Unity协程实战从yield return到WaitUntil的7个高频使用场景解析在Unity游戏开发中协程Coroutine是实现异步逻辑的重要工具。不同于传统的同步代码执行方式协程允许我们将任务分解为多个步骤并在特定条件下暂停和恢复执行。这种特性使得协程特别适合处理需要等待的游戏逻辑如技能冷却、资源加载、NPC行为等场景。本文将深入解析Unity协程在实际开发中的7个高频使用场景帮助开发者理解不同Yield Instruction的适用性并掌握如何根据具体需求选择合适的等待指令。我们将从基础概念出发逐步深入到复杂应用场景提供可直接复用的代码示例和最佳实践建议。1. 角色技能冷却系统实现技能冷却Cooldown是游戏开发中最常见的协程应用场景之一。通过协程我们可以优雅地实现技能使用后的等待时间而不阻塞主线程的执行。1.1 使用WaitForSeconds实现基础冷却最简单的技能冷却实现方式是使用WaitForSecondsIEnumerator SkillCooldown(float cooldownTime) { isSkillReady false; skillButton.interactable false; yield return new WaitForSeconds(cooldownTime); isSkillReady true; skillButton.interactable true; Debug.Log(技能已冷却完毕); }关键点说明WaitForSeconds会暂停协程执行指定的时间以秒为单位冷却期间可以更新UI显示剩余时间适用于大多数简单的技能冷却需求1.2 冷却时间显示优化为了提升玩家体验我们通常需要在UI上显示冷却剩余时间IEnumerator SkillCooldownWithUI(float cooldownTime) { isSkillReady false; skillButton.interactable false; float remainingTime cooldownTime; while (remainingTime 0) { cooldownText.text Mathf.Ceil(remainingTime).ToString(); remainingTime - Time.deltaTime; yield return null; // 每帧执行一次 } isSkillReady true; skillButton.interactable true; cooldownText.text 就绪; }注意使用yield return null可以让协程每帧执行一次适合需要频繁更新的逻辑。2. 关卡加载与进度显示关卡加载是另一个协程大显身手的场景特别是当我们需要显示加载进度时。2.1 基础场景加载实现IEnumerator LoadLevelAsync(string sceneName) { AsyncOperation asyncLoad SceneManager.LoadSceneAsync(sceneName); asyncLoad.allowSceneActivation false; while (!asyncLoad.isDone) { float progress Mathf.Clamp01(asyncLoad.progress / 0.9f); loadingSlider.value progress; loadingText.text $加载中... {progress * 100}%; if (progress 0.9f) { loadingText.text 按任意键继续; if (Input.anyKeyDown) { asyncLoad.allowSceneActivation true; } } yield return null; } }实现要点AsyncOperation.progress范围是0-0.9需要手动归一化到0-1通过allowSceneActivation控制场景切换时机每帧更新进度条和文本显示2.2 多资源并行加载优化对于需要加载大量资源的场景可以使用多个协程并行执行IEnumerator LoadMultipleResources() { ListCoroutine loadCoroutines new ListCoroutine(); // 启动多个资源加载协程 loadCoroutines.Add(StartCoroutine(LoadTextures())); loadCoroutines.Add(StartCoroutine(LoadModels())); loadCoroutines.Add(StartCoroutine(LoadAudio())); // 等待所有协程完成 foreach (var coroutine in loadCoroutines) { yield return coroutine; } Debug.Log(所有资源加载完成); }3. NPC行为树与状态切换协程非常适合实现NPC的复杂行为逻辑特别是需要按顺序执行多个动作的场景。3.1 基础巡逻行为实现IEnumerator NPCPatrolBehaviour() { while (true) { // 移动到下一个巡逻点 yield return StartCoroutine(MoveToPosition(patrolPoints[currentPatrolIndex])); // 在巡逻点停留一段时间 yield return new WaitForSeconds(waitTimeAtPoint); // 选择下一个巡逻点 currentPatrolIndex (currentPatrolIndex 1) % patrolPoints.Length; } } IEnumerator MoveToPosition(Vector3 target) { while (Vector3.Distance(transform.position, target) 0.1f) { transform.position Vector3.MoveTowards( transform.position, target, moveSpeed * Time.deltaTime ); yield return null; } }3.2 使用WaitUntil实现条件触发当NPC需要等待特定条件满足时才执行下一步时WaitUntil非常有用IEnumerator NPCReactionBehaviour() { Debug.Log(NPC正在观察玩家...); // 等待玩家进入警戒范围 yield return new WaitUntil(() IsPlayerInRange()); Debug.Log(发现玩家); yield return StartCoroutine(ChasePlayer()); // 等待玩家离开视线 yield return new WaitWhile(() CanSeePlayer()); Debug.Log(玩家已逃离返回巡逻); yield return StartCoroutine(ReturnToPatrol()); }4. 游戏流程控制与过场动画协程可以优雅地控制游戏流程特别是需要按顺序播放多个动画或事件的场景。4.1 过场动画序列控制IEnumerator PlayCutscene() { // 播放开场动画 yield return StartCoroutine(PlayOpeningAnimation()); // 等待玩家按下确认键 yield return new WaitUntil(() Input.GetButtonDown(Submit)); // 播放剧情对话 yield return StartCoroutine(PlayDialogueSequence()); // 等待2秒后进入游戏 yield return new WaitForSeconds(2f); StartGame(); }4.2 多阶段Boss战实现IEnumerator BossBattleSequence() { // 第一阶段 yield return StartCoroutine(BossPhaseOne()); // 转场动画 yield return StartCoroutine(PlayPhaseTransition()); // 第二阶段 yield return StartCoroutine(BossPhaseTwo()); // 等待Boss血量归零 yield return new WaitUntil(() bossHealth 0); // 播放死亡动画 yield return StartCoroutine(PlayBossDeath()); }5. UI动画与交互反馈协程在UI动画和交互反馈方面也有广泛应用可以实现平滑的过渡效果。5.1 渐入渐出效果IEnumerator FadeUI(CanvasGroup group, float targetAlpha, float duration) { float startAlpha group.alpha; float elapsed 0f; while (elapsed duration) { group.alpha Mathf.Lerp(startAlpha, targetAlpha, elapsed / duration); elapsed Time.deltaTime; yield return null; } group.alpha targetAlpha; }5.2 按钮点击效果序列IEnumerator ButtonPressEffect() { // 缩小动画 float scale 1f; while (scale 0.8f) { scale - Time.deltaTime * 2f; transform.localScale Vector3.one * scale; yield return null; } // 放大动画 while (scale 1f) { scale Time.deltaTime * 2f; transform.localScale Vector3.one * scale; yield return null; } transform.localScale Vector3.one; }6. 网络请求与数据加载协程可以很好地处理异步网络请求避免阻塞主线程。6.1 基础数据请求IEnumerator LoadPlayerData(string playerId) { string url $https://api.game.com/players/{playerId}; UnityWebRequest request UnityWebRequest.Get(url); yield return request.SendWebRequest(); if (request.result UnityWebRequest.Result.Success) { PlayerData data JsonUtility.FromJsonPlayerData(request.downloadHandler.text); UpdatePlayerUI(data); } else { Debug.LogError($加载失败: {request.error}); } }6.2 带超时的网络请求IEnumerator LoadWithTimeout(string url, float timeout) { UnityWebRequest request UnityWebRequest.Get(url); request.SendWebRequest(); float elapsed 0f; while (!request.isDone elapsed timeout) { elapsed Time.deltaTime; yield return null; } if (request.isDone) { // 处理成功响应 } else { request.Abort(); Debug.LogError(请求超时); } }7. 自定义Yield Instruction实现当内置的Yield Instruction不能满足需求时我们可以创建自定义实现。7.1 等待动画播放完成public class WaitForAnimation : IEnumerator { private Animator animator; private string stateName; private int layerIndex; public WaitForAnimation(Animator animator, string stateName, int layerIndex 0) { this.animator animator; this.stateName stateName; this.layerIndex layerIndex; } public object Current null; public bool MoveNext() { var stateInfo animator.GetCurrentAnimatorStateInfo(layerIndex); return animator.IsInTransition(layerIndex) || !stateInfo.IsName(stateName) || stateInfo.normalizedTime 1f; } public void Reset() {} } // 使用示例 IEnumerator PlayAnimationAndWait() { animator.Play(Attack); yield return new WaitForAnimation(animator, Attack); Debug.Log(动画播放完成); }7.2 等待物理碰撞发生public class WaitForCollision : IEnumerator { private bool collisionOccurred false; public WaitForCollision(GameObject target) { var listener target.AddComponentCollisionListener(); listener.OnCollision () collisionOccurred true; } public object Current null; public bool MoveNext() { return !collisionOccurred; } public void Reset() {} } // 使用示例 IEnumerator WaitForPlayerCollision() { yield return new WaitForCollision(playerObject); Debug.Log(玩家发生了碰撞); }在实际项目中我发现合理使用协程可以大幅简化异步逻辑的实现。特别是在处理复杂的游戏流程时协程能够将分散的逻辑组织成线性的代码结构提高可读性和可维护性。对于性能敏感的场景需要注意避免创建过多的协程实例可以考虑使用对象池技术来重用协程。

更多文章