WaitForSecondsRealtime 未按预期工作

WaitForSecondsRealtime not working as expected

所以决定尝试游戏开发,选择了 unity,现在我决定创建一个简单的乒乓球游戏。

我的游戏有Bat.csclass、ball.cs和GameHandler.cs。

GameHandler.cs 使用 Bat.cs、Ball.cs class 和预制件初始化 batRight、batLeft、球,它还会跟踪是否有人得分然后开始停止集会发球:


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

}

ball.cs 听静态 GameHandler.rallyOn 并开始相应地移动球,如果球撞到垂直的墙壁或球棒,它也会改变方向:

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

bat.cs 初始化两个球拍,如果是 2 个玩家则听取用户输入,或者如果是玩家 vs CPU 则听取玩家并使用 AI。:

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

现在我还有 score.cs 和 mainMenu scene ,但是这个问题不需要包括它,我现在纠结的是 AI 在得分后没有停止,我想要 AI paddle在服务前停止几秒钟。

正如您在代码中看到的,我添加了一个 yield return new WaitForSecondsRealtime(2f);

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

但这只会导致桨等待它第一次得分,然后如果它在快速间隔内再次得分,它根本不会等待。

不确定我在这里做错了什么,谢谢你的时间。

对我来说更合理的解释是 GameHandler.rallyOnisServing* 并没有全部重置为正确的值,以防止在协同程序甚至开始之前发生服务,或者进行另一次检查如果未设置所有正确的布尔值,则某处需要防止发球。尝试放置适当的断点并在调试器中单步执行。

顺便说一下,您最好使用 WaitForSeconds 而不是 WaitForSecondsRealtime,至少在您可能希望将来暂停游戏的情况下。

意识到协程甚至在完成之前就被多次调用,因为它是从 GameHandler.cs 中的更新函数调用的。需要一种方法来检查 co-routine 是否已经 运行 并且如果是这样则不从更新开始一个新的,使用 Game.isServing 变量来这样做:

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

我在 GameHandler 中的更新已经在启动协程之前检查该变量:

       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());
            }
        }
    }