Home > Mobile >  WaitForSecondsRealtime not working as expected
WaitForSecondsRealtime not working as expected

Time:04-09

So decided to give game dev a try, picked up unity,Now I decided to create a simple ping pong game.

My game has Bat.cs class, ball.cs, and GameHandler.cs.

The GameHandler.cs initializes the batRight, batLeft, ball using the Bat.cs, Ball.cs class and prefabs, it also keeps track of if someone scores then starts a serve by stopping the rally:


public class GameHandler : MonoBehaviour
{
    public Bat bat;
    public Ball ball;
    public Score score;


    public static Vector2 bottomLeft;
    public static Vector2 topRight;
    public static bool rallyOn = false;
    public static bool isServingLeft = true;
    public static bool isServingRight = false;
    public static bool isTwoPlayers = true;

    static Bat batRight;
    static Bat batLeft;
    public static Ball ball1;


    static Score scoreLeft;
    static Score scoreRight;
    // Start is called before the first frame update
    void Start()
    {
        //Convert screens pixels coordinate into games coordinate
        bottomLeft = Camera.main.ScreenToWorldPoint(new Vector2(0, 0));
        topRight = Camera.main.ScreenToWorldPoint(new Vector2(Screen.width, Screen.height));

        ball1 =  Instantiate(ball) as Ball;
        //Debug.Log("GameHandler.Start");
        batRight = Instantiate(bat) as Bat;
        batRight.Init(true);
        batLeft = Instantiate(bat) as Bat;
        batLeft.Init(false);
        ball1.transform.position = new Vector3(batLeft.transform.position.x   ball1.getRadius() * 2, batLeft.transform.position.y);

        //Instatiate scores
        scoreLeft = Instantiate(score, new Vector2(-2, -4), Quaternion.identity) as Score;
        scoreRight = Instantiate(score, new Vector2(2, -4), Quaternion.identity) as Score;


    }

    private void Update()
    {
        if (isServingLeft)
        {
            ball1.transform.position = new Vector3(batLeft.transform.position.x   ball1.getRadius() * 2, batLeft.transform.position.y);
            if (Input.GetKey(KeyCode.LeftControl))
            {
                rallyOn = true;
                isServingLeft = false;
            }
        }

        if (isServingRight)
        {
            ball1.transform.position = new Vector3(batRight.transform.position.x - ball1.getRadius() * 2, batRight.transform.position.y);
            if (isTwoPlayers && Input.GetKey(KeyCode.RightControl))
            {
                rallyOn = true;
                isServingRight = false;
            }
            else
            {
                StartCoroutine(batRight.serveAi());
                if (GameHandler.rallyOn)
                {
                    StopCoroutine(batRight.serveAi());
                }
                
            }
        }
    }

    public static void increaseScoreByOne(bool isRight)
    {
        rallyOn = false;
        if (isRight)
        {
            scoreRight.increaseScore();
            isServingRight = true;

        }
        else
        {
            scoreLeft.increaseScore();
            isServingLeft = true;
        }
        
    }

}

The ball.cs listens to the static GameHandler.rallyOn and starts moving the ball accordingly, it also changes direction of the ball if it hits a vertical wall or the bat:

 public class Ball : MonoBehaviour
{
    [SerializeField]
    float speed;

    float radius;
    public Vector2 direction;
    public Vector3 ballPosition;

    // Start is called before the first frame update
    void Start()
    {
        direction = Vector2.one.normalized; // direction is (1, 1) normalized
        //Debug.Log("direction * speed * Time.deltaTime:"   direction * speed * Time.deltaTime);
        radius = transform.localScale.x / 2;
    }

    // Update is called once per frame
    void Update()
    {
        ballPosition = transform.position;
        if (GameHandler.rallyOn)
        {
            startRally();
        }

    }

    void startRally()
    {
        transform.Translate(direction * speed * Time.deltaTime);
        Debug.Log("direction * speed * Time.deltaTime:"   direction * speed * Time.deltaTime);

        if ((transform.position.y   radius) > GameHandler.topRight.y && direction.y > 0)
        {
            direction.y = -direction.y;
        }

        if ((transform.position.y   radius) < GameHandler.bottomLeft.y && direction.y < 0)
        {
            direction.y = -direction.y;
        }

        if ((transform.position.x   radius) > GameHandler.topRight.x && direction.x > 0)
        {
            // Left player scores
            GameHandler.increaseScoreByOne(false);

            //For no, just freeza the script
            // Time.timeScale = 0;
            //enabled = false;
        }

        if ((transform.position.x - radius) < GameHandler.bottomLeft.x && direction.x < 0)
        {
            // right player scores 
            GameHandler.increaseScoreByOne(true);
        }
    }

    void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.tag == "Bat")
        {
            if (collision.GetComponent<Bat>().isRight && direction.x > 0)
            {
                direction.x = -direction.x;
            }
            if (!collision.GetComponent<Bat>().isRight && direction.x < 0)
            {
                direction.x = -direction.x;
            }
        }
    }

    public float getRadius()
    {
        return radius;
    }
}

The bat.cs initiazes the two paddles, and either listens to user input if its 2 players or listens to player and use AI if it is player vs CPU.:

public class Bat : MonoBehaviour
{
    float height;
    [SerializeField] // this make this appear on editor without making this field public
    float speed;

    string input;
    public bool isRight;
    string PLAYER1_INPUT = "PaddleLeft";
    string PLAYER2_INPUT = "PaddleRight";

    // Start is called before the first frame update
    void Start()
    {
        height = transform.localScale.y;
    }

    // Update is called once per frame
    void Update()
    {
        if (GameHandler.isTwoPlayers)
        {
            if (isRight)
            {
                movePaddleonUserInput(PLAYER2_INPUT);
            }
            else
            {
                movePaddleonUserInput(PLAYER1_INPUT);
            }
        }
        else
        {
            if (isRight)
            {
                movePaddleAi();
            }
            else
            {
                movePaddleonUserInput(PLAYER1_INPUT);
            }
            

        }
    }

    void movePaddleAi()
    {
        if (isRight && GameHandler.ball1.direction.x > 0 && GameHandler.ball1.ballPosition.x > 0)
        {
            
            //transform.Translate(direction * speed * Time.deltaTime);

            if ((transform.position.y) > GameHandler.ball1.ballPosition.y && GameHandler.ball1.direction.y < 0)
            {
                transform.Translate(GameHandler.ball1.direction * speed * Time.deltaTime * Vector2.up);
            }

            if ((transform.position.y) < GameHandler.ball1.ballPosition.y && GameHandler.ball1.direction.y > 0)
            {
                transform.Translate(GameHandler.ball1.direction * speed * Time.deltaTime * Vector2.up);
            }
        }
        
    }

    void movePaddleonUserInput(string input)
    {
        // move = (-1 -> 1) * speed * timeDelta(this keep move framerate independent)
        float move = Input.GetAxis(input) * speed * Time.deltaTime;
        //Debug.Log((transform.position.y   height / 2)   " > "  GameHandler.topRight.y  "&&"   move  " > 0");
        //Restrict paddle movement to to the screen
        // (top edge of paddle > Screen top and we are moving up)
        if ((transform.position.y   height / 2) > GameHandler.topRight.y && move > 0)
        {
            move = 0;
        }
        // (bottom edge of paddle < Screen top and we are moving down)
        if ((transform.position.y - height / 2) < GameHandler.bottomLeft.y && move < 0)
        {
            move = 0;
        }

        transform.Translate(move * Vector2.up);
    }

    public void Init(bool isRightPaddle)
    {
        isRight = isRightPaddle;
        Vector2 pos;
        if (isRightPaddle)
        {
            isRight = isRightPaddle;
            pos = new Vector2(GameHandler.topRight.x, 0);
            // offset since center is the anchor
            pos -= Vector2.right * transform.localScale.x;
            input = "PaddleRight";
        }
        else
        {
            pos = new Vector2(GameHandler.bottomLeft.x, 0);
            // offset since center is the anchor
            pos  = Vector2.right * transform.localScale.x;
            input = "PaddleLeft";
        }

        transform.name = input;

        transform.position = pos;

    }

    public IEnumerator serveAi()
    {
        yield return new WaitForSecondsRealtime (2f);
        GameHandler.rallyOn = true;
        GameHandler.isServingRight = false;
       
    }
}

Now I also have score.cs and mainMenu scene , but no need to include that for this question, What I am struggling with now is the AI not stopping after it scores a point, I want the AI paddle to stop for a few seconds before it serves.

As you can see in the code, I added a yield return new WaitForSecondsRealtime(2f);

    public IEnumerator serveAi()
    {
        yield return new WaitForSecondsRealtime (2f);
        GameHandler.rallyOn = true;
        GameHandler.isServingRight = false;
       
    }

But this only cause the paddle to wait for the first time it scores , and then if it scores again in quick interval, it doesn't wait at all.

Not sure what I doing wrong here, thanks for your time.

CodePudding user response:

The more plausible explanation to me would be that GameHandler.rallyOn and isServing* aren't all being reset to the correct values to prevent the serve from occurring before the coroutine even begins, or another check is needed somewhere to prevent a serve from occurring if all of the correct booleans aren't set. Try placing appropriate breakpoints and stepping through in a debugger.

You'd probably be better off using WaitForSeconds rather than WaitForSecondsRealtime, by the way, at least if there's a chance you might want to allow the game to be paused in the future.

CodePudding user response:

Realize a the coroutine is called multiple times even before its finished due do it being called from update in GameHandler.cs. Needed a way to check if the co-routine is already running and not start a new one from update if so, used the Game.isServing variable to do so:

    public IEnumerator serveAi()
    {
        GameHandler.isServingRight = false; // coroutine started , no need to start new one
        yield return new WaitForSecondsRealtime (2f);
        GameHandler.rallyOn = true;
    }

And my update in GameHandler is already checking for that variable before starting the coroutine:

       private void Update()
    {
        if (isServingLeft)
        {
            ball1.transform.position = new Vector3(batLeft.transform.position.x   ball1.getRadius() * 2, batLeft.transform.position.y);
            if (Input.GetKey(KeyCode.LeftControl))
            {
                rallyOn = true;
                isServingLeft = false;
            }
        }

        if (isServingRight)
        {
            ball1.transform.position = new Vector3(batRight.transform.position.x - ball1.getRadius() * 2, batRight.transform.position.y);
            if (isTwoPlayers && Input.GetKey(KeyCode.RightControl))
            {
                rallyOn = true;
                isServingRight = false;
            }
            else
            {
                StartCoroutine(batRight.serveAi());
            }
        }
    }
  • Related