Unknown difference between logic of OOP code compared to Procedural

Question:

I have written two versions of the same bouncing ball game. One is OOP based and one is procedural, and I would expect them to do the same thing. Except, the Object Oriented program behaves differently.

I don’t know the best way to explain it but the procedural code ‘bouncing’ keeps the ball bouncing indefinitely and bouncing to the same height each time. But the OOP code ‘bouncing’ increases the bounce height on each consecutive bounce. Yet I cannot find a difference in the logic between them.

OOP code

import pygame, time
        
class Ball(pygame.sprite.Sprite):
    def __init__(self, colour, radius):
        super().__init__()
        self.image = pygame.Surface([radius*2, radius*2])
        self.colour = colour
        self.radius = radius
        pygame.draw.circle(self.image, self.colour, (radius, radius), self.radius)
        self.rect = self.image.get_rect()
        self.rect.x = 350
        self.rect.y = 350
        self.change_y = 0.5
        self.vel_y = 0
    def update(self):
        self.vel_y += self.change_y
        self.rect.y += self.vel_y
    def bounce(self):
        self.vel_y = self.vel_y * -1
        self.rect.y += self.vel_y 

def play_game():
    all_sprites_list = pygame.sprite.Group()
    ball_list = pygame.sprite.Group()
    ball = Ball(WHITE, 10)
    ball_list.add(ball)
    all_sprites_list.add(ball)

    done = False
    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
        screen.fill(BLACK)
        all_sprites_list.draw(screen)
        for ball in ball_list:
            if ball.rect.y > 690:
                ball.bounce()
            else:
                ball.update()
        pygame.display.update()
        clock.tick(60)

BLACK = (0,0,0)
WHITE = (255,255,255)
RED = (255,0,0)
BLUE = (0,255,0)
GREEN = (0,0,255)

pygame.init()
screen_width = 700
screen_height = 700
screen = pygame.display.set_mode([screen_width, screen_height])
clock = pygame.time.Clock()

play_game()
pygame.quit() 

Procedural code

import pygame
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BROWN = (200, 100, 0)
 
pygame.init()
size = (700, 700)
screen = pygame.display.set_mode(size)
done = False
clock = pygame.time.Clock()

rect_x = 350
rect_y = 350
rect_changey = 0

while not done:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True
    screen.fill(BLACK)  
    pygame.draw.circle(screen, WHITE, [rect_x, rect_y], 10)
    if (rect_y > 690):
        rect_changey = rect_changey* -1
        rect_y += rect_changey
    else:
        rect_changey = rect_changey + 0.5
        rect_y += rect_changey
    pygame.display.flip()
    clock.tick(60)
 
pygame.quit()

Update: The ball.update() function is running 1 more time than the equivalent part of the code in the procedural code. Still dont know why though

Asked By: J4ns323

||

Answers:

rect_x and rect_y can store floating point values. However rect.x and rect.y cannot just store integral values.

Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.

The coordinates for Rect objects are all integers. […]

The fraction part of the coordinates gets lost when the new position of the object is assigned to the Rect object. If this is done every frame, the position error will accumulate over time.

If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location (e.g. .topleft) of the rectangle:

x, y = # floating point coordinates
rect.topleft = round(x), round(y)

Ball class:

class Ball(pygame.sprite.Sprite):
    def __init__(self, colour, radius):
        super().__init__()
        self.image = pygame.Surface([radius*2, radius*2])
        self.colour = colour
        self.radius = radius
        pygame.draw.circle(self.image, self.colour, (radius, radius), self.radius)
        self.rect = self.image.get_rect()
        self.x = 350
        self.y = 350
        self.rect.x = self.x
        self.rect.y = self.y
        self.change_y = 0.5
        self.vel_y = 0
    def update(self):
        self.vel_y += self.change_y
        self.y += self.vel_y
        self.rect.topleft = round(self.x), round(self.y)
    def bounce(self):
        self.vel_y = self.vel_y * -1
        self.y += self.vel_y 
        self.rect.topleft = round(self.x), round(self.y)
Answered By: Rabbid76
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.