Search Unity

Adding delay to my Inputs (for keyboard and button gui inputs)

Discussion in 'Scripting' started by xslipstream, May 25, 2017.

  1. xslipstream

    xslipstream

    Joined:
    Feb 3, 2017
    Posts:
    17
    I'm working on a snake game and I'm running into a problem where the user may erroneously change the snake's direction too quickly causing the snake to run into itself on the next frame. I'm running an InvokeRepeating function (set to an interval) to "move" my snake. I'm creating a new cube and setting it as head in whichever direction the user wants to move the Snake. I have 2 forms of inputs: 1) Update() to listen for keyboard inputs (up, down, left, right arrows) and 2) a "gamepad" formed using buttons UI.

    Though I try to prevent the user from moving backwards (i.e. my direction input functions prevent user from moving South if the Snake is moving North), the user may change directions faster the Snake moves. When this happens, the game reads my inputs before the frame is completed which could cause the snake to instantiate a new cube inside the snake's body thus running into itself.

    Here are my codes related to movement and inputs:
    Code (CSharp):
    1.    
    2.     void Start () {
    3.         Screen.orientation = ScreenOrientation.Portrait;
    4.         isPaused = false;
    5.         InvokeRepeating("TimerInvoke", 0, deltaTimer);
    6.     }
    7.  
    8.     void TimerInvoke() {
    9.     Movement();
    10.     StartCoroutine(checkVisible());
    11.     if (currentSize == maxSize)
    12.         TailFunction();
    13.     else
    14.         currentSize++;
    15.     }
    16.  
    17.     void Update () {
    18.          CompDirectionControl();
    19.          PauseCMD();
    20.     }
    21.  
    22.     void CompDirectionControl() {
    23.     if (direction != Direction.South && Input.GetKeyDown(KeyCode.UpArrow))
    24.         direction = Direction.North;
    25.     if (direction != Direction.West && Input.GetKeyDown(KeyCode.RightArrow))
    26.         direction = Direction.East;
    27.     if (direction != Direction.North && Input.GetKeyDown(KeyCode.DownArrow))
    28.         direction = Direction.South;
    29.     if (direction != Direction.East && Input.GetKeyDown(KeyCode.LeftArrow))
    30.         direction = Direction.West;
    31.     }
    32.  
    33.     public void  MPadDirectionControl(int numDirection)
    34.     {
    35.     if (direction != Direction.South && numDirection == 0)
    36.         direction = Direction.North;
    37.     if (direction != Direction.West && numDirection == 1)
    38.         direction = Direction.East;
    39.     if (direction != Direction.North && numDirection == 2)
    40.         direction = Direction.South;
    41.     if (direction != Direction.East && numDirection == 3)
    42.         direction = Direction.West;
    43.     }
    Here is an attempt to create a delay. I had a feeling this wasn't going to work properly
    Code (CSharp):
    1. if (Time.time >= timeStamp)
    2.     {
    3.         CompDirectionControl();
    4.         timeStamp = Time.time + moveInterval;
    5.     }

    I tried to create a timer under Update() to regulate my movements but I find my inputs to be inconsistent in their capture. Sometimes my inputs are responsive, other times my inputs take way longer than I set the delay to be.

    Any suggestions to how I could regulate my directional changes?
     
  2. Deleted User

    Deleted User

    Guest

    Have you tried making your control methods into coroutines? You can use yield return new WaitForSeconds(float) and nested coroutines to induce that delay.
     
    Last edited by a moderator: May 25, 2017
    xslipstream likes this.
  3. xslipstream

    xslipstream

    Joined:
    Feb 3, 2017
    Posts:
    17
    Using a coroutine seems to work with my keyboard inputs, thank you for the suggestion.

    However, I'm not sure how to do the same with my buttons UI inputs.

    Updated code for Keyboard input:
    Code (CSharp):
    1.     void Update () {
    2.         StartCoroutine(DirControl());
    3.         //CompDirectionControl();
    4.         PauseCMD();
    5.     }
    6.  
    7.     IEnumerator DirControl() {
    8.         if (direction != Direction.South && Input.GetKeyDown(KeyCode.UpArrow))
    9.         {
    10.             yield return new WaitForSeconds(deltaTimer);
    11.             direction = Direction.North;
    12.         }
    13.         else if (direction != Direction.West && Input.GetKeyDown(KeyCode.RightArrow))
    14.         {
    15.             yield return new WaitForSeconds(deltaTimer);
    16.             direction = Direction.East;
    17.         }
    18.         else if (direction != Direction.North && Input.GetKeyDown(KeyCode.DownArrow))
    19.         {
    20.             yield return new WaitForSeconds(deltaTimer);
    21.             direction = Direction.South;
    22.         }
    23.         else if (direction != Direction.East && Input.GetKeyDown(KeyCode.LeftArrow))
    24.         {
    25.             yield return new WaitForSeconds(deltaTimer);
    26.             direction = Direction.West;
    27.         }
    28.     }
    My buttons are set to OnClick to pass an int (0 = North, 1 = East, 2 = South, 3 = West) to my MPadDirection(int numDirection) function. I tried to create a public IEnumerator GpadDirControl(int numDirection) but it doesn't show up in my inspector. How do I pass my button's parameter to the function?
     
    Last edited: May 25, 2017
  4. Deleted User

    Deleted User

    Guest

    I may suggest that you don't do StartCoroutine(DirControl()); in your Update. You're making a new coroutine every frame and while I'm still amateurish at coding, I can only imagine the implication of all those coroutines. Perhaps something the lines of:

    Code (CSharp):
    1.    
    2.  
    3. void Start()
    4. {
    5. StartCoroutine(DirControl());
    6. }
    7.  
    8. IEnumerator DirControl() {
    9. while(true)
    10. {
    11.         if (direction != Direction.South && Input.GetKeyDown(KeyCode.UpArrow))
    12.         {
    13.             yield return new WaitForSeconds(deltaTimer);
    14.             direction = Direction.North;
    15.         }
    16.         else if (direction != Direction.West && Input.GetKeyDown(KeyCode.RightArrow))
    17.         {
    18.             yield return new WaitForSeconds(deltaTimer);
    19.             direction = Direction.East;
    20.         }
    21.         else if (direction != Direction.North && Input.GetKeyDown(KeyCode.DownArrow))
    22.         {
    23.             yield return new WaitForSeconds(deltaTimer);
    24.             direction = Direction.South;
    25.         }
    26.         else if (direction != Direction.East && Input.GetKeyDown(KeyCode.LeftArrow))
    27.         {
    28.             yield return new WaitForSeconds(deltaTimer);
    29.             direction = Direction.West;
    30.         }
    31. yield return null;
    32. }
    33. }
    As for your MouseClick controls, you're talking about Unity's canvas buttons, right? Instead of making a public IEnumerator, you can try something like this:
    Code (CSharp):
    1. public void TurnNorth()
    2. {
    3. StartCoroutine(MPadDirection(0));
    4. }
    Your button will find the method and run it when clicked.
     
  5. xslipstream

    xslipstream

    Joined:
    Feb 3, 2017
    Posts:
    17
    What's the implication if I set my coroutine under update? Is it because it would be an inefficient use of memory?

    For the buttons UI, I created individual coroutines for each direction I then call on them from my original MPadDirectionControl(int numDirection) function. This seems to have solved my problem. I will test out your suggestions too, thanks again

    Updated code for button ui inputs
    Code (CSharp):
    1.     public void MPadDirectionControl(int numDirection)
    2.     {
    3.         if (direction != Direction.South && numDirection == 0)
    4.         {
    5.             StartCoroutine(IntervalN());
    6.             //direction = Direction.North;
    7.         }
    8.         else if (direction != Direction.West && numDirection == 1)
    9.         {
    10.             StartCoroutine(IntervalE());
    11.             //direction = Direction.East;
    12.         }
    13.         else if (direction != Direction.North && numDirection == 2)
    14.         {
    15.             StartCoroutine(IntervalS());
    16.             //direction = Direction.South;
    17.         }
    18.         else if (direction != Direction.East && numDirection == 3)
    19.         {
    20.             StartCoroutine(IntervalW());
    21.             //direction = Direction.West;
    22.         }
    23.     }
    24.  
    25.     IEnumerator IntervalN() {
    26.         yield return new WaitForSeconds(deltaTimer);
    27.         direction = Direction.North;
    28.     }
    29.  
    30.     IEnumerator IntervalE()
    31.     {
    32.         yield return new WaitForSeconds(deltaTimer);
    33.         direction = Direction.East;
    34.     }
    35.  
    36.     IEnumerator IntervalS()
    37.     {
    38.         yield return new WaitForSeconds(deltaTimer);
    39.         direction = Direction.South;
    40.     }
    41.  
    42.     IEnumerator IntervalW()
    43.     {
    44.         yield return new WaitForSeconds(deltaTimer);
    45.         direction = Direction.West;
    46.     }
     
  6. xslipstream

    xslipstream

    Joined:
    Feb 3, 2017
    Posts:
    17
    hey @Dyxanh - putting my dir control function under start() doesn't work as my game crashes before it runs. I'm not sure why the game crashes so if anyone knows please share the knowledge. I do believe my keyboard input function has to be under Update() so that the game is constantly listening for keyboard inputs
     
  7. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    it would mean that every frame you would start another Coroutine, not overwrite the old one. Memory is not the primary concern (though you evenutally would run out of memory) its the fact that you would be trying to run thousands of coroutines after just a minute of play.

    "Inefficient use of Memory" isn't the correct term. "Crash-prone" is.

    Show us the code you are using now.

    You need to make a decision. Update or Coroutine. either can be used to listen for Input but you typically don't want to use both at the same time.
     
  8. xslipstream

    xslipstream

    Joined:
    Feb 3, 2017
    Posts:
    17
    Hi Josh, this is what I'm using now. Any suggestion on how I may optimize this?

    Code (CSharp):
    1.     void Update () {
    2.         StartCoroutine(DirControl());
    3.         //CompDirectionControl();
    4.         PauseCMD();
    5.     }
    6.     IEnumerator DirControl() {
    7.         if (direction != Direction.South && Input.GetKeyDown(KeyCode.UpArrow))
    8.         {
    9.             yield return new WaitForSeconds(deltaTimer);
    10.             direction = Direction.North;
    11.         }
    12.         else if (direction != Direction.West && Input.GetKeyDown(KeyCode.RightArrow))
    13.         {
    14.             yield return new WaitForSeconds(deltaTimer);
    15.             direction = Direction.East;
    16.         }
    17.         else if (direction != Direction.North && Input.GetKeyDown(KeyCode.DownArrow))
    18.         {
    19.             yield return new WaitForSeconds(deltaTimer);
    20.             direction = Direction.South;
    21.         }
    22.         else if (direction != Direction.East && Input.GetKeyDown(KeyCode.LeftArrow))
    23.         {
    24.             yield return new WaitForSeconds(deltaTimer);
    25.             direction = Direction.West;
    26.         }
    27.     }
     
  9. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    Well personally I would just read the input every frame and save the most recent press. then when the time comes to actually move to the next cell you then use the most recent decision.

    your original code was close you just needed to store a currentDirection and a nextDirection.

    Code (CSharp):
    1.  
    2. private Direction currentDirection;
    3. private Direction nextDirection;
    4.  
    5. private void TimerInvoke()
    6. {
    7.     currentDirection = nextDirection;
    8.     Movement();
    9.    // blah blah the rest of your code
    10. }
    11. private void Update()
    12. {
    13.     ReadNextDirection();
    14. }
    15.  
    16. private void ReadNextDirection()
    17. {
    18.    bool pUp    = currentDirection != Direction.South && Input.GetKeyDown(KeyCode.UpArrow);
    19.    bool pDown  = currentDirection != Direction.North && Input.GetKeyDown(KeyCode.DownArrow);
    20.    bool pLeft    = currentDirection != Direction.East  && Input.GetKeyDown(KeyCode.LeftArrow);
    21.    bool pRight = currentDirection != Direction.West  && Input.GetKeyDown(KeyCode.RightArrow);
    22.  
    23.     if(pRight) nextDirection = Direction.East;
    24.     if(pLeft)  nextDirection = Direction.West;
    25.     if(pDown)  nextDirection = Direction.South;
    26.     if(pUp)    nextDirection = Direction.North;
    27. }
    28.