background music in the gameloop - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: Game Development (https://python-forum.io/forum-11.html) +--- Thread: background music in the gameloop (/thread-36731.html) |
background music in the gameloop - flash77 - Mar-24-2022 Dear community, I've got a problem with game development/tkinter... I generated a form which let the user choose some tracks. These tracks should be played randomly in the game loop. I printed playlist after function "ischecked" is processed - and it contains, as expected, the randomly sorted values of "listl". But in main() the list "playlist" is emtpy... What I am doing wrong? Thanks a lot for your help! import random, math, pygame, tkinter from tkinter import * from PIL import Image from itertools import product #from PyQt6.QtWidgets import QLabel, QRadioButton, QVBoxLayout, QApplication, QWidget #from PyQt6.QtGui import * pygame.init() pygame.mixer.init() pygame.mixer.music.set_volume(0.7) master = Tk() master.geometry('500x400') # Define colors used by the game. TEXT_COLOR = (255, 255, 255) FOREGROUND = (0, 0, 0) # Recolor image pixels that are this color TRANSPARENT = (255, 255, 255) # Make image pixels this color transparent BALL_COLOR = (255, 255, 255) PADDLE_COLOR = (255, 255, 255) BRICK_COLORS = ((255, 0, 0), (255, 50, 0), (255, 100, 0), (255, 150, 0), (255, 200, 0), (255, 255, 0)) BRICK_COORDS = [ (32, 32), (64, 32), (96, 32), (160, 32), (288, 32), (320, 32), (352, 32), (416, 32), (448, 32), (480, 32), (576, 32), (608, 32), (640, 32), (32, 64), (160, 64), (288, 64), (352, 64), (416, 64), (480, 64), (576, 64), (640, 64), (32, 96), (160, 96), (288, 96), (352, 96), (416, 96), (480, 96), (576, 96), (640, 96), (32, 128), (64, 128), (96, 128), (160, 128), (288, 128), (352, 128), (416, 128), (448, 128), (480, 128), (576, 128), (608, 128), (640, 128), (32, 160), (160, 160), (288, 160), (352, 160), (416, 160), (448, 160), (576, 160), (640, 160), (32, 192), (160, 192), (288, 192), (352, 192), (416, 192), (480, 192), (576, 192), (640, 192), (32, 224), (160, 224), (192, 224), (224, 224), (288, 224), (320, 224), (352, 224), (416, 224), (512, 224), (576, 224), (640, 224)] # Define some image files. These are not your files BALL_IMAGE = "ball.png" PADDLE_IMAGE = "paddle.png" BRICK_FILES = (("brick0.png", "brick1.png", "brick2.png"), ("bbrick0.png", "bbrick1.png", "bbrick2.png")) BACKGROUND_IMAGE = pygame.image.load("Hintergrund.png") SCREEN_WIDTH = 800 SCREEN_HEIGHT = 578 SCORE_POSITION = (SCREEN_WIDTH - 150, SCREEN_HEIGHT - 30) listl = [] playlist = [] def insert_into_listl(listl, track): # Adding songs file in our listl listl.append(track) def isChecked(): if cb1.get() is True: insert_into_listl(listl, "1.mp3") if cb2.get() is True: insert_into_listl(listl, "2.mp3") if cb3.get() is True: insert_into_listl(listl, "3.mp3") if cb4.get() is True: insert_into_listl(listl, "4.mp3") if cb5.get() is True: insert_into_listl(listl, "5.mp3") if cb6.get() is True: insert_into_listl(listl, "6.mp3") if cb7.get() is True: insert_into_listl(listl, "7.mp3") if cb8.get() is True: insert_into_listl(listl, "8.mp3") if cb9.get() is True: insert_into_listl(listl, "9.mp3") if cb10.get() is True: insert_into_listl(listl, "10.mp3") playlist = random.sample(listl, len(listl)) return playlist def play(bnr): if bnr == 1: pygame.mixer.music.load(str(bnr) + ".mp3") pygame.mixer.music.play() elif bnr == 2: pygame.mixer.music.load(str(bnr) + ".mp3") pygame.mixer.music.play() elif bnr == 3: pygame.mixer.music.load(str(bnr) + ".mp3") pygame.mixer.music.play() elif bnr == 4: pygame.mixer.music.load(str(bnr) + ".mp3") pygame.mixer.music.play() elif bnr == 5: pygame.mixer.music.load(str(bnr) + ".mp3") pygame.mixer.music.play() elif bnr == 6: pygame.mixer.music.load(str(bnr) + ".mp3") pygame.mixer.music.play() elif bnr == 7: pygame.mixer.music.load(str(bnr) + ".mp3") pygame.mixer.music.play() elif bnr == 8: pygame.mixer.music.load(str(bnr) + ".mp3") pygame.mixer.music.play() elif bnr == 9: pygame.mixer.music.load(str(bnr) + ".mp3") pygame.mixer.music.play() elif bnr == 10: pygame.mixer.music.load(str(bnr) + ".mp3") pygame.mixer.music.play() l = Label(master, text="Welche tracks sollen zufällig gespielt werden (mit playlist)?", font=('arial', 12, 'bold', 'italic')) l.place(x=0, y=0) button1 = tkinter.Button(master, text="reinhören", command=lambda: play(1)) button1.place(x=0, y=25) button2 = tkinter.Button(master, text="reinhören", command=lambda: play(2)) button2.place(x=0, y=50) button3 = tkinter.Button(master, text="reinhören", command=lambda: play(3)) button3.place(x=0, y=75) button4 = tkinter.Button(master, text="reinhören", command=lambda: play(4)) button4.place(x=0, y=100) button5 = tkinter.Button(master, text="reinhören", command=lambda: play(5)) button5.place(x=0, y=125) button6 = tkinter.Button(master, text="reinhören", command=lambda: play(6)) button6.place(x=0, y=150) button7 = tkinter.Button(master, text="reinhören", command=lambda: play(7)) button7.place(x=0, y=175) button8 = tkinter.Button(master, text="reinhören", command=lambda: play(8)) button8.place(x=0, y=200) button9 = tkinter.Button(master, text="reinhören", command=lambda: play(9)) button9.place(x=0, y=225) button10 = tkinter.Button(master, text="reinhören", command=lambda: play(10)) button10.place(x=0, y=250) cb1=BooleanVar() cb2=BooleanVar() cb3=BooleanVar() cb4=BooleanVar() cb5=BooleanVar() cb6=BooleanVar() cb7=BooleanVar() cb8=BooleanVar() cb9=BooleanVar() cb10=BooleanVar() Checkbutton1 = Checkbutton(master, text="Adeline Yeo (HP) - Kite Fly High", variable=cb1) Checkbutton1.place(x=75, y=25) Checkbutton2 = Checkbutton(master, text="Nul Tiel Records - Fireflies", variable=cb2) Checkbutton2.place(x=75, y=50) Checkbutton3 = Checkbutton(master, text="cryptic scenery - Endzeit Endlos", variable=cb3) Checkbutton3.place(x=75, y=75) Checkbutton4 = Checkbutton(master, text="cryptic scenery - Helix Spire", variable=cb4) Checkbutton4.place(x=75, y=100) Checkbutton5 = Checkbutton(master, text="cryptic scenery - Minsk Metro", variable=cb5) Checkbutton5.place(x=75, y=125) Checkbutton6 = Checkbutton(master, text="cryptic scenery - Stazione Termini", variable=cb6) Checkbutton6.place(x=75, y=150) Checkbutton7 = Checkbutton(master, text="cryptic scenery - The Future was Japanese", variable=cb7) Checkbutton7.place(x=75, y=175) Checkbutton8 = Checkbutton(master, text="Ketsa - Holding The Line", variable=cb8) Checkbutton8.place(x=75, y=200) Checkbutton9 = Checkbutton(master, text="Maarten Schellekens - Salt Lake Swerve", variable=cb9) Checkbutton9.place(x=75, y=225) Checkbutton10 = Checkbutton(master, text="Strobotone - Dance Track", variable=cb10) Checkbutton10.place(x=75, y=250) button11 = tkinter.Button(master, text="ausgewählte tracks zu playlist hinzufügen", command=isChecked) button11.place(x=75, y=275) def create_image(file, color=None): """ Create image from a file. If color is specified, replace all FOREGROUND pixels with color pixels. Modify image so TRANSPARENT colored pixels are transparent. """ if color: # Recolor the image image = Image.open(file).convert("RGB") for xy in product(range(image.width), range(image.height)): if image.getpixel(xy) == FOREGROUND: image.putpixel(xy, color) image = pygame.image.fromstring(image.tobytes(), image.size, "RGB") else: image = pygame.image.load(file) image.set_colorkey(TRANSPARENT) return image.convert() class EnhancedSprite(pygame.sprite.Sprite): """ Sprite with image and rectangle. I expose some of my rectangle's properties. """ def __init__(self, image, group=None, **kwargs): super().__init__(**kwargs) self.image = image self.rect = image.get_rect() if group is not None: group.add(self) def at(self, x, y): """Convenience method for setting my position""" self.x = x self.y = y return self # Properties below expose properties of my rectangle so you can use # self.x = 10 or self.centery = 30 instead of self.rect.x = 10 @property def x(self): return self.rect.x @x.setter def x(self, value): self.rect.x = value @property def y(self): return self.rect.y @y.setter def y(self, value): self.rect.y = value @property def centerx(self): return self.rect.centerx @centerx.setter def centerx(self, value): self.rect.centerx = value @property def centery(self): return self.rect.centery @centery.setter def centery(self, value): self.rect.centery = value @property def right(self): return self.rect.right @right.setter def right(self, value): self.rect.right = value @property def bottom(self): return self.rect.bottom @bottom.setter def bottom(self, value): self.rect.bottom = value class Brick(EnhancedSprite): """ A target for the ball. After I take some number of hits I die. Number of hits I can take is in range 1 to 3. Hits is randomly selected if not specified. Specify brick color using (R, G, B) format. If color not specified a color is selected based on the row. """ group = pygame.sprite.Group() def __init__(self, x, y, image_files=None, color=None, hits=None): color = color or random.choice(BRICK_COLORS) hits = hits or random.choice((1, 1, 1, 2, 2, 3)) self.value = self.hits = max(1, min(3, hits)) image_files = image_files or random.choice(BRICK_FILES) self.images = [create_image(file, color) for file in image_files] super().__init__(self.images[self.hits - 1], self.group) self.at(x, y) def __len__(self): """Return how many bricks remaining""" return len(self.group) def hit(self, score): """ I was hit! Update my appearance or die based on my hit total. Return my value if I was killed. """ self.hits -= 1 if self.hits > 0: self.image = self.images[self.hits - 1] return 0 self.kill() return self.value class Paddle(EnhancedSprite): """The sprite the player moves around to redirect the ball""" group = pygame.sprite.Group() def __init__(self, bottom): super().__init__(create_image(PADDLE_IMAGE, PADDLE_COLOR), self.group) self.bottom = bottom self.xmin = self.rect.width // 2 # Compute paddle x range. self.xmax = SCREEN_WIDTH - self.xmin def move(self, x): """Move to follow the cursor. Clamp to window bounds""" self.centerx = max(self.xmin, min(self.xmax, x)) class LifeCounter(): """Keep track of lives count. Display lives remaining using ball image""" def __init__(self, x, y, count=5): self.x, self.y = x, y self.image = create_image(BALL_IMAGE, BALL_COLOR) self.spacing = self.image.get_width() + 5 self.group = pygame.sprite.Group() self.reset(count) def reset(self, count): """Reset number of lives""" self.count = count for c in range(count - 1): EnhancedSprite(self.image, self.group).at(self.x + c * self.spacing, self.y) def __len__(self): """Return number of lives remaining""" return self.count def kill(self): """Reduce number of lives""" if self.count > 1: self.group.sprites()[-1].kill() self.count = max(0, self.count - 1) class Ball(EnhancedSprite): """Ball bounces around colliding with walls, paddles and bricks""" group = pygame.sprite.Group() def __init__(self, paddle, lives, speed=5): super().__init__(create_image(BALL_IMAGE, BALL_COLOR), self.group) self.paddle = paddle self.lives = lives self.speed = speed self.dx = self.dy = 0 self.xmax = SCREEN_WIDTH - self.rect.width self.ymax = paddle.bottom - self.rect.height self.reset(0) def reset(self, score=None): """Reset for a new game""" self.active = False if score is not None: self.score = score def start(self): """Start moving the ball in a random direction""" angle = random.random() - 0.5 # Launch angle limited to about +/-60 degrees self.dx = int(self.speed * math.sin(angle)) self.dy = -int(self.speed * math.cos(angle)) self.active = True def move(self): """Update the ball position. Check for collisions with bricks, walls and the paddle""" if not self.active: # Sit on top of the paddle self.centerx = self.paddle.centerx self.bottom = self.paddle.y - 2 return self # Did I hit some bricks? Update the bricks and the score x1, y1 = self.x, self.y x2, y2 = x1 + self.dx, y1 + self.dy if (xhits := pygame.sprite.spritecollide(self.at(x2, y1), Brick.group, False)): self.dx = -self.dx if (yhits := pygame.sprite.spritecollide(self.at(x1, y2), Brick.group, False)): self.dy = -self.dy if (hits := set(xhits) or set(yhits)): for brick in hits: self.score += brick.hit(self.score) # Did I hit a wall? if x2 <= 0 or x2 >= self.xmax: self.dx = -self.dx hits = True if y2 <= 0: self.dy = abs(self.dy) hits = True # Did I hit or get past the paddle? if y2 >= self.paddle.y: # Did it get past the paddle? if self.x > self.paddle.right or self.right < self.paddle.x: self.lives.kill() self.active = False else: # I hit the paddle. Compute angle of reflection bangle = math.atan2(-self.dx, self.dy) # Ball angle of approach pangle = math.atan2(self.centerx - self.paddle.centerx, 30) # Paddle angle rangle = (pangle - bangle) / 2 # Angle of reflection self.dx = math.sin(rangle) * self.speed self.dy = -math.cos(rangle) * self.speed hits = True if hits: self.at(x1, y1) else: self.at(x2, y2) def start_playlist(playlist): # Loading first audio file into our player pygame.mixer.music.load(playlist[0]) # Playing our music pygame.mixer.music.play() if len(playlist) > 1: playlist.pop(0) pygame.mixer.music.queue(playlist[0]) # setting up an end event which host an event # after the end of every song pygame.mixer.music.set_endevent(pygame.USEREVENT) # checking if any event has been # hosted at time of playing for event in pygame.event.get(): # A event will be hosted # after the end of every song if event.type == pygame.USEREVENT: if len(playlist) > 0: pygame.mixer.music.queue(playlist[0]) playlist.pop(0) if not pygame.mixer.music.get_busy(): break def main(playlist): """Play game until out of lives or out of bricks""" def displayText(text, font, pos=None, color=TEXT_COLOR): text = font.render(text, 1, color) if pos is None: pos = ((SCREEN_WIDTH - text.get_width()) // 2, (SCREEN_HEIGHT - text.get_height()) // 2) screen.blit(text, pos) screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("Breakout") clock = pygame.time.Clock() allsprites = pygame.sprite.Group() score_font = pygame.font.Font(None, 34) try: level = 1 lives = LifeCounter(10, SCREEN_HEIGHT - 30) paddle = Paddle(bottom=SCREEN_HEIGHT - 40) ball = Ball(paddle, lives) allsprites.add(paddle.group, lives.group, ball.group) while len(lives) > 0: # Start new board. Could have different layouts for each level for coord in BRICK_COORDS: Brick(*coord) allsprites.add(Brick.group) # Play until out of bricks or lives while len(lives) > 0 and len(Brick.group): print(playlist) start_playlist(playlist) clock.tick(60) for event in pygame.event.get(): if event.type == pygame.QUIT: raise SystemExit elif event.type == pygame.MOUSEMOTION: paddle.move(event.pos[0]) elif event.type == pygame.MOUSEBUTTONUP: if not ball.active: ball.start() ball.move() screen.blit(BACKGROUND_IMAGE, (0, 0)) displayText(f"Score: {ball.score}", font=score_font, pos=SCORE_POSITION) allsprites.draw(screen) pygame.display.flip() # Display results if len(lives) == 0: displayText("Game over", font=pygame.font.Font(None, 74)) elif len(Brick.group) == 0: level += 1 displayText(f"Level {level}", font=pygame.font.Font(None, 74)) ball.speed *= 1.25 ball.reset(ball.score) pygame.display.flip() pygame.time.wait(3000) finally: pygame.quit() if __name__ == "__main__": mainloop() main(playlist) RE: background music in the gameloop - deanhystad - Mar-24-2022 You assign playlist = [] in line 40 and never modify the list or assign a different list to playlist. You have a different variable named playlist that is assigned a value in line 68, but this playlist is a local variable that only exists inside the function isChecked(). Why are you writing this horrible code with all the cbx, buttonx and Checkbuttonx variables and a hard coded list of songs? I mentioned in response to another post that any time you have multiple related things that you should use lists, tuples or dictionaries instead of individual variables. Your player code should look like this: import tkinter as tk songs = ( "Adeline Yeo (HP) - Kite Fly High", "Nul Tiel Records - Fireflies", "cryptic scenery - Endzeit Endlos", "cryptic scenery - Helix Spire", "cryptic scenery - Minsk Metro", "cryptic scenery - Stazione Termini", "cryptic scenery - The Future was Japanese", "Ketsa - Holding The Line", "Maarten Schellekens - Salt Lake Swerve", "Strobotone - Dance Track") song_selections = [] playlist = [] def select_song(): playlist.clear() for var in song_selections: if var.get(): playlist.append(f"{var.song}.mp3") def play_song(song): pygame.mixer.music.load(f"{song}.mp3") pygame.mixer.music.play() root = tk.Tk() tk.Label(root, text="Welche tracks sollen zufällig gespielt werden (mit playlist)?", font=('arial', 12, 'bold', 'italic')) \ .grid(row=0, column=0, columnspan=2) for index, song in enumerate(songs): tk.Button(root, text="reinhören", command=lambda arg=song: play_song(arg)) \ .grid(row=index+1, column=0) var = tk.BooleanVar() var.song = song song_selections.append(var) tk.Checkbutton(root, text=song, variable=var, command=select_song) \ .grid(row=index+1, column=1)And I don't like this code much because of the hard coded play list. You should not use place() anywhere in a tkinter program. Tkinter has layout managers that do the widget layout for you. Use grid() or pack() instead. I used your playlist code to make a standalone music player that could be used to edit a playlist for your game. Making a standalone player makes it easier to understand and debug the code. Since I wrote it, it does not have a hardcoded song list, but instead gets the song titles from mp3 files in a music subfolder. import pygame, pathlib, random import tkinter as tk class MusicPlayer(tk.Tk): """ A simple music player that uses the pygame music mixer. I maintain a class "playlist" so I can be used to select music for a pygame. """ music_folder = pathlib.Path('.') / 'music' # Put mp3 files in subfolder named "music" playlist = [] # Class variable so can be accessed after player shuts down. def __init__(self, *args, **kvargs): super().__init__(*args, **kvargs) self.title("Music") tk.Label( self, text="Music to game by", font=('arial', 12, 'bold', 'italic')).grid(row=0, column=0, columnspan=2) # Make controls for constructing playlist and playing songs self.song_selections = [] for index, song in enumerate(self.music_folder.glob("*.mp3")): # Get all mp3 files in music folder # Checkboxes are used to select songs for playlist songtitle = pathlib.Path(song).name[:-4] # Use filename sans extension var = tk.BooleanVar() var.song = song # Adding song file as attribute to var self.song_selections.append(var) tk.Checkbutton(self, text=songtitle, variable=var, command=self.update_playlist) \ .grid(row=index+1, column=0, sticky="w") # Button is used to immediately play song tk.Button(self, text="Play", command=lambda arg=song: self.play_song(arg)) \ .grid(row=index+1, column=1, padx=5, pady=2) # Button to play the playlist tk.Button(self, text="Play Selected", command=self.play_playlist) \ .grid(row=index+1, column=0, columnspan=2, sticky="ew") def update_playlist(self): """Update the playlist to contain selected songs""" self.playlist.clear() for var in self.song_selections: if var.get(): self.playlist.append(var.song) def play_playlist(self): """Play songs in playlist""" pygame.mixer.music.stop() if len(self.playlist) > 0: songs = random.sample(self.playlist, k=len(self.playlist)) pygame.mixer.music.load(songs[0]) for song in songs[1:]: pygame.mixer.music.queue(song) pygame.mixer.music.play() @staticmethod def play_song(song): """Play song now""" pygame.mixer.music.load(song) pygame.mixer.music.play() pygame.init() pygame.mixer.init() pygame.mixer.music.set_volume(0.7) MusicPlayer().mainloop() print("This is the playlist:", MusicPlayer.playlist) # Get the playlist after player shuts down RE: background music in the gameloop - flash77 - Mar-24-2022 Dear deanhystad, I'm very sorry for causing annoyance... One of my friends is hindered and she can better deal with a hardcoded playlist - that's the reason for the hard coded playlist. In main() (in the game loop) I added some code for randomly playing the playlist. I also added "return playlist" in line 57. I noticed that the ball is bouncing inside the paddle, not on the edge. But I'm thinking that it is to complicated to find the reason (and I don't want to annoy). Now I think the gamplay is finished.... Thanks for your patience and effort! I followed your instructions and the code now looks like: import random, math, pygame import tkinter as tk from PIL import Image from itertools import product from random import sample pygame.init() pygame.mixer.init() pygame.mixer.music.set_volume(0.7) # Define colors used by the game. TEXT_COLOR = (255, 255, 255) FOREGROUND = (0, 0, 0) # Recolor image pixels that are this color TRANSPARENT = (255, 255, 255) # Make image pixels this color transparent BALL_COLOR = (255, 255, 255) PADDLE_COLOR = (255, 255, 255) BRICK_COLORS = ((255, 0, 0), (255, 50, 0), (255, 100, 0), (255, 150, 0), (255, 200, 0), (255, 255, 0)) BRICK_COORDS = [ (32, 32), (64, 32), (96, 32), (160, 32), (288, 32), (320, 32), (352, 32), (416, 32), (448, 32), (480, 32), (576, 32), (608, 32), (640, 32), (32, 64), (160, 64), (288, 64), (352, 64), (416, 64), (480, 64), (576, 64), (640, 64), (32, 96), (160, 96), (288, 96), (352, 96), (416, 96), (480, 96), (576, 96), (640, 96), (32, 128), (64, 128), (96, 128), (160, 128), (288, 128), (352, 128), (416, 128), (448, 128), (480, 128), (576, 128), (608, 128), (640, 128), (32, 160), (160, 160), (288, 160), (352, 160), (416, 160), (448, 160), (576, 160), (640, 160), (32, 192), (160, 192), (288, 192), (352, 192), (416, 192), (480, 192), (576, 192), (640, 192), (32, 224), (160, 224), (192, 224), (224, 224), (288, 224), (320, 224), (352, 224), (416, 224), (512, 224), (576, 224), (640, 224)] # Define some image files. These are not your files BALL_IMAGE = "ball.png" PADDLE_IMAGE = "paddle.png" BRICK_FILES = (("brick0.png", "brick1.png", "brick2.png"), ("bbrick0.png", "bbrick1.png", "bbrick2.png")) BACKGROUND_IMAGE = pygame.image.load("Hintergrund.png") SCREEN_WIDTH = 800 SCREEN_HEIGHT = 578 SCORE_POSITION = (SCREEN_WIDTH - 150, SCREEN_HEIGHT - 30) songs = ( "Adeline Yeo (HP) - Kite Fly High", "Nul Tiel Records - Fireflies", "cryptic scenery - Endzeit Endlos", "cryptic scenery - Helix Spire", "cryptic scenery - Minsk Metro", "cryptic scenery - Stazione Termini", "cryptic scenery - The Future was Japanese", "Ketsa - Holding The Line", "Maarten Schellekens - Salt Lake Swerve", "Strobotone - Dance Track") song_selections = [] playlist = [] def select_song(): playlist.clear() for var in song_selections: if var.get(): playlist.append(f"{var.song}.mp3") return playlist def play_song(song): pygame.mixer.music.load(f"{song}.mp3") pygame.mixer.music.play() root = tk.Tk() root.geometry('500x400') tk.Label(root, text="Welche tracks sollen zufällig gespielt werden?", font=('arial', 12, 'bold', 'italic')) \ .grid(row=0, column=0, columnspan=2) for index, song in enumerate(songs): tk.Button(root, text="reinhören", command=lambda arg=song: play_song(arg)) \ .grid(row=index + 1, column=0) var = tk.BooleanVar() var.song = song song_selections.append(var) tk.Checkbutton(root, text=song, variable=var, command=select_song) \ .grid(row=index + 1, column=1) root.mainloop() def create_image(file, color=None): """ Create image from a file. If color is specified, replace all FOREGROUND pixels with color pixels. Modify image so TRANSPARENT colored pixels are transparent. """ if color: # Recolor the image image = Image.open(file).convert("RGB") for xy in product(range(image.width), range(image.height)): if image.getpixel(xy) == FOREGROUND: image.putpixel(xy, color) image = pygame.image.fromstring(image.tobytes(), image.size, "RGB") else: image = pygame.image.load(file) image.set_colorkey(TRANSPARENT) return image.convert() class EnhancedSprite(pygame.sprite.Sprite): """ Sprite with image and rectangle. I expose some of my rectangle's properties. """ def __init__(self, image, group=None, **kwargs): super().__init__(**kwargs) self.image = image self.rect = image.get_rect() if group is not None: group.add(self) def at(self, x, y): """Convenience method for setting my position""" self.x = x self.y = y return self # Properties below expose properties of my rectangle so you can use # self.x = 10 or self.centery = 30 instead of self.rect.x = 10 @property def x(self): return self.rect.x @x.setter def x(self, value): self.rect.x = value @property def y(self): return self.rect.y @y.setter def y(self, value): self.rect.y = value @property def centerx(self): return self.rect.centerx @centerx.setter def centerx(self, value): self.rect.centerx = value @property def centery(self): return self.rect.centery @centery.setter def centery(self, value): self.rect.centery = value @property def right(self): return self.rect.right @right.setter def right(self, value): self.rect.right = value @property def bottom(self): return self.rect.bottom @bottom.setter def bottom(self, value): self.rect.bottom = value class Brick(EnhancedSprite): """ A target for the ball. After I take some number of hits I die. Number of hits I can take is in range 1 to 3. Hits is randomly selected if not specified. Specify brick color using (R, G, B) format. If color not specified a color is selected based on the row. """ group = pygame.sprite.Group() def __init__(self, x, y, image_files=None, color=None, hits=None): color = color or random.choice(BRICK_COLORS) hits = hits or random.choice((1, 1, 1, 2, 2, 3)) self.value = self.hits = max(1, min(3, hits)) image_files = image_files or random.choice(BRICK_FILES) self.images = [create_image(file, color) for file in image_files] super().__init__(self.images[self.hits - 1], self.group) self.at(x, y) def __len__(self): """Return how many bricks remaining""" return len(self.group) def hit(self, score): """ I was hit! Update my appearance or die based on my hit total. Return my value if I was killed. """ self.hits -= 1 if self.hits > 0: self.image = self.images[self.hits - 1] return 0 self.kill() return self.value class Paddle(EnhancedSprite): """The sprite the player moves around to redirect the ball""" group = pygame.sprite.Group() def __init__(self, bottom): super().__init__(create_image(PADDLE_IMAGE, PADDLE_COLOR), self.group) self.bottom = bottom self.xmin = self.rect.width // 2 # Compute paddle x range. self.xmax = SCREEN_WIDTH - self.xmin def move(self, x): """Move to follow the cursor. Clamp to window bounds""" self.centerx = max(self.xmin, min(self.xmax, x)) class LifeCounter(): """Keep track of lives count. Display lives remaining using ball image""" def __init__(self, x, y, count=5): self.x, self.y = x, y self.image = create_image(BALL_IMAGE, BALL_COLOR) self.spacing = self.image.get_width() + 5 self.group = pygame.sprite.Group() self.reset(count) def reset(self, count): """Reset number of lives""" self.count = count for c in range(count - 1): EnhancedSprite(self.image, self.group).at(self.x + c * self.spacing, self.y) def __len__(self): """Return number of lives remaining""" return self.count def kill(self): """Reduce number of lives""" if self.count > 1: self.group.sprites()[-1].kill() self.count = max(0, self.count - 1) class Ball(EnhancedSprite): """Ball bounces around colliding with walls, paddles and bricks""" group = pygame.sprite.Group() def __init__(self, paddle, lives, speed=5): super().__init__(create_image(BALL_IMAGE, BALL_COLOR), self.group) self.paddle = paddle self.lives = lives self.speed = speed self.dx = self.dy = 0 self.xmax = SCREEN_WIDTH - self.rect.width self.ymax = paddle.bottom - self.rect.height self.reset(0) def reset(self, score=None): """Reset for a new game""" self.active = False if score is not None: self.score = score def start(self): """Start moving the ball in a random direction""" angle = random.random() - 0.5 # Launch angle limited to about +/-60 degrees self.dx = int(self.speed * math.sin(angle)) self.dy = -int(self.speed * math.cos(angle)) self.active = True def move(self): """Update the ball position. Check for collisions with bricks, walls and the paddle""" if not self.active: # Sit on top of the paddle self.centerx = self.paddle.centerx self.bottom = self.paddle.y - 2 return self # Did I hit some bricks? Update the bricks and the score x1, y1 = self.x, self.y x2, y2 = x1 + self.dx, y1 + self.dy if (xhits := pygame.sprite.spritecollide(self.at(x2, y1), Brick.group, False)): self.dx = -self.dx if (yhits := pygame.sprite.spritecollide(self.at(x1, y2), Brick.group, False)): self.dy = -self.dy if (hits := set(xhits) or set(yhits)): for brick in hits: self.score += brick.hit(self.score) # Did I hit a wall? if x2 <= 0 or x2 >= self.xmax: self.dx = -self.dx hits = True if y2 <= 0: self.dy = abs(self.dy) hits = True # Did I hit or get past the paddle? if y2 >= self.paddle.y: # Did it get past the paddle? if self.x > self.paddle.right or self.right < self.paddle.x: self.lives.kill() self.active = False else: # I hit the paddle. Compute angle of reflection bangle = math.atan2(-self.dx, self.dy) # Ball angle of approach pangle = math.atan2(self.centerx - self.paddle.centerx, 30) # Paddle angle rangle = (pangle - bangle) / 2 # Angle of reflection self.dx = math.sin(rangle) * self.speed self.dy = -math.cos(rangle) * self.speed hits = True if hits: self.at(x1, y1) else: self.at(x2, y2) def main(): """Play game until out of lives or out of bricks""" def displayText(text, font, pos=None, color=TEXT_COLOR): text = font.render(text, 1, color) if pos is None: pos = ((SCREEN_WIDTH - text.get_width()) // 2, (SCREEN_HEIGHT - text.get_height()) // 2) screen.blit(text, pos) screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("Breakout") clock = pygame.time.Clock() allsprites = pygame.sprite.Group() score_font = pygame.font.Font(None, 34) try: # to stop the music if he has listened shortly at the tkinter form pygame.mixer.music.stop() playlist = select_song() track = sample(playlist, 1) level = 1 lives = LifeCounter(10, SCREEN_HEIGHT - 30) paddle = Paddle(bottom=SCREEN_HEIGHT - 40) ball = Ball(paddle, lives) allsprites.add(paddle.group, lives.group, ball.group) while len(lives) > 0: # Start new board. Could have different layouts for each level for coord in BRICK_COORDS: Brick(*coord) allsprites.add(Brick.group) # Play until out of bricks or lives while len(lives) > 0 and len(Brick.group): clock.tick(60) pygame.mixer.music.load(track[0]) pygame.mixer.music.play() pygame.mixer.music.set_endevent(pygame.USEREVENT) music_running = True while music_running: for event in pygame.event.get(): if event.type == pygame.QUIT: raise SystemExit elif event.type == pygame.MOUSEMOTION: paddle.move(event.pos[0]) elif event.type == pygame.MOUSEBUTTONUP: if not ball.active: ball.start() elif event.type == pygame.USEREVENT: track = random.sample(playlist, 1) if not pygame.mixer.music.get_busy(): music_running = False break ball.move() screen.blit(BACKGROUND_IMAGE, (0, 0)) displayText(f"Score: {ball.score}", font=score_font, pos=SCORE_POSITION) allsprites.draw(screen) pygame.display.flip() # Display results if len(lives) == 0: displayText("Game over", font=pygame.font.Font(None, 74)) elif len(Brick.group) == 0: level += 1 displayText(f"Level {level}", font=pygame.font.Font(None, 74)) ball.speed *= 1.25 ball.reset(ball.score) pygame.display.flip() pygame.time.wait(3000) finally: pygame.quit() if __name__ == "__main__": main() RE: background music in the gameloop - deanhystad - Mar-25-2022 I don't think I explained the file search very well. I will try to do a better job. You have the mp3 files in the same folder as the game (I think there should be a subfolder for all the image and mp3 files). All you need to do is replace the hardcoded song list with a one line file search for mp3 files. Results will be the same except the order, and order doesn't matter because you randomize the playback. Players don't have to do anything special and will not notice any difference. However, players now have the option of replacing music files if they want. I am listening to "Dark Side of the Moon" while playing. I took the standalone music player and modified it for use by the breakout game. Only required a few small changes. If I can't talk you into using a subfolder for all the game related files you just need to remove the "/ 'music'" from line 9. This makes the music player look for mp3 files in the current folder. import pygame, pathlib, random import tkinter as tk class MusicPlayer(tk.Tk): """ A simple music player that uses the pygame music mixer. I maintain a class "playlist" so I can be used to select music for a pygame. """ music_folder = pathlib.Path('.') / 'music' # Put mp3 files in subfolder named "music" playlist = [] # Class variable so can be accessed after player shuts down. def __init__(self, *args, title="Music Player", playmsg=None, **kvargs): super().__init__(*args, **kvargs) self.title("music") tk.Label( self, text=title, font=('arial', 12, 'bold', 'italic')).grid(row=0, column=0, columnspan=2) # Make controls for constructing playlist and playing songs self.song_selections = [] for index, song in enumerate(self.music_folder.glob("*.mp3")): # Checkboxes are used to select songs for playlist songtitle = pathlib.Path(song).name[:-4] var = tk.BooleanVar() var.song = song self.song_selections.append(var) tk.Checkbutton(self, text=songtitle, variable=var, command=self.update_playlist) \ .grid(row=index+1, column=0, sticky="w") # Button is used to immediately play song tk.Button(self, text="Play", command=lambda arg=song: self.play_song(arg)) \ .grid(row=index+1, column=1, padx=5, pady=2) # Play the playlist if playmsg is None: tk.Button(self, text="Play Selected", command=self.play_playlist) \ .grid(row=index+1, column=0, columnspan=2, sticky="ew") else: tk.Button(self, text=playmsg, command=self.destroy) \ .grid(row=index+1, column=0, columnspan=2, sticky="ew") def update_playlist(self): """Update the playlist to contain selected songs""" self.playlist.clear() for var in self.song_selections: if var.get(): self.playlist.append(var.song) def play_playlist(self): """Play songs in playlist""" pygame.mixer.music.stop() if len(self.playlist) > 0: songs = random.sample(self.playlist, k=len(self.playlist)) pygame.mixer.music.load(songs[0]) for song in songs[1:]: pygame.mixer.music.queue(song) pygame.mixer.music.play() @staticmethod def play_song(song): """Play song now""" pygame.mixer.music.load(song) pygame.mixer.music.play() if __name__ == "__main__": pygame.init() pygame.mixer.init() pygame.mixer.music.set_volume(0.7) MusicPlayer().mainloop()Then I modified your breakout game to import the module and use it to get a playlist. import random, math, pygame import tkinter as tk from musicplayer import MusicPlayer from PIL import Image from itertools import product # Define colors used by the game. TEXT_COLOR = (255, 255, 255) FOREGROUND = (0, 0, 0) # Recolor image pixels that are this color TRANSPARENT = (255, 255, 255) # Make image pixels this color transparent BALL_COLOR = (255, 255, 255) PADDLE_COLOR = (255, 255, 255) BRICK_COLORS = ((255, 0, 0), (255, 50, 0), (255, 100, 0), (255, 150, 0), (255, 200, 0), (255, 255, 0)) BRICK_COORDS = [ (32, 32), (64, 32), (96, 32), (160, 32), (288, 32), (320, 32), (352, 32), (416, 32), (448, 32), (480, 32), (576, 32), (608, 32), (640, 32), (32, 64), (160, 64), (288, 64), (352, 64), (416, 64), (480, 64), (576, 64), (640, 64), (32, 96), (160, 96), (288, 96), (352, 96), (416, 96), (480, 96), (576, 96), (640, 96), (32, 128), (64, 128), (96, 128), (160, 128), (288, 128), (352, 128), (416, 128), (448, 128), (480, 128), (576, 128), (608, 128), (640, 128), (32, 160), (160, 160), (288, 160), (352, 160), (416, 160), (448, 160), (576, 160), (640, 160), (32, 192), (160, 192), (288, 192), (352, 192), (416, 192), (480, 192), (576, 192), (640, 192), (32, 224), (160, 224), (192, 224), (224, 224), (288, 224), (320, 224), (352, 224), (416, 224), (512, 224), (576, 224), (640, 224)] # Define some image files BALL_IMAGE = "ball.png" PADDLE_IMAGE = "paddle.png" BRICK_FILES = (("brick0.png", "brick1.png", "brick2.png"), ("bbrick0.png", "bbrick1.png", "bbrick2.png")) BACKGROUND_FILE = "Hintergrund.png" SCREEN_WIDTH = 800 SCREEN_HEIGHT = 578 SCORE_POSITION = (SCREEN_WIDTH - 150, SCREEN_HEIGHT - 30) def create_image(file, color=None): """ Create image from a file. If color is specified, replace all FOREGROUND pixels with color pixels. Modify image so TRANSPARENT colored pixels are transparent. """ if color: # Recolor the image image = Image.open(file).convert("RGB") for xy in product(range(image.width), range(image.height)): if image.getpixel(xy) == FOREGROUND: image.putpixel(xy, color) image = pygame.image.fromstring(image.tobytes(), image.size, "RGB") else: image = pygame.image.load(file) image.set_colorkey(TRANSPARENT) return image.convert() class EnhancedSprite(pygame.sprite.Sprite): """ Sprite with image and rectangle. I expose some of my rectangle's properties. """ def __init__(self, image, group=None, **kwargs): super().__init__(**kwargs) self.image = image self.rect = image.get_rect() if group is not None: group.add(self) def at(self, x, y): """Convenience method for setting my position""" self.x = x self.y = y return self # Properties below expose properties of my rectangle so you can use # self.x = 10 or self.centery = 30 instead of self.rect.x = 10 @property def x(self): return self.rect.x @x.setter def x(self, value): self.rect.x = value @property def y(self): return self.rect.y @y.setter def y(self, value): self.rect.y = value @property def centerx(self): return self.rect.centerx @centerx.setter def centerx(self, value): self.rect.centerx = value @property def centery(self): return self.rect.centery @centery.setter def centery(self, value): self.rect.centery = value @property def right(self): return self.rect.right @right.setter def right(self, value): self.rect.right = value @property def bottom(self): return self.rect.bottom @bottom.setter def bottom(self, value): self.rect.bottom = value class Brick(EnhancedSprite): """ A target for the ball. After I take some number of hits I die. Number of hits I can take is in range 1 to 3. Hits is randomly selected if not specified. Specify brick color using (R, G, B) format. If color not specified a color is selected based on the row. """ group = pygame.sprite.Group() def __init__(self, x, y, image_files=None, color=None, hits=None): color = color or random.choice(BRICK_COLORS) hits = hits or random.choice((1, 1, 1, 2, 2, 3)) self.value = self.hits = max(1, min(3, hits)) image_files = image_files or random.choice(BRICK_FILES) self.images = [create_image(file, color) for file in image_files] super().__init__(self.images[self.hits - 1], self.group) self.at(x, y) def __len__(self): """Return how many bricks remaining""" return len(self.group) def hit(self, score): """ I was hit! Update my appearance or die based on my hit total. Return my value if I was killed. """ self.hits -= 1 if self.hits > 0: self.image = self.images[self.hits - 1] return 0 self.kill() return self.value class Paddle(EnhancedSprite): """The sprite the player moves around to redirect the ball""" group = pygame.sprite.Group() def __init__(self, bottom): super().__init__(create_image(PADDLE_IMAGE, PADDLE_COLOR), self.group) self.bottom = bottom self.xmin = self.rect.width // 2 # Compute paddle x range. self.xmax = SCREEN_WIDTH - self.xmin def move(self, x): """Move to follow the cursor. Clamp to window bounds""" self.centerx = max(self.xmin, min(self.xmax, x)) class LifeCounter(): """Keep track of lives count. Display lives remaining using ball image""" def __init__(self, x, y, count=5): self.x, self.y = x, y self.image = create_image(BALL_IMAGE, BALL_COLOR) self.spacing = self.image.get_width() + 5 self.group = pygame.sprite.Group() self.reset(count) def reset(self, count): """Reset number of lives""" self.count = count for c in range(count - 1): EnhancedSprite(self.image, self.group).at(self.x + c * self.spacing, self.y) def __len__(self): """Return number of lives remaining""" return self.count def kill(self): """Reduce number of lives""" if self.count > 1: self.group.sprites()[-1].kill() self.count = max(0, self.count - 1) class Ball(EnhancedSprite): """Ball bounces around colliding with walls, paddles and bricks""" group = pygame.sprite.Group() def __init__(self, paddle, lives, speed=5): super().__init__(create_image(BALL_IMAGE, BALL_COLOR), self.group) self.paddle = paddle self.lives = lives self.speed = speed self.dx = self.dy = 0 self.xmax = SCREEN_WIDTH - self.rect.width self.ymax = paddle.bottom - self.rect.height self.reset(0) def reset(self, score=None): """Reset for a new game""" self.active = False if score is not None: self.score = score def start(self): """Start moving the ball in a random direction""" angle = random.random() - 0.5 # Launch angle limited to about +/-60 degrees self.dx = int(self.speed * math.sin(angle)) self.dy = -int(self.speed * math.cos(angle)) self.active = True def move(self): """Update the ball position. Check for collisions with bricks, walls and the paddle""" if not self.active: # Sit on top of the paddle self.centerx = self.paddle.centerx self.bottom = self.paddle.y - 2 return self # Did I hit some bricks? Update the bricks and the score x1, y1 = self.x, self.y x2, y2 = x1 + self.dx, y1 + self.dy if (xhits := pygame.sprite.spritecollide(self.at(x2, y1), Brick.group, False)): self.dx = -self.dx if (yhits := pygame.sprite.spritecollide(self.at(x1, y2), Brick.group, False)): self.dy = -self.dy if (hits := set(xhits) or set(yhits)): for brick in hits: self.score += brick.hit(self.score) # Did I hit a wall? if x2 <= 0 or x2 >= self.xmax: self.dx = -self.dx hits = True if y2 <= 0: self.dy = abs(self.dy) hits = True # Did I hit or get past the paddle? if y2 >= self.paddle.y: # Did it get past the paddle? if self.x > self.paddle.right or self.right < self.paddle.x: self.lives.kill() self.active = False else: # I hit the paddle. Compute angle of reflection bangle = math.atan2(-self.dx, self.dy) # Ball angle of approach pangle = math.atan2(self.centerx - self.paddle.centerx, 30) # Paddle angle rangle = (pangle - bangle) / 2 # Angle of reflection self.dx = math.sin(rangle) * self.speed self.dy = -math.cos(rangle) * self.speed hits = True if hits: self.at(x1, y1) else: self.at(x2, y2) def main(playlist): def play_music(): if playlist and len(playlist) > 0: pygame.mixer.music.load(random.choice(playlist)) pygame.mixer.music.play() """Play game until out of lives or out of bricks""" def displayText(text, font, pos=None, color=TEXT_COLOR): text = font.render(text, 1, color) if pos is None: pos = ((SCREEN_WIDTH - text.get_width()) // 2, (SCREEN_HEIGHT - text.get_height()) // 2) screen.blit(text, pos) screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("Breakout") clock = pygame.time.Clock() allsprites = pygame.sprite.Group() score_font = pygame.font.Font(None, 34) background = pygame.image.load(BACKGROUND_FILE) try: level = 1 lives = LifeCounter(10, SCREEN_HEIGHT - 30) paddle = Paddle(bottom=SCREEN_HEIGHT - 40) ball = Ball(paddle, lives) allsprites.add(paddle.group, lives.group, ball.group) while len(lives) > 0: # Start new board. Could have different layouts for each level for coord in BRICK_COORDS: Brick(*coord) allsprites.add(Brick.group) play_music() pygame.mixer.music.set_endevent(pygame.USEREVENT) # Play until out of bricks or lives while len(lives) > 0 and len(Brick.group): clock.tick(60) for event in pygame.event.get(): if event.type == pygame.QUIT: raise SystemExit elif event.type == pygame.MOUSEMOTION: paddle.move(event.pos[0]) elif event.type == pygame.MOUSEBUTTONUP: if not ball.active: ball.start() elif event.type == pygame.USEREVENT: if not pygame.mixer.music.get_busy(): play_music() ball.move() screen.fill((0, 0, 180)) # screen.blit(background, (0, 0)) # I don't have a background image displayText(f"Score: {ball.score}", font=score_font, pos=SCORE_POSITION) allsprites.draw(screen) pygame.display.flip() # Display results if len(lives) == 0: displayText("Game over", font=pygame.font.Font(None, 74)) elif len(Brick.group) == 0: level += 1 displayText(f"Level {level}", font=pygame.font.Font(None, 74)) ball.speed *= 1.25 ball.reset(ball.score) pygame.display.flip() pygame.time.wait(3000) finally: pygame.quit() if __name__ == "__main__": pygame.init() pygame.mixer.init() pygame.mixer.music.set_volume(0.7) MusicPlayer(title="Select Music", playmsg="Play Breakout").mainloop() main(MusicPlayer.playlist)My image files aren't right for your game. I think my bricks are too big and the ball and paddle don't show up because I don't have your background file and my ball and paddle end up being black on black. I commented out the line that draws the background image and replaced it with one that fills the screen with blue. I also removed the music loop and instead wait for events from the music mixer. You were kind of doing this, but not quite. The event works pretty well for me. RE: background music in the gameloop - flash77 - Mar-25-2022 Dear deanhystad, thanks a lot for the excellent work you did!! I moved the music in the music subfolder and implemented the MusicPlayer class... I've got a little last question concerning the game: Do you have an idea why the ball is bouncing inside the paddle (and not on the edge)? I don't want to cause inconvenience again, because you made a great many of effort for the game and I don't want to overtax your helpfulness... If fixing it demands to much work, just communicate it to me - and I can deal with it... Please don't put to much effort in it, because the game works perfectly now... Without your help I couldn't get the game this perfect own my own!! Thanks very much for your patience and your great helpfulness... RE: background music in the gameloop - deanhystad - Mar-25-2022 The ball is bouncing on the bottom of the paddle because Ball.ymax = bottom of paddle - height of ball. The ball does not check for collisions until the bottom of the ball reaches the bottom of the paddle. I was just about to post some new code that addresses this very problem and another that I had noticed for a while but never really looked at until I could play breakout while listening to Jethro Tull. Paddle collisions: I've tried a lot of things with paddle/ball collisions but I think I finally have it. See Ball.move() in the code below for details, but the cliff notes version is: Check if the ball got past the paddle. If not, check for ball/paddle collision only if ball is moving down (positive dy). The other thing I noticed had to do with horizontal ball movement. Far too often there is none. Initially I was converting ball dx and dy to ints and I figured the vertical ball motion was from clipping all -1 < dx < 1 to zero. I also noticed there didn't seem to be a lot of variety in the angles either. Ball movement was experiencing "quantization". With ball speed set to 5, the ball angle off the paddle was limited to 9 possible values. I changed dx and dy to be floats, but this changed nothing. Turned out the problem was not only with dx and dy being ints, but with x and y being ints also. If I ask Pygame to move the ball to 5.4, 14.8 it moves the ball to 5, 14. All coordinates are converted to int, and any decimal information is lost. A ball angle near vertical can have a dx in the range -1 < dx < 1. Because of the integer clipping any dx in this range is effectively zero. If you ask Pygame to move less than a pixel it doesn't move at all. The solution is to have float x and y coordinates. Pygame won't do that, so I added float coordinates to the Ball class. Now when I tell the ball to go to 5.4, 14.8 it still moves the sprite to 5, 14, but it remembers the decimal part of the coordinates. The next time I move the ball the reported ball position is 5.4, 14.8. Now the ball can move at sub-pixel "speeds". import random, math, pygame import tkinter as tk from musicplayer import MusicPlayer from PIL import Image from itertools import product # Define colors used by the game. TEXT_COLOR = (255, 255, 255) FOREGROUND = (0, 0, 0) # Recolor image pixels that are this color TRANSPARENT = (255, 255, 255) # Make image pixels this color transparent BALL_COLOR = (255, 255, 255) PADDLE_COLOR = (255, 255, 255) BRICK_COLORS = ((255, 0, 0), (255, 50, 0), (255, 100, 0), (255, 150, 0), (255, 200, 0), (255, 255, 0)) BRICK_COORDS = [ (32, 32), (64, 32), (96, 32), (160, 32), (288, 32), (320, 32), (352, 32), (416, 32), (448, 32), (480, 32), (576, 32), (608, 32), (640, 32), (32, 64), (160, 64), (288, 64), (352, 64), (416, 64), (480, 64), (576, 64), (640, 64), (32, 96), (160, 96), (288, 96), (352, 96), (416, 96), (480, 96), (576, 96), (640, 96), (32, 128), (64, 128), (96, 128), (160, 128), (288, 128), (352, 128), (416, 128), (448, 128), (480, 128), (576, 128), (608, 128), (640, 128), (32, 160), (160, 160), (288, 160), (352, 160), (416, 160), (448, 160), (576, 160), (640, 160), (32, 192), (160, 192), (288, 192), (352, 192), (416, 192), (480, 192), (576, 192), (640, 192), (32, 224), (160, 224), (192, 224), (224, 224), (288, 224), (320, 224), (352, 224), (416, 224), (512, 224), (576, 224), (640, 224)] # Define some image files. These are not your files BALL_IMAGE = "ball.png" PADDLE_IMAGE = "paddle.png" BRICK_FILES = (("brick0.png", "brick1.png", "brick2.png"), ("bbrick0.png", "bbrick1.png", "bbrick2.png")) BACKGROUND_FILE = "Hintergrund.png" SCREEN_WIDTH = 800 SCREEN_HEIGHT = 578 SCORE_POSITION = (SCREEN_WIDTH - 150, SCREEN_HEIGHT - 30) def create_image(file, color=None): """ Create image from a file. If color is specified, replace all FOREGROUND pixels with color pixels. Modify image so TRANSPARENT colored pixels are transparent. """ if color: # Recolor the image image = Image.open(file).convert("RGB") for xy in product(range(image.width), range(image.height)): if image.getpixel(xy) == FOREGROUND: image.putpixel(xy, color) image = pygame.image.fromstring(image.tobytes(), image.size, "RGB") else: image = pygame.image.load(file) image.set_colorkey(TRANSPARENT) return image.convert() class EnhancedSprite(pygame.sprite.Sprite): """ Sprite with image and rectangle. I expose some of my rectangle's properties. """ def __init__(self, image, group=None, **kwargs): super().__init__(**kwargs) self.image = image self.rect = image.get_rect() if group is not None: group.add(self) def at(self, x, y): """Convenience method for setting my position""" self.x = x self.y = y return self # Properties below expose properties of my rectangle so you can use # self.x = 10 or self.centery = 30 instead of self.rect.x = 10 @property def x(self): return self.rect.x @x.setter def x(self, value): self.rect.x = value @property def y(self): return self.rect.y @y.setter def y(self, value): self.rect.y = value @property def centerx(self): return self.rect.centerx @centerx.setter def centerx(self, value): self.rect.centerx = value @property def centery(self): return self.rect.centery @centery.setter def centery(self, value): self.rect.centery = value @property def right(self): return self.rect.right @right.setter def right(self, value): self.rect.right = value @property def bottom(self): return self.rect.bottom @bottom.setter def bottom(self, value): self.rect.bottom = value @property def width(self): return self.rect.width @property def height(self): return self.rect.height class Brick(EnhancedSprite): """ A target for the ball. After I take some number of hits I die. Number of hits I can take is in range 1 to 3. Hits is randomly selected if not specified. Specify brick color using (R, G, B) format. If color not specified a color is selected based on the row. """ group = pygame.sprite.Group() def __init__(self, x, y, image_files=None, color=None, hits=None): color = color or random.choice(BRICK_COLORS) hits = hits or random.choice((1, 1, 1, 2, 2, 3)) self.value = self.hits = max(1, min(3, hits)) image_files = image_files or random.choice(BRICK_FILES) self.images = [create_image(file, color) for file in image_files] super().__init__(self.images[self.hits - 1], self.group) self.at(x, y) def __len__(self): """Return how many bricks remaining""" return len(self.group) def hit(self, score): """ I was hit! Update my appearance or die based on my hit total. Return my value if I was killed. """ self.hits -= 1 if self.hits > 0: self.image = self.images[self.hits - 1] return 0 self.kill() return self.value class Paddle(EnhancedSprite): """The sprite the player moves around to redirect the ball""" group = pygame.sprite.Group() def __init__(self, bottom): super().__init__(create_image(PADDLE_IMAGE, PADDLE_COLOR), self.group) self.bottom = bottom self.xmin = self.rect.width // 2 # Compute paddle x range. self.xmax = SCREEN_WIDTH - self.xmin def move(self, x): """Move to follow the cursor. Clamp to window bounds""" self.centerx = max(self.xmin, min(self.xmax, x)) class LifeCounter(): """Keep track of lives count. Display lives remaining using ball image""" def __init__(self, x, y, count=5): self.x, self.y = x, y self.image = create_image(BALL_IMAGE, BALL_COLOR) self.spacing = self.image.get_width() + 5 self.group = pygame.sprite.Group() self.reset(count) def reset(self, count): """Reset number of lives""" self.count = count for c in range(count - 1): EnhancedSprite(self.image, self.group).at(self.x + c * self.spacing, self.y) def __len__(self): """Return number of lives remaining""" return self.count def kill(self): """Reduce number of lives""" if self.count > 1: self.group.sprites()[-1].kill() self.count = max(0, self.count - 1) class Ball(EnhancedSprite): """Ball bounces around colliding with walls, paddles and bricks""" group = pygame.sprite.Group() def __init__(self, paddle, lives, speed=5): super().__init__(create_image(BALL_IMAGE, BALL_COLOR), self.group) self.paddle = paddle self.lives = lives self.speed = speed self.dx = self.dy = 0 self.xfloat = self.yfloat = 0 self.xmax = SCREEN_WIDTH - self.rect.width self.ymax = paddle.bottom - self.rect.height self.reset(0) def at(self, x, y): self.xfloat = x self.yfloat = y return super().at(x, y) def reset(self, score=None): """Reset for a new game""" self.active = False if score is not None: self.score = score def start(self): """Start moving the ball in a random direction""" angle = random.random() - 0.5 # Launch angle limited to about +/-60 degrees self.dx = self.speed * math.sin(angle) self.dy = -self.speed * math.cos(angle) self.active = True def move(self): """Update the ball position. Check for collisions with bricks, walls and the paddle""" hit_status = 0 if not self.active: # Sit on top of the paddle self.at(self.paddle.centerx-self.width//2, self.paddle.y-self.height-2) return self # Did I hit some bricks? Update the bricks and the score x1, y1 = self.xfloat, self.yfloat x2, y2 = x1 + self.dx, y1 + self.dy if (xhits := pygame.sprite.spritecollide(self.at(x2, y1), Brick.group, False)): self.dx = -self.dx hit_status += 1 if (yhits := pygame.sprite.spritecollide(self.at(x1, y2), Brick.group, False)): self.dy = -self.dy hit_status += 2 if (hits := set(xhits) or set(yhits)): for brick in hits: self.score += brick.hit(self.score) # Did I hit a wall? if x2 <= 0 or x2 >= self.xmax: self.dx = -self.dx hit_status += 4 if y2 <= 0: self.dy = abs(self.dy) hit_status += 8 # Did I get past the paddle? if (y2 >= self.paddle.y) and ((self.x > self.paddle.right) or (self.right < self.paddle.x)): self.lives.kill() self.active = False elif self.dy > 0 and pygame.Rect.colliderect(self.at(x2, y2).rect, self.paddle.rect): # I hit the paddle. Compute angle of reflection bangle = math.atan2(-self.dx, self.dy) # Ball angle of approach pangle = math.atan2(self.centerx - self.paddle.centerx, 30) # Paddle angle rangle = (pangle - bangle) / 2 # Angle of reflection self.dx = math.sin(rangle) * self.speed self.dy = -math.cos(rangle) * self.speed hit_status += 16 if hit_status > 0: self.at(x1, y1) print(hit_status, self.dx, self.dy, x1, y1, x2, y2) else: self.at(x2, y2) def main(playlist): def play_music(): """Play song from playlist""" if playlist and len(playlist) > 0: pygame.mixer.music.load(random.choice(playlist)) pygame.mixer.music.play() def displayText(text, font, pos=None, color=TEXT_COLOR): """Draw text on screen""" text = font.render(text, 1, color) if pos is None: pos = ((SCREEN_WIDTH - text.get_width()) // 2, (SCREEN_HEIGHT - text.get_height()) // 2) screen.blit(text, pos) pygame.display.set_caption("Breakout") screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) clock = pygame.time.Clock() allsprites = pygame.sprite.Group() score_font = pygame.font.Font(None, 34) background = pygame.image.load(BACKGROUND_FILE) try: level = 1 lives = LifeCounter(10, SCREEN_HEIGHT - 30) paddle = Paddle(bottom=SCREEN_HEIGHT - 40) ball = Ball(paddle, lives) allsprites.add(paddle.group, lives.group, ball.group) while len(lives) > 0: # Start new board. Could have different brick layouts for each level for coord in BRICK_COORDS: Brick(*coord) allsprites.add(Brick.group) play_music() # Play until out of bricks or lives while len(lives) > 0 and len(Brick.group): clock.tick(60) for event in pygame.event.get(): if event.type == pygame.QUIT: raise SystemExit elif event.type == pygame.MOUSEMOTION: paddle.move(event.pos[0]) elif event.type == pygame.MOUSEBUTTONUP: if not ball.active: ball.start() elif event.type == pygame.USEREVENT: if not pygame.mixer.music.get_busy(): play_music() ball.move() screen.fill((0, 0, 180)) # Doing this because I don't have a background image # screen.blit(background, (0, 0)) displayText(f"Score: {ball.score}", font=score_font, pos=SCORE_POSITION) allsprites.draw(screen) pygame.display.flip() # Display results if len(lives) == 0: displayText("Game over", font=pygame.font.Font(None, 74)) elif len(Brick.group) == 0: level += 1 displayText(f"Level {level}", font=pygame.font.Font(None, 74)) ball.speed *= 1.25 ball.reset(ball.score) pygame.display.flip() pygame.time.wait(3000) finally: pygame.quit() if __name__ == "__main__": pygame.init() pygame.mixer.init() pygame.mixer.music.set_volume(0.7) pygame.mixer.music.set_endevent(pygame.USEREVENT) MusicPlayer(title="Select Music", playmsg="Play Breakout").mainloop() main(MusicPlayer.playlist) RE: background music in the gameloop - flash77 - Mar-26-2022 Dear deanhystad, like in my previous post I'm deeply grateful... Now everything is perfect!! Thanks for helping me complete this game for this long period!! flash77 |