Need help with a coroutine. been giving me fits, and I'm stuck.

I’m attempting to code a simple door script, however I’m having a problem with door functionality after the co-routine fires.

A few things:
When the co-routine fires I get a very nasty sound (rapidly repeating - like its starting the sound over again 60 times in one frame) out of the PlayOneShot, then immediately afterwards the sound plays normally.

After the co-routine fires the first time, closing the door, and I click the door to open it again immediately after it closes, the open animation fires momentarily, and then immediately closes the door, at which point the door is stuck in permanent open mode (even though its closed) and will not open any longer.

I’m just using a simple raycast click script attached to the players camera to fire the OpenDoor method.

Here is the door code for dissecting - any insight would be greatly appreciated!

using System.Collections;

public class DoorOpener : MonoBehaviour
{

    private GameObject player;

    public bool requireKey;
    public GameObject doorKey;

    public bool autoClose;

    public AudioClip doorOpenSound;
    public AudioClip doorCloseSound;

    private AudioSource source;
    private Animator doorAnimator;
    private float doorDistance;

    public bool doorOpen;

    // Use this for initialization
    void Start ()
    {
        doorAnimator = GetComponent<Animator> ();
        source = GetComponent<AudioSource>();
        player = GameObject.FindGameObjectWithTag ("Player");
    }

    void Update ()
    {
        if (doorOpen && autoClose)                 //is the doorOpen and is the bool autoClose checked (both true)? If so, fire the coroutine.
        {
            StartCoroutine ("CloseDoorTimer");
        }
    }

    public void OpenDoor()                        //we are checking to see if the player is in range of the door and if so, lets open/close it based on the doorOpen bool true/false
    {
        float doorDistance = Vector3.Distance (player.transform.position, transform.position);
        if(doorDistance < 4 && !source.isPlaying)
            {
            if (!doorOpen)
            {
                doorAnimator.SetTrigger ("Open");
                source.PlayOneShot (doorOpenSound, 1.0f);
                doorOpen = true;
            }
            else if (doorOpen)
            {
                doorAnimator.SetTrigger ("Close");
                source.PlayOneShot(doorCloseSound, 1.0f);
                doorOpen = false;
            }
        }
    }

    IEnumerator CloseDoorTimer()                //Wait 8 seconds, close the door, play a sound and set the doorOpen bool to false
    {
        yield return new WaitForSeconds(8);
        doorAnimator.SetTrigger ("Close");
        source.PlayOneShot (doorCloseSound, 1.0f);
        doorOpen = false;
    }
}

Here is my clicker script - as it may or may not be relevant:

using UnityEngine;
using System.Collections;

public class PlayerClickAction : MonoBehaviour {

   
    // Update is called once per frame
    void Update () {
        if (Input.GetMouseButtonDown (0))
        {
            Ray toMouse = Camera.main.ScreenPointToRay (Input.mousePosition);
            RaycastHit rhInfo;
            bool didHit = Physics.Raycast (toMouse, out rhInfo, 100.0f);

            if (didHit) {
                Debug.Log (rhInfo.collider.name + "..." + rhInfo.point);
                //start looking for scripts on objects and calling functions within them
                DoorOpener doorOpenScript = rhInfo.collider.GetComponent<DoorOpener> ();
                if (doorOpenScript)
                {
                    doorOpenScript.OpenDoor ();
                }
            }
            else
            {
                Debug.Log ("This object has no script attached...");
            }
        }
    }
}

In Update (every frame), you’re starting the CloseDoorTimer coroutine as long as doorOpen is true, and then doorOpen doesn’t get set to false until 8 seconds later, by which time you’ve started it a few hundred times. Hence the many sounds overlapping.

You could either set the flag in Update itself, or (if OpenDoor is the only way that the door gets opened) ditched Update and just call CloseDoorTimer from OpenDoor.

1 Like

Thanks Errosatz. I’d love to be able to call the co-routine in the OpenDoor method, but the problem I’m having is the distance check of the player in relation to the door. I’d like to get the door to auto-close nomatter what distance the player is at after he/she opens it. But in order to manually close/open the thing, they need to be right next to it (which I have licked) I can’t seem to get my head around the “if, if, else if, else” statement in that method… I’m still very much a beginner, and the linear logic in those statements sometimes gives me huge problems.

I’m still plugging away trying to get it to work, but I’m getting frustrated :frowning:

How about calling the close door timer coroutine after doorAnimator.SetTrigger(“Open”);

Image, I’ve been there and done that but I’m getting stuck with that first distance bool check.

So out of curiosity, after trying the suggestions here (and way more) unsuccessfully, how would you folks embed the co-routine here into the OpenDoor method while maintaining the main functionality of both the distance from the door, and the autoClose functionality (the co-routine) which does not need a distance check? Hours have gone by with me jacking with this thing and I’m missing something simple I’m sure… :frowning:

You can always manage the door as a FSM. A door would have four states, opening, open, closing and closed.

Psuedo code:

void Start () {
    StartCoroutine (DoorHandler());
}

IEnumerator DoorHandler (){
    while (true){
        while (distanceToPlayer > thresholdDistance) yield return null;
        OpenDoor ();
        yield return new WaitForSeconds (doorOpeningTime);
        while (distanceToPlayer < thresholdDistance) yield return null;
        CloseDoor();
        yield return new WaitForSeconds (doorClosingTime);
    }
}

This code is not optimised, the door will check for player distance every frame. If you use a lot of doors then a trigger based approach may be more appropriate.

PsuedoCode

bool shouldDoorBeOpen = false;
bool doorRunning = false;

void OnTriggerEnter () {
    shouldDoorBeOpen = true; 
    if(!doorRunning){
        StartCoroutine (DoorHandler());
    }
}

void OnTriggerExit (){
    shouldDoorBeOpen = false;
}

IEnumerator DoorHandler () {
    doorRunning = true;
    OpenDoor ();
    yield return new WaitForSeconds (doorOpeningTime);
    while (shouldDoorBeOpen) yield return null;
    CloseDoor();
    yield return new WaitForSeconds (doorClosingTime);
    doorRunning = false;
}

Its not perfect, there are a few edge cases that will misbehave. But its a move in the right direction

BoredMormon I have only 27 doors in the level. Are you saying that what I’ve cobbled together will not work with the co-routine I’ve written inserted into the Update or OpenDoor methods and I should start from scratch? I’m not using triggers either, I’m using another script to call the OpenDoor method with a raycast mouse click. The only reason I’m even running the co-routine is for the timer function to automatically close the door where ever the player is in the scene (no distance check), and that’s absolutely it.

Your code will work, you just need to add a bool to check if the coroutine is already running, and not start it if it is. Essentially what I did in the second example.

So I’m almost there. I have a 100 millisecond potential problem though. If I click the door to close it, right before the last statement fires(within that one second), I can potentially break the co-routine and send the door into a permanent open state. Clicking the door after that happens results in it immediately opening and closing, and breaking the OpenDoor() method so it no longer functions. I’ve tried a variety of while statements, but after a few hours I gave up (frustrated).

Here is my mess if anyone is interested.

public class DoorOpener : MonoBehaviour
{

    private GameObject player;

    public bool requireKey;
    public GameObject doorKey;

    public bool autoClose;

    public AudioClip doorOpenSound;
    public AudioClip doorCloseSound;

    private AudioSource source;
    private Animator doorAnimator;
    private float doorDistance;

    public bool doorOpen;

    // Use this for initialization
    void Start ()
    {
        doorAnimator = GetComponent<Animator> ();
        source = GetComponent<AudioSource>();
        player = GameObject.FindGameObjectWithTag ("Player");
    }

    void Update ()
    {
      
    }

    public void OpenDoor()                        //we are checking to see if the player is in range of the door and if so, lets open/close it based on the doorOpen bool true/false
    {
        float doorDistance = Vector3.Distance (player.transform.position, transform.position);
        if(doorDistance < 4 && !source.isPlaying)
            {
            if (!doorOpen) {
                doorAnimator.SetTrigger ("Open");
                source.PlayOneShot (doorOpenSound, 1.0f);
                doorOpen = true;
                if (autoClose && doorOpen)
                {
                    StartCoroutine (CloseDoorTimer ());
                }
            }

            else if (doorOpen)
            {
                doorAnimator.SetTrigger ("Close");
                source.PlayOneShot (doorCloseSound, 1.0f);
                doorOpen = false;
            }
        }
    }

    IEnumerator CloseDoorTimer()                //Wait 8 seconds, close the door, play a sound and set the doorOpen bool to false
    {
        if (!doorOpen) {
            yield break;
        }
        else if (doorOpen) {yield return new WaitForSeconds (3);}
        if (!doorOpen) {
            yield break;
        }
        else if (doorOpen) {yield return new WaitForSeconds (3);}
        if (!doorOpen) {
            yield break;
        }
        else if (doorOpen) {yield return new WaitForSeconds (1);}
        if (!doorOpen) {
            yield break;
        } else if (doorOpen) {
            doorAnimator.SetTrigger ("Close");
            source.PlayOneShot (doorCloseSound, 1.0f);
            doorOpen = false;
            yield break;
        }
    }
}