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
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)
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
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)