1

I have a problem with the game I'm currently working on. The point of the game is to hit a ball with a plank so that the ball doesn't drop. There are a lot of bugs with the code but i figured i should fix this one first. Basically, once the ball bounces off the plank i increase the velocity increment. However once the increment gets too high, you can see the ball 'jumping' instead of smoothly moving through the screen. I tried increasing the FPS and decreasing the velocity increment but it would go too slow. Below is my code:

import pygame
pygame.init()

win = pygame.display.set_mode((750, 750))

pygame.display.set_caption('Ball Plank')
clock = pygame.time.Clock()

x = 305
y = 650
width = 150
height = 10
vel = 30

score = 0
lives = 3

radius = 35
ball_x = 377
ball_y = 297
ball_vely = 10
ball_velx = 10

font = pygame.font.SysFont('comicsans', 40, True)

def drawing():

    win.fill((0, 0, 0))

    text_score = font.render('Score: ' + str(score), 1, (255, 255, 255))
    text_lives = font.render('Lives: ' + str(lives), 1, (255, 255, 255))
    win.blit(text_score, (570, 20))
    win.blit(text_lives, (30, 20))

    circle = pygame.draw.circle(win, (0, 255, 0), (int(ball_x), int(ball_y)), radius)
    rect = pygame.draw.rect(win, (255, 0, 0), (x, y, width, height))
    pygame.display.update()

run = True

while run:
    clock.tick(40)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    keys = pygame.key.get_pressed()

    if keys[pygame.K_LEFT] and x > vel:
        x -= vel
    if keys[pygame.K_RIGHT] and x < 750 - width - vel:
        x += vel

    ball_y += ball_vely
    ball_x += ball_velx
    if ball_y - radius - 10 <= 0:
        ball_vely *= -1

    elif ball_y + radius + 8 > y and ball_y <= y and ball_x > x - radius and ball_x < x + width + radius:
        ball_velx = 10
        if ball_vely > 0:
            ball_vely += 0.5
        else:
            ball_vely -= 0.5
        ball_vely *= -1
        score += 1

        if ball_x > x + 75:
            ball_velx *= 1 + (ball_x - x + 75)/150
        elif ball_x < x + 75:
            ball_velx *= -(1 + (ball_x - x + 75)/150)

    if ball_x - radius - 10 <= 0 or ball_x + radius + 10  >= 750:
        ball_velx *= -1
    if ball_y > 760 + radius:
        lives -= 1
    if lives == 0:
        run = False

    drawing()

pygame.display.quit()
pygame.quit()

Not sure what exactly is the problem, so sorry for the abundance of the code.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • where is any "fps" related code in the above listing? You just do things to draw as fastas your computer can go (and that will obviously suck up all CPU resources) – jsbueno Nov 25 '19 at 17:40
  • Sorry, somehow this escaped. I was using the clock command `clock = pygame.time.Clock()` `clock.tick(40)` I edited the question, now it should be correct – Артем Брехин Nov 25 '19 at 17:44
  • *"[...] it would go too slow."* - the ball seems to move very fast. What exactly is the issue? – Rabbid76 Nov 25 '19 at 17:50
  • @Rabbid76 i meant if i increase the FPS and decrease the velocity increment the ball would then go too slow so it is not the solution (and it isn't in the code). – Артем Брехин Nov 25 '19 at 17:55
  • @АртемБрехин So you've decreased the velocity too much. – Rabbid76 Nov 25 '19 at 17:57
  • @Rabbid76 exactly, so it cannot be a solution. but the issue is that velocity that too high results in the 'jumps' of the ball. Instead i want smooth steady motion – Артем Брехин Nov 25 '19 at 18:12
  • @АртемБрехин I don't get it. If the ball is to slow, then you can increase the FPS. What do you mean by *"jumps"*? – Rabbid76 Nov 25 '19 at 18:33
  • 1
    @Rabbid76 when the ball is too slow and i increase the fps, it doesn't actually increase the speed because my computer cannot go above certain value of fps (since clock.tick() only puts an upper boundary). I have to find a way so that the circle will move smoother without sacrificing the speed of its movement (meaning smaller increments otherwise it will look like the ball just moved 1cm on the screen instead of continuous motion). Is there a way to do that? – Артем Брехин Nov 25 '19 at 21:10
  • You might want to read [this article](http://gameprogrammingpatterns.com/game-loop.html) about how to structure your game loop. That link isn't pygame specific, but the idea should hold. You basically need to decouple your rendering (drawing function, which runs in an infinite while loop without a ticking clock) from your fixed interval game logic (which updates the ball position and runs at 40 fps). – CodeSurgeon Nov 27 '19 at 17:43
  • Here is [another article](https://gafferongames.com/post/fix_your_timestep/) about the same topic if the previous one is unclear. – CodeSurgeon Nov 27 '19 at 17:45
  • @CodeSurgeon wow that's exactly what i'm looking for. Do you know what i can use to do that in pygame? – Артем Брехин Nov 28 '19 at 15:26
  • I would use python's time module rather than a pygame clock object. The latter has extremely poor resolution (as it uses SDL's basic timer behind the scenes and runs at 15ms for its accuracy). There is nothing built-in as far as I am aware, but it is easy enough to implement from the articles I linked to using that time.time() function. – CodeSurgeon Nov 28 '19 at 16:43

1 Answers1

0

You have to calculate the movement per frame depending on the frame rate.

pygame.time.Clock.tick returns the number of milliseconds since the last call. When you call it in the application loop, this is the number of milliseconds that have passed since the last frame. Multiply the objects speed by the elapsed time per frame to get constant movement regardless of FPS.

Define the distance in pixels that the player should move per second (ball_move_per_second, paddle_move_per_second). Compute the distance per frame in the application loop (ball_move_per_frame, paddle_move_per_frame). Add a direction vector of type pygame.math.Vector2 for the movement of the ball:

clock = pygame.time.Clock()
ball_dir = pygame.math.Vector2(1, 1).normalize()
ball_move_per_second = 500
paddle_move_per_second = 500

run = True
while run:
    ms_frame = clock.tick(300)
    ball_move_per_frame = ball_move_per_second * ms_frame / 1000
    paddle_move_per_frame = paddle_move_per_second * ms_frame / 1000 

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        x = max(0, x - paddle_move_per_frame)
    if keys[pygame.K_RIGHT]:
        x = min(x + paddle_move_per_frame, win.get_width() - width)

    ball_x += ball_dir.x * ball_move_per_frame
    ball_y += ball_dir.y * ball_move_per_frame

Additionally, there are some issues with your collision test. For solutions, see Pong and Sometimes the ball doesn't bounce off the paddle in pong game.


Complete example:

import pygame
pygame.init()

win = pygame.display.set_mode((750, 750))

pygame.display.set_caption('Ball Plank')
clock = pygame.time.Clock()

x, y = 305, 650
width, height = 150, 10
score = 0
lives = 3
radius = 35
ball_x, ball_y = 377, 297
ball_dir = pygame.math.Vector2(1, 1).normalize()
ball_move_per_second = 500
paddle_move_per_second = 500

def drawing():

    win.fill((0, 0, 0))

    text_score = font.render('Score: ' + str(score), 1, (255, 255, 255))
    text_lives = font.render('Lives: ' + str(lives), 1, (255, 255, 255))
    win.blit(text_score, (570, 20))
    win.blit(text_lives, (30, 20))

    circle = pygame.draw.circle(win, (0, 255, 0), (round(ball_x), round(ball_y)), radius)
    rect = pygame.draw.rect(win, (255, 0, 0), (round(x), round(y), width, height))
    pygame.display.update()

font = pygame.font.SysFont('comicsans', 40, True)

run = True
while run:
    ms_frame = clock.tick(300)
    ball_move_per_frame = ball_move_per_second * ms_frame / 1000
    paddle_move_per_frame = paddle_move_per_second * ms_frame / 1000

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.KEYDOWN and event.key == pygame.K_r:
            x, y = 305, 650
            ball_x, ball_y = 377, 297
            ball_dir = pygame.math.Vector2(1, 1).normalize()
            ball_move_per_second = 500
            paddle_move_per_second = 500
            score = 0
            lives = 3

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        x = max(0, x - paddle_move_per_frame)
    if keys[pygame.K_RIGHT]:
        x = min(x + paddle_move_per_frame, win.get_width() - width)

    ball_x += ball_dir.x * ball_move_per_frame
    ball_y += ball_dir.y * ball_move_per_frame
    
    if ball_x < radius:
        ball_x = radius
        ball_dir.x *= -1
    if ball_x > win.get_width() - radius:
        ball_x = win.get_width() - radius
        ball_dir.x *= -1
    
    if ball_y < radius:
        ball_y = radius
        ball_dir.y *= -1
    
    if ball_dir.y > 0 and y - radius < ball_y  < y - radius + height and x - radius < ball_x < x + width + radius:
        ball_y = y - radius
        ball_dir.y *= -1
        ball_move_per_second += 10
        score += 1

    elif ball_y > win.get_height() - radius:
        ball_y = win.get_height() - radius
        ball_dir.y *= -1
        lives -= 1

    drawing()

pygame.display.quit()
pygame.quit()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174