Creating a Reaction time based game - should iterate through 10 loops but doesn't

So essentially for a project I’m working on a series of reaction time tests/games. Pretty basic with just a blinking dot or blinking audio cue and I tried to set this up so that I could reuse this script for both audio and visual depending on what I set it up for.

Currently the console displays no errors however once the code gets into AVtester() method it goes through what seems like 1 iteration, prints only the first position of the time array each time spitting out 0.02 seconds. Why is this happening and how can I fix it? Also I’m using WaitForSeconds() to have a duration to display the dot for .1 seconds

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class ReactionTestAVController : MonoBehaviour
{
    public bool avswitch; //true for audio, false for visual
    public GameObject cue;
    public GameObject start;

    private float timer;
    private float[] time = new float[10];

    
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            start.SetActive(false);
            StartCoroutine(visDelay());
            AVtester();
        }
    }

    
    public void AVtester()
    {
        cue.SetActive(true);

        for (int x = 0; x < 10; x++)
        {
            bool input = true;
            timer = 1500;

            if (avswitch) // cue for audio
            {
                AudioSource audioCue = cue.GetComponent<AudioSource>(); // get audio component
                audioCue.Play();
            }
            else if (!avswitch) // cue for visual 
            {
                RawImage imageCue = cue.GetComponent<RawImage>(); // get image component
                imageCue.enabled = true;
                StartCoroutine(visDelay());
            }

            while (timer > 0 && input) // wait for response
            {
                if (Input.GetKeyDown(KeyCode.Space))
                    input = false;

                timer -= Time.fixedDeltaTime;
            }

            time[x] = 1500 - timer;
        }

        for (int x = 0; x < 10; x++)
            print(time[x]);

        SceneManager.LoadScene(2, LoadSceneMode.Single);
    }

    IEnumerator visDelay()
    {
        yield return new WaitForSeconds(.1f);
    }
}

Hey @ktaswell

I think you might have misunderstood the concept of coroutines and WaitForSeconds a little.
Everytime you call visDelay with StartCoroutine() it will only wait .1 seconds within this method (internally). It will run independently from AVtester() or Update(). Thus you won’t notice the waiting in the AVtester() method.

Another thing is, that Input.GetKeyDown returns true during the frame it was pressed. You can read about it here: Input.GetKeyDown

That means, after you pressed the space bar the AVtester() method will be called (still in the same frame) and everything else in the method (except the code of the coroutine) will be executed in the same frame. Thus it is still true in the while loop, setting input to false, leaving it in the first frame.

while (timer > 0 && input) // wait for response
{
        if (Input.GetKeyDown(KeyCode.Space)) // STILL TRUE
                input = false;
 
        timer -= Time.fixedDeltaTime;
}

EDIT:

Here is a small untested code example of how it might work and is more clearly arranged. Best thing to do is probably to paste it into MonoDevelop and have a calm look at it :slight_smile:

using UnityEngine;

public class CueController : MonoBehaviour {

	public const float MAX_INPUT_DELAY = 1.5F; // in seconds

	public GameObject Cue;
	public GameObject StartCue;
	public bool AudioVisualSwitch;

	// counts how much cues have been shown.
	private int cueCounter;

	private float startingTimeOfLastCue;
	// The time delays between a cue was shown and the spacebar was hit by the player.
	private float[] timeDelays;

	private bool isRunning;
	
	// Update is called once per frame
	void Update () {
		// Check whether game round is supposed to start.
		if (Input.GetKeyDown(KeyCode.I))
		{
			InitGame();
		}
			
		if (isRunning) {
			// Is the player still in time?
			if (Time.time < startingTimeOfLastCue + MAX_INPUT_DELAY) {
				// Did he also pressed space in time?
				if (Input.GetKeyDown(KeyCode.Space)) {
					// Save the delay for cueCounter-th element.
					timeDelays[cueCounter] = Time.time - startingTimeOfLastCue;
					InitNextCue(AudioVisualSwitch);
				}
			} else {
				timeDelays[cueCounter] = MAX_INPUT_DELAY;
				InitNextCue(AudioVisualSwitch);
			}
		}
	}

	/// <summary>
	/// Set everything up for the game to start.
	/// Pauses the game when it already has started.
	/// </summary>
	private void InitGame() {
		if (!isRunning) {
			StartCue.SetActive(false);
			isRunning = true;

			// TODO: Immediately shows the first cue after pressing i, might be nice to have a little starting countdown.
			InitNextCue(AudioVisualSwitch);
		} else {
			// You could pause the game here for example.
		}
	}

	/// <summary>
	/// Inits the next cue and sets the time to the beginning of the frame when it was shown.
	/// </summary>
	/// <param name="avSwitch">If set to <c>true</c> an audio cue will played, a visual cue will be displayed otherwise.</param>
	private void InitNextCue(bool avSwitch) {
		if (avSwitch) {
			// Play some audio cue.
			AudioSource audioCue = Cue.GetComponent<AudioSource>(); // get audio component
			if (audioCue != null) audioCue.Play();
		} else {
			// Display some visual cue.
			Cue.SetActive(true);
		}

		startingTimeOfLastCue = Time.time;
		cueCounter++;
	}
}