How to fix inconsistent frame rate (speed) in python turtle

梦想的初衷 提交于 2021-02-10 13:25:13

问题


I a made pong game by following this tutorial https://youtu.be/C6jJg9Zan7w The problem I'm having is that speed of the ball (turtle object) is not the same on different computers. For example, on the tutorial instructor's computer the value of ball.dx ball.dy is 2 and the ball speed is normal but on my computer is really fast so I had to set it to 0.1. I think this problem is because different computers can put out more or less frame. I know there is a method in popular game engines (like unity-unreal) that use time instead of frames so variables are global in different computers. Another problem I have is that when I move the paddles speed of the ball slightly changes. I don't know if this is the same issue as above

import turtle
import winsound

wn = turtle.Screen()
wn.title('Pong')
wn.bgcolor('black')
wn.setup(width=800, height=600)
wn.tracer(0)

# Paddle A
paddle_a = turtle.Turtle()
paddle_a.speed(0)
paddle_a.shape('square')
paddle_a.color('white')
paddle_a.penup()
paddle_a.goto(-350, 0)
paddle_a.shapesize(5, 1)

# Paddle B
paddle_b = turtle.Turtle()
paddle_b.speed(0)
paddle_b.shape('square')
paddle_b.color('white')
paddle_b.penup()
paddle_b.goto(350, 0)
paddle_b.shapesize(5, 1)

# Ball
ball = turtle.Turtle()
ball.speed(0)
ball.shape('square')
ball.color('white')
ball.penup()
ball.dx = 0.15
ball.dy = 0.15

# Pen
pen = turtle.Turtle()
pen.speed(0)
pen.color('white')
pen.penup()
pen.goto(0, 260)
pen.write("Player A: 0  Player B: 0", align='center', font=('Courier', 24, 'bold'))
pen.hideturtle()

# Score
score_a = 0
score_b = 0

def paddle_a_up():
    y = paddle_a.ycor()
    y += 20
    paddle_a.sety(y)

def paddle_b_up():
    y = paddle_b.ycor()
    y += 20
    paddle_b.sety(y)

def paddle_a_down():
    y = paddle_a.ycor()
    y += -20
    paddle_a.sety(y)

def paddle_b_down():
    y = paddle_b.ycor()
    y += -20
    paddle_b.sety(y)

# Keyboard binding
wn.listen()
wn.onkeypress(paddle_a_up, 'w')
wn.onkeypress(paddle_a_down, 's')
wn.onkeypress(paddle_b_up, 'Up')
wn.onkeypress(paddle_b_down, 'Down')


# Main game loop
while True:
    wn.update()

    # Moving Ball
    ball.setx(ball.xcor() + ball.dx)
    ball.sety(ball.ycor() + ball.dy)

    # Border checking
    if ball.ycor() > 290 or ball.ycor() < -290:
        winsound.PlaySound('bounce.wav', winsound.SND_ASYNC)
        ball.dy *= -1

    if ball.xcor() > 390:
        winsound.PlaySound('bounce.wav', winsound.SND_ASYNC)
        ball.goto(0, 0)
        ball.dx *= -1
        score_a += 1
        pen.clear()
        pen.write("Player A: {}  Player B: {}".format(score_a, score_b), align='center', font=('Courier', 24, 'bold'))

    if ball.xcor() < -390:
        winsound.PlaySound('bounce.wav', winsound.SND_ASYNC)
        ball.goto(0, 0)

        ball.dx *= -1
        score_b += 1
        pen.clear()
        pen.write("Player A: {}  Player B: {}".format(score_a, score_b), align='center', font=('Courier', 24, 'bold'))

    # Paddle and ball collisions
    if (ball.xcor() > 340 and ball.xcor() < 350) and (ball.ycor() < paddle_b.ycor() + 60 and ball.ycor() > paddle_b.ycor() -60):
        winsound.PlaySound('bounce.wav', winsound.SND_ASYNC)
        ball.setx(340)
        ball.dx *= -1

    if (ball.xcor() < -340 and ball.xcor() > -350) and (ball.ycor() < paddle_a.ycor() + 60 and ball.ycor() > paddle_a.ycor() -60):
        winsound.PlaySound('bounce.wav', winsound.SND_ASYNC)
        ball.setx(-340)
        ball.dx *= -1

回答1:


Your while True: has no place in an event-driven environment like turtle. The first level replacement would be to put the body of your loop into a function that you invoke with ontimer(). This will call your routine at fixed intervals (make sure to include the ontimer() call as the last step as it's a one-shot.) Something like:

def move_balls():
    global score_a, score_b

    wn.update()

    # Moving Ball
    ball.setx(ball.xcor() + ball.dx)
    ball.sety(ball.ycor() + ball.dy)

    # Border checking
    if ball.ycor() > 290 or ball.ycor() < -290:
        winsound.PlaySound('bounce.wav', winsound.SND_ASYNC)
        ball.dy *= -1

    if ball.xcor() > 390:
        winsound.PlaySound('bounce.wav', winsound.SND_ASYNC)
        ball.goto(0, 0)
        ball.dx *= -1
        score_a += 1
        pen.clear()
        pen.write("Player A: {}  Player B: {}".format(score_a, score_b), align='center', font=('Courier', 24, 'bold'))

    elif ball.xcor() < -390:
        winsound.PlaySound('bounce.wav', winsound.SND_ASYNC)
        ball.goto(0, 0)
        ball.dx *= -1
        score_b += 1
        pen.clear()
        pen.write("Player A: {}  Player B: {}".format(score_a, score_b), align='center', font=('Courier', 24, 'bold'))

    # Paddle and ball collisions
    if (340 < ball.xcor() < 350) and (paddle_b.ycor() - 60 < ball.ycor() < paddle_b.ycor() + 60):
        winsound.PlaySound('bounce.wav', winsound.SND_ASYNC)
        ball.setx(340)
        ball.dx *= -1

    elif (-350 < ball.xcor() < -340) and (paddle_a.ycor() - 60 < ball.ycor() < paddle_a.ycor() + 60):
        winsound.PlaySound('bounce.wav', winsound.SND_ASYNC)
        ball.setx(-340)
        ball.dx *= -1

    wn.ontimer(move_balls, 100)

move_balls()

However, the timer only controls the time between the end of one call to move_balls() and the start of another. It doesn't account for the time spent in the method itself. If you need more precise control, then something like this high precision frames per second code might be what you're looking for.




回答2:


When animating, especially if you want control the speed of animation, you need stick to the principle of model-view separation. This means you have one part of program that controls the state of objects (model), and the other part that draws the objects (view). State of objects may include position, heading, color, and other properties. This part of program that changes objects’ states is not concerned with drawing of the objects. The drawing is done by separate part of program that checks the states of the objects and render them on the screen.

cdlane's answer mixed model and view. Within one single timer function, objects' states are changed and also rendered (which could take lots of CPU time). This will cause delay to the next time this function will be called.

My solution is to make move_balls() functions change the positions of the balls only (without drawing it), which will return with virtually no delay keeping the time interval consistent. Then you put code for drawing the objects into a timer handler with minimal possible delay. To avoid unnecessary drawing make a should_draw global variable and returns immediately when it is false, without wasting CPU time. After move_balls() function executes the objects will need drawing. So make sure set should_draw to true before returning from move_balls(). Also, after finished drawing is done, make sure set should_draw to False. (this part edited after discussion with cdlane)

This should allow you draw a lot of objects with smooth animation. Check out this Youtube video that draws hundreds of fireflies lighting up every (almost precisely) 5'th second. There is also a tutorial in the video description.

https://www.youtube.com/watch?v=K9D-wO4w_k0

Here is tutorial with complete code: https://pythonturtle.academy/tutorial-fireflies/



来源:https://stackoverflow.com/questions/55495581/how-to-fix-inconsistent-frame-rate-speed-in-python-turtle

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!