Home > Software design >  How can i stop the coroutine immediately in the middle once the player is not looking at any target
How can i stop the coroutine immediately in the middle once the player is not looking at any target

Time:12-24

When the player is looking at a target the ui text is enabled true and it's showing some text.

void OnAnimatorIK()
    {
        if (lookObjs != null)
        {
            lookObjs.RemoveAll(x => x == null);

            InteractableItem primaryTarget = null;

            float closestLookWeight = 0;

            // Here we find the target which is closest (by angle) to the players view line
            allDetectedItems.Clear();
            foreach (InteractableItem target in lookObjs)
            {
                if (target.enabledInteraction == false)
                {
                    continue;
                }

                Vector3 lookAt = target.transform.position - transform.position;
                lookAt.y = 0f;

                // Filter out all objects that are too far away
                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;
                            if (showText && primaryTarget.description != "")
                            {
                                StartCoroutine(WaitBeforeShowingText(primaryTarget));
                                showText = false;
                            }
                        }
                        
                        allDetectedItems.Add(target);
                    }
                    else
                    {
                        showText = true;
                        text.text = "";
                        descriptionTextImage.SetActive(false);
                    }
                }
            }

            InteractWithTarget(primaryTarget, closestLookWeight);
        }
    }

This line start the coroutine that showing the text :

StartCoroutine(WaitBeforeShowingText(primaryTarget));

and inside the coroutine

IEnumerator WaitBeforeShowingText(InteractableItem primaryTarget)
    {
        yield return new WaitForSeconds(1f);

        descriptionTextImage.SetActive(true);
        text.text = primaryTarget.description;
    }

i did it because i wanted to give some delay before showing the text when the player is looking at a target.

the problem is if the coroutine started but before even showing the text i'm rotating the player to NOT looking at any target but because the coroutine started already it will show the text even if the player is not looking anymore at any target.

so the coroutine must be stopped somehow in the middle and show no text.

another possible problem is what if there are two close targets and i move/rotate the player so the target he is looking at will change too fast for the coroutine ?

This is the full code :

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 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;

            // Here we find the target which is closest (by angle) to the players view line
            allDetectedItems.Clear();
            foreach (InteractableItem target in lookObjs)
            {
                if (target.enabledInteraction == false)
                {
                    continue;
                }

                Vector3 lookAt = target.transform.position - transform.position;
                lookAt.y = 0f;

                // Filter out all objects that are too far away
                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) 
                    {
                        // First object hit was the target so there is a clear line of sight

                        //Debug.DrawRay(playerEyes.transform.position, Vector3.forward, Color.red);

                        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;
                            if (showText && primaryTarget.description != "")
                            {
                                StartCoroutine(WaitBeforeShowingText(primaryTarget));
                                showText = false;
                            }
                        }

                        allDetectedItems.Add(target);
                    }
                    else
                    {
                        showText = true;
                        text.text = "";
                        descriptionTextImage.SetActive(false);
                    }
                }
            }

            InteractWithTarget(primaryTarget, closestLookWeight);
        }
    }

    private void InteractWithTarget(InteractableItem primaryTarget, float closestLookWeight)
    {
        if (primaryTarget != null)
        {
            if ((lastPrimaryTarget != null) && (lastPrimaryTarget != primaryTarget) && (finalLookWeight > 0f))
            {
                // Here we start a new transition because the player looks already to a target but
                // we have found another target the player should look at
                transitionToNextTarget = true;
            }
        }

        // The player is in a neutral look position but has found a new target
        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);
                }
            }
        }

        // Let the player smoothly look away from the last target to the neutral look position
        if ((primaryTarget == null && lastPrimaryTarget != null) || transitionToNextTarget)
        {
            finalLookWeight = Mathf.Lerp(finalLookWeight, 0f, t / duration);//Time.deltaTime * weightDamping);
            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 primaryTarget)
    {
        yield return new WaitForSeconds(1f);

        descriptionTextImage.SetActive(true);
        text.text = primaryTarget.description;
    }
}

CodePudding user response:

To stop a Coroutine, call StopCoroutine(WaitBeforeShowingText);

Here is the doc : https://docs.unity3d.com/ScriptReference/MonoBehaviour.StopCoroutine.html

  • Related