I wrote a very simple Ping Pong game using pygame and everything worked out quite well. The only issue is the high CPU usage, which seems a bit strange. One thread is immediately occupied up to 100% after starting the application.
I am currently using a pygame.clock
and clock.tick(30)
inside my main loop. Therefore, pygame shouldn't occupied as many CPU cycles as possible. Additionally, I replaced the use of pygame.event.get()
(sometimes mentioned as a potential bottleneck), which did not change the CPU usage.
main.py
import pygame
from colors import *
from ball import Ball
from racket import Racket
from directions import Directions
from player import Player
clock = pygame.time.Clock()
WIN_WIDTH = 800
WIN_HEIGHT = 640
MAX_SCORE = 5
DISPLAY = (WIN_WIDTH, WIN_HEIGHT)
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode(DISPLAY, 0, 32)
DONE = False
FPS = 30
left_player = Player(Directions.LEFT, 'Left')
right_player = Player(Directions.RIGHT, 'Right')
curr_ball = Ball(screen, WIN_WIDTH, WIN_HEIGHT)
left_racket = Racket(screen, WIN_WIDTH, WIN_HEIGHT, Directions.LEFT)
right_racket = Racket(screen, WIN_WIDTH, WIN_HEIGHT, Directions.RIGHT)
rackets = pygame.sprite.Group()
rackets.add(left_racket)
rackets.add(right_racket)
stuff_to_draw = pygame.sprite.Group()
stuff_to_draw.add(left_racket)
stuff_to_draw.add(right_racket)
def game_over(screen, winner, left_paper, right_player):
gray_overlay = pygame.Surface((WIN_WIDTH, WIN_HEIGHT))
gray_overlay.fill(GRAY)
gray_overlay.set_colorkey(GRAY)
pygame.draw.rect(gray_overlay, BLACK, [0, 0, WIN_WIDTH, WIN_HEIGHT])
gray_overlay.set_alpha(99)
screen.blit(gray_overlay, (0, 0))
font = pygame.font.SysFont(None, 100)
game_over = font.render('{} Player WINS!'.format(winner.name), True, WHITE)
screen.blit(game_over, (WIN_WIDTH / 2 - 300, WIN_HEIGHT / 2 - 100))
scoreline = font.render(
'{} - {}'.format(left_paper.score, right_player.score), True, WHITE)
screen.blit(scoreline, (WIN_WIDTH / 2 - 50, WIN_HEIGHT / 2 + 100))
pygame.display.update()
pygame.time.delay(2000)
while not DONE:
screen.fill(BLACK)
for event in pygame.event.get():
if event.type == pygame.QUIT:
DONE = True
pygame.event.pump()
keys = pygame.key.get_pressed()
if keys[pygame.K_q]:
DONE = True
if keys[pygame.K_UP]:
right_racket.move_up()
if keys[pygame.K_DOWN]:
right_racket.move_down()
if keys[pygame.K_w]:
left_racket.move_up()
if keys[pygame.K_s]:
left_racket.move_down()
stuff_to_draw.update()
curr_ball.update()
col_left, col_right = curr_ball.rect.colliderect(left_racket.rect), curr_ball.rect.colliderect(right_racket.rect)
if col_right == 1 or col_left == 1:
curr_ball.toggle_direction()
curr_ball.hit()
if curr_ball.get_x_val() <= 0: # left border
right_player.score = 1
curr_ball = Ball(screen, WIN_WIDTH, WIN_HEIGHT)
elif curr_ball.get_x_val() >= WIN_WIDTH: # right border
left_player.score = 1
curr_ball = Ball(screen, WIN_WIDTH, WIN_HEIGHT)
# Print scores
font = pygame.font.SysFont('Helvetica', 25)
left_player_score = font.render(
'{}'.format(left_player.score), True, (255, 255, 255))
right_player_score = font.render(
'{}'.format(right_player.score), True, (255, 255, 255))
goal_text = font.render(
'{}'.format(MAX_SCORE), True, (255, 255, 0))
screen.blit(left_player_score, (WIN_WIDTH / 2 - 100, 10))
screen.blit(right_player_score, (WIN_WIDTH / 2 + 100, 10))
screen.blit(goal_text, (WIN_WIDTH / 2, 0))
stuff_to_draw.draw(screen)
curr_ball.draw(screen)
if left_player.score >= MAX_SCORE:
game_over(screen, left_player, left_player, right_player)
elif right_player.score >= MAX_SCORE:
game_over(screen, right_player, left_player, right_player)
if left_player.score >= MAX_SCORE or right_player.score >= MAX_SCORE:
DONE = True
pygame.display.set_caption('Ping Pong '+ str(clock.get_fps()))
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
colors.py
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GRAY = (64, 64, 64)
ball.py
import pygame
import random
from colors import *
from directions import *
class Ball(pygame.sprite.Sprite):
def __init__(self, screen, width, height):
super().__init__()
self.width, self.height = width, height
self.direction = random.choice([Directions.DOWN_LEFT, Directions.DOWN_RIGHT, Directions.UP_LEFT, Directions.UP_RIGHT])
self.screen = screen
self.image = pygame.Surface([10, 10])
self.image.fill(WHITE)
pygame.draw.rect(self.image, WHITE, [0, 0, 10, 10])
self.rect = self.image.get_rect()
self.position = (width / 2 + 2, height / + 2)
self.hits = 0
self.speed_up = 1.0
def draw(self, screen):
screen.blit(self.image, self.rect)
def hit(self):
self.hits += 1
self.speed_up = 1.0+self.hits/10
@property
def position(self):
return (self.rect.x, self.rect.y)
@position.setter
def position(self, pos):
try:
pos_x, pos_y = pos
except ValueError:
raise ValueError("Pass an iterable with two items")
else:
self.rect.x, self.rect.y = pos_x, pos_y
def up_left(self):
self.position = (self.position[0] - 10*self.speed_up, self.position[1] - 10*self.speed_up)
def up_right(self):
self.position = (self.position[0] + 10*self.speed_up, self.position[1] - 10*self.speed_up)
def down_left(self):
self.position = (self.position[0] - 10*self.speed_up, self.position[1] + 10*self.speed_up)
def down_right(self):
self.position = (self.position[0] + 10*self.speed_up, self.position[1] + 10*self.speed_up)
def update(self):
if self.position[1] <= 10: # upper border
self.direction = random.choice(
[Directions.DOWN_LEFT, Directions.DOWN_RIGHT])
if self.position[1] >= self.height - 10: # bottom border
self.direction = random.choice(
[Directions.UP_LEFT, Directions.UP_RIGHT])
options = {Directions.UP_LEFT: self.up_left,
Directions.UP_RIGHT: self.up_right,
Directions.DOWN_LEFT: self.down_left,
Directions.DOWN_RIGHT: self.down_right,
}
options[self.direction]()
def toggle_direction(self):
if self.direction == Directions.DOWN_LEFT:
new_direction = Directions.DOWN_RIGHT
if self.direction == Directions.DOWN_RIGHT:
new_direction = Directions.DOWN_LEFT
if self.direction == Directions.UP_RIGHT:
new_direction = Directions.UP_LEFT
if self.direction == Directions.UP_LEFT:
new_direction = Directions.UP_RIGHT
try:
self.direction = new_direction
except NameError:
pass
def get_x_val(self):
return self.rect.x
racket.py
import pygame
from colors import *
from directions import *
class Racket(pygame.sprite.Sprite):
def __init__(self, screen, width, height, side):
super().__init__()
self.width, self.height = width, height
self.racket_height = 100
self.movement_speed = 20
offset = 20
self.screen = screen
self.image = pygame.Surface([10, self.racket_height])
self.image.fill(WHITE)
pygame.draw.rect(self.image, WHITE, [0, 0, 10, self.racket_height])
self.rect = self.image.get_rect()
print(side)
if side is Directions.LEFT:
self.position = (offset, self.height / 2)
else:
self.position = (self.width - offset - 10, self.height / 2)
@property
def position(self):
return (self.rect.x, self.rect.y)
@position.setter
def position(self, pos):
try:
pos_x, pos_y = pos
except ValueError:
raise ValueError("Pass an iterable with two items")
else:
self.rect.x, self.rect.y = pos_x, pos_y
def move_up(self):
if self.position[1] > 0:
self.position = (self.position[0], self.position[1] - self.movement_speed)
def move_down(self):
if self.position[1] + self.racket_height < self.height:
self.position = (self.position[0], self.position[1] + self.movement_speed)
directions.py
from enum import Enum
class Directions(Enum):
UP_LEFT = 7
UP_RIGHT = 9
DOWN_LEFT = 1
DOWN_RIGHT = 3
LEFT = 4
RIGHT = 6
player.py
class Player():
def __init__(self, side, name):
self.side = side
self.points = 0
self.name = name
@property
def score(self):
return self.points
@score.setter
def score(self, val):
self.points += val
font
object each frame (do it once before the while loop). \$\endgroup\$