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

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秒内移开视线,协程仍会执行并错误显示文本
  • 快速切换注视目标时,旧协程未终止,可能显示前一个目标的文本

解决方案

要解决这两个问题,需要实现协程的中途终止机制,并在等待过程中持续验证注视状态:

  1. 添加字段保存当前运行的文本协程引用,用于后续终止操作
  2. 在检测到新目标或失去目标时,先停止旧协程再处理新逻辑
  3. 修改协程逻辑,逐帧检查是否仍在注视目标,若目标切换则直接终止协程
  4. 调整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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 06:40:29