Unity中如何在玩家不再注视目标时立即终止文本Coroutine?
问题与解决方案
问题场景与现状
玩家注视有效目标时,UI文本会延迟1秒后显示。当前逻辑是在OnAnimatorIK方法中检测到注视目标时,启动WaitBeforeShowingText协程,等待1秒后展示目标描述文本。
核心逻辑代码片段:
// OnAnimatorIK中启动协程的部分 if (lookWeight > 0.1f && lookWeight > closestLookWeight) { closestLookWeight = lookWeight; primaryTarget = target; if (showText && primaryTarget.description != "") { StartCoroutine(WaitBeforeShowingText(primaryTarget)); showText = false; } } // 协程实现 IEnumerator WaitBeforeShowingText(InteractableItem primaryTarget) { yield return new WaitForSeconds(1f); descriptionTextImage.SetActive(true); text.text = primaryTarget.description; }
存在的问题
- 协程启动后,若玩家在1秒内移开视线,协程仍会执行并错误显示文本
- 快速切换注视目标时,旧协程未终止,可能显示前一个目标的文本
解决方案
要解决这两个问题,需要实现协程的中途终止机制,并在等待过程中持续验证注视状态:
- 添加字段保存当前运行的文本协程引用,用于后续终止操作
- 在检测到新目标或失去目标时,先停止旧协程再处理新逻辑
- 修改协程逻辑,逐帧检查是否仍在注视目标,若目标切换则直接终止协程
- 调整
showText变量的控制逻辑,避免重复启动协程
修改后的完整代码
using UnityEngine; using System; using System.Collections; using UnityEngine.UI; using System.Collections.Generic; using System.Linq; using TMPro; [RequireComponent(typeof(Animator))] public class IKControl : MonoBehaviour { public List<InteractableItem> lookObjs = new List<InteractableItem>(); public TextMeshProUGUI text; public float weightDamping = 1.5f; public bool RightHandToTarget = false; public GameObject descriptionTextImage; public float duration; // 新增:保存当前运行的文本协程引用 private Coroutine activeTextCoroutine; private List<InteractableItem> allDetectedItems; private Animator animator; private InteractableItem lastPrimaryTarget; private float lerpEndDistance = 0.1f; private float finalLookWeight = 0; private bool transitionToNextTarget = false; private float t; private bool showText = true; private GameObject playerEyes; void Start() { playerEyes = GameObject.Find("rig_head");//"rig_eye.L"); animator = GetComponent<Animator>(); allDetectedItems = new List<InteractableItem>(); t = 0; } // Callback for calculating IK void OnAnimatorIK() { if (lookObjs != null) { lookObjs.RemoveAll(x => x == null); InteractableItem primaryTarget = null; float closestLookWeight = 0; // 找到离玩家视线角度最近的目标 allDetectedItems.Clear(); foreach (InteractableItem target in lookObjs) { if (target.enabledInteraction == false) { continue; } Vector3 lookAt = target.transform.position - transform.position; lookAt.y = 0f; // 过滤过远的目标 if (lookAt.magnitude > target.distance) continue; RaycastHit hit; if (Physics.Raycast(playerEyes.transform.position, target.transform.position - playerEyes.transform.position, out hit, target.distance, ~LayerMask.GetMask("kid_from_space"))) { if (hit.collider.gameObject == target.gameObject) { float dotProduct = Vector3.Dot(new Vector3(transform.forward.x, 0f, transform.forward.z).normalized, lookAt.normalized); float lookWeight = Mathf.Clamp(dotProduct, 0f, 1f); if (lookWeight > 0.1f && lookWeight > closestLookWeight) { closestLookWeight = lookWeight; primaryTarget = target; } allDetectedItems.Add(target); } else { // 视线被遮挡:停止协程并清空文本 if (activeTextCoroutine != null) { StopCoroutine(activeTextCoroutine); activeTextCoroutine = null; } showText = true; text.text = ""; descriptionTextImage.SetActive(false); } } } // 单独处理文本协程的启动与停止 HandleTextCoroutine(primaryTarget); InteractWithTarget(primaryTarget, closestLookWeight); } } // 新增:处理文本协程逻辑 private void HandleTextCoroutine(InteractableItem primaryTarget) { // 有新的主目标 if (primaryTarget != null && primaryTarget.description != "") { // 如果当前有运行的协程且目标不同,先停止旧协程 if (activeTextCoroutine != null) { StopCoroutine(activeTextCoroutine); activeTextCoroutine = null; } // 启动新协程 if (showText) { activeTextCoroutine = StartCoroutine(WaitBeforeShowingText(primaryTarget)); showText = false; } } else { // 没有主目标:停止协程并清空文本 if (activeTextCoroutine != null) { StopCoroutine(activeTextCoroutine); activeTextCoroutine = null; } showText = true; text.text = ""; descriptionTextImage.SetActive(false); } } private void InteractWithTarget(InteractableItem primaryTarget, float closestLookWeight) { if (primaryTarget != null) { if ((lastPrimaryTarget != null) && (lastPrimaryTarget != primaryTarget) && (finalLookWeight > 0f)) { transitionToNextTarget = true; } } // 玩家处于中立状态,找到新目标 if ((primaryTarget != null) && !transitionToNextTarget) { if (primaryTarget.IsAnyAction()) { RightHandToTarget = true; } lastPrimaryTarget = primaryTarget; finalLookWeight = Mathf.Lerp(finalLookWeight, 1f, Time.deltaTime * weightDamping); float bodyWeight = finalLookWeight * .1f; animator.SetLookAtWeight(finalLookWeight, bodyWeight, 1f); animator.SetLookAtPosition(primaryTarget.transform.position); if (RightHandToTarget && primaryTarget.IsAnyAction()) { Vector3 relativePos = primaryTarget.transform.position - transform.position; Quaternion rotationtoTarget = Quaternion.LookRotation(relativePos, Vector3.up); if (primaryTarget.interactableMode == InteractableItem.InteractableMode.ActionWithoutThrow) { animator.SetIKRotationWeight(AvatarIKGoal.RightHand, finalLookWeight); animator.SetIKRotation(AvatarIKGoal.RightHand, rotationtoTarget); animator.SetIKPositionWeight(AvatarIKGoal.RightHand, finalLookWeight * 1f * closestLookWeight); animator.SetIKPosition(AvatarIKGoal.RightHand, primaryTarget.transform.position); } if (primaryTarget.interactableMode == InteractableItem.InteractableMode.Action) { animator.SetIKRotationWeight(AvatarIKGoal.RightHand, finalLookWeight); animator.SetIKRotation(AvatarIKGoal.RightHand, rotationtoTarget); animator.SetIKPositionWeight(AvatarIKGoal.RightHand, finalLookWeight * 0.1f * closestLookWeight); animator.SetIKPosition(AvatarIKGoal.RightHand, primaryTarget.transform.position); } } } // 玩家从目标平滑切换回中立状态 if ((primaryTarget == null && lastPrimaryTarget != null) || transitionToNextTarget) { finalLookWeight = Mathf.Lerp(finalLookWeight, 0f, t / duration); t += Time.deltaTime; float bodyWeight = finalLookWeight * .1f; animator.SetLookAtWeight(finalLookWeight, bodyWeight, 1f); animator.SetLookAtPosition(lastPrimaryTarget.transform.position); if (RightHandToTarget) { Vector3 relativePos = lastPrimaryTarget.transform.position - transform.position; Quaternion rotationtoTarget = Quaternion.LookRotation(relativePos, Vector3.up); animator.SetIKRotationWeight(AvatarIKGoal.RightHand, finalLookWeight); animator.SetIKRotation(AvatarIKGoal.RightHand, rotationtoTarget); animator.SetIKPositionWeight(AvatarIKGoal.RightHand, finalLookWeight * 0.5f * closestLookWeight); animator.SetIKPosition(AvatarIKGoal.RightHand, lastPrimaryTarget.transform.position); } if (finalLookWeight < lerpEndDistance) { showText = true; text.text = ""; descriptionTextImage.SetActive(false); transitionToNextTarget = false; finalLookWeight = 0f; lastPrimaryTarget = null; transform.rotation = Quaternion.Euler(0, transform.eulerAngles.y, 0); } } } // 修改后的协程:逐帧检查目标状态 IEnumerator WaitBeforeShowingText(InteractableItem targetToShow) { float waitTime = 1f; float elapsedTime = 0f; while (elapsedTime < waitTime) { elapsedTime += Time.deltaTime; // 每帧检查:如果当前主目标不是要显示的目标,终止协程 if (lastPrimaryTarget != targetToShow) { showText = true; yield break; } yield return new WaitForEndOfFrame(); } // 等待结束后再次确认目标状态 if (lastPrimaryTarget == targetToShow) { descriptionTextImage.SetActive(true); text.text = targetToShow.description; } else { showText = true; } activeTextCoroutine = null; } }
内容的提问来源于stack exchange,提问作者Daniel Lip
相关产品推荐
相关产品推荐

