Shmup - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: General (https://python-forum.io/forum-1.html) +--- Forum: Code sharing (https://python-forum.io/forum-5.html) +--- Thread: Shmup (/thread-32462.html) |
Shmup - menator01 - Feb-10-2021 Been a while since I've been here and posted anything so, here is a little game that I've been playing around with. Works on windows 10. I've not had a chance to modify to work on linux. You can download the files from github Shmup. Uses pygame 1.9.6 and tkinter Here is the coding. # Import needed modules import pygame import os import random as rnd import tkinter as tk from functools import partial try: import pygame.freetype as freetype except ImportError: print ("No FreeType support compiled") sys.exit () # Initialize pygame and the sound mixer pygame.init() if pygame.get_sdl_version()[0] == 2: pygame.mixer.pre_init(44100, 32, 2, 1024) pygame.mixer.init() # Screen and framerate setup width = 800 height = 600 fps = 60 screen = pygame.display.set_mode((width, height)) pygame.display.set_caption('Shoot \'m Up!') clock = pygame.time.Clock() # Load font ttf file fontdir = os.path.dirname(os.path.abspath (__file__)) # font = freetype.Font(os.path.join (fontdir, "fonts", "comicbd.ttf")) # Set some colors note: all are not used black = (0, 0, 0) white = (255, 255, 255) palewhite = (240, 240, 240) grayish = (195, 195, 190) tomato = (250, 80, 100) skyblue = (0, 180, 255) green = (0, 255, 0) red = (255, 0, 0) yellow = (255, 255, 0) orange = (250, 80, 0) purple = (255, 0, 255) peru = (205, 133, 63) limegreen = (50, 205, 50) yellowgreen = (154, 205, 50) # Set the path for the image, music and sound directories imgdir = os.path.join(os.path.dirname(__file__), 'images') snddir = os.path.join(os.path.dirname(__file__), 'sounds') musicdir = os.path.join(os.path.dirname(__file__), 'music') # Set the background image background = pygame.image.load(f'{imgdir}/backgrounds/space.png') background_rect = background.get_rect() # Load all images used by the player player_img = pygame.image.load(f'{imgdir}/ships/myship.png').convert() mini_img = pygame.transform.scale(player_img, (25, 25)) mini_img.set_colorkey(black) shipbmp = pygame.image.load(f'{imgdir}/icons/myship.bmp') icon = pygame.transform.scale(shipbmp, (32, 32)) pygame.display.set_icon(icon) pygame.mouse.set_visible(0) # Load the missile image missile_img = pygame.image.load(f'{imgdir}/ammo/missile.png').convert() # Load shield images shield_100 = pygame.image.load(f'{imgdir}/shield/shield_100.png').convert() shield_75 = pygame.image.load(f'{imgdir}/shield/shield_75.png').convert() shield_50 = pygame.image.load(f'{imgdir}/shield/shield_50.png').convert() shield_25 = pygame.image.load(f'{imgdir}/shield/shield_25.png').convert() # Load powerup images power_imgs = {} power_imgs['shield_img'] = pygame.image.load(f'{imgdir}/powerups/shield.png').convert() power_imgs['full_life_img'] = pygame.image.load(f'{imgdir}/powerups/full_life.png').convert() power_imgs['partial_life_img'] = pygame.image.load(f'{imgdir}/powerups/partial_life.png').convert() # Load sound effects shoot_sound = pygame.mixer.Sound(f'{snddir}/rocket_fire.wav') enemy_explode = pygame.mixer.Sound(f'{snddir}/explosion.wav') player_hit = pygame.mixer.Sound(os.path.join(snddir, "myexplosion.wav")) player_die = pygame.mixer.Sound(os.path.join(snddir, 'player_die.wav')) shield_up = pygame.mixer.Sound(os.path.join(snddir, 'shield_up.wav')) partial_heal = pygame.mixer.Sound(os.path.join(snddir, 'partial_heal.wav')) full_heal = pygame.mixer.Sound(os.path.join(snddir, 'full_heal.wav')) # Explosion animations containers explosion_animation = {} explosion_animation['lg'] = [] explosion_animation['sm'] = [] explosion_animation['player'] = [] # Load explosion images for animation for i in range(90): filename = f'explosion1_{i}.png' img = pygame.image.load(os.path.join(f'{imgdir}/animation/enemy_explosion', filename)) img.set_colorkey(black) img_lg = pygame.transform.scale(img, (75, 75)) explosion_animation['lg'].append(img_lg) img_sm = pygame.transform.scale(img, (32, 32)) explosion_animation['sm'].append(img_sm) for i in range(9): filename = f'explosion_{i}.png' img = pygame.image.load(os.path.join(f'{imgdir}/animation/player_explosion', filename)) img.set_colorkey(black) explosion_animation['player'].append(img) # Number of enemy ships to display on the screen how_many_enemy_ships = 10 # Function for displaying text on the screen def draw_text(surface, xpos, ypos, text, color, size, myfont, style): font = freetype.Font(os.path.join(fontdir, 'fonts', myfont)) return font.render_to(surface, (round(xpos), round(ypos)), text, color, size=size, style=style) # Class for the background music. class BgMusic: def __init__(self): music_list = os.listdir(musicdir) file = rnd.choice(music_list) music = pygame.mixer.Sound(f'{musicdir}/{file}') music.play(-1) # Class for the explosion effects class Explosion(pygame.sprite.Sprite): def __init__(self, center, size): pygame.sprite.Sprite.__init__(self) self.size = size self.image = explosion_animation[self.size][0] self.rect = self.image.get_rect() self.rect.center = center self.frame = 0 self.last_update = pygame.time.get_ticks() self.frame_rate = 20 def update(self): now = pygame.time.get_ticks() if now - self.last_update > self.frame_rate: self.last_update = now self.frame += 1 if self.frame == len(explosion_animation[self.size]): self.kill() else: center = self.rect.center self.image = explosion_animation[self.size][self.frame] self.rect = self.image.get_rect() self.rect.center = center # Class for displaying various information class Display: def __init__(self): pass def rotate_image(self, rotate, x, y, ship): img = pygame.image.load(os.path.join(imgdir, ship)).convert() img = pygame.transform.scale(img, (64, 64)) img = pygame.transform.rotate(img, rotate) img.set_colorkey(black) screen.blit(img, (x, y)) # Define the title screen def title_screen(self): # Set the background screen.blit(background, background_rect) # Background music BgMusic() # Draw text to the screen draw_text(screen, 225, 15, 'Shmup!', purple, 100, 'comicbd.ttf', freetype.STYLE_NORMAL) draw_text(screen, 230, 130, 'Beta Version 0.04', grayish, 15, 'comic.ttf', freetype.STYLE_NORMAL) draw_text(screen, 410, 130, 'Powered by Pygame 1.9.6', \ green, 15, 'comic.ttf', freetype.STYLE_NORMAL|freetype.STYLE_OBLIQUE) draw_text(screen, 225, 180, 'Top Five High Scores', red, 30, 'comicbd.ttf', freetype.STYLE_UNDERLINE) # Initialize the Scores class scores = Scores() # Check for a score text file scores.check_for_score_file() # Display the top five scores i = 0 n = 1 for score in scores.get_scores(5): draw_text(screen, 225, 240+i, f'{n}. {score[1].upper()}', palewhite, 20, 'comic.ttf', freetype.STYLE_NORMAL) draw_text(screen, 425, 240+i, f'{score[0]}', palewhite, 20, 'comic.ttf', freetype.STYLE_NORMAL) i += 35 n += 1 # Draw game contols on the title screen draw_text(screen, 225, 430, f'Press Spacebar to Start', yellow, 25, 'comic.ttf', freetype.STYLE_NORMAL) draw_text(screen, 220, 460, f'Arrow or A and D keys to move, spacebar to fire.', yellow, 20, 'comic.ttf', freetype.STYLE_NORMAL) # Draw power icons and text screen.blit(power_imgs['shield_img'], (50,height-100)) draw_text(screen, 100, height-90, f'Gives a shield', palewhite, 20, 'comic.ttf', freetype.STYLE_NORMAL) screen.blit(power_imgs['full_life_img'], (260, height-100)) draw_text(screen, 310,height-90, f'Restores Full Life', palewhite, 20, 'comic.ttf', freetype.STYLE_NORMAL) screen.blit(power_imgs['partial_life_img'], (500, height-100)) draw_text(screen, 550, height-90, f'Restores Partial Life', palewhite, 20, 'comic.ttf', freetype.STYLE_NORMAL) # The footer draw_text(screen, 280, height-30, \ f'GamingRat Productions 2020 {chr(169)}', \ peru, 15, 'comic.ttf', freetype.STYLE_NORMAL|freetype.STYLE_OBLIQUE) img = pygame.image.load(os.path.join(f'{imgdir}/icons', 'ratt2b.bmp')) img.set_colorkey(black) img = pygame.transform.scale(img, (50, 50)) screen.blit(img, (220, height-50)) # The two ships on the title screen ships = os.listdir(f'{imgdir}/ships') self.rotate_image(-130, 25, 25, f'ships/{rnd.choice(ships)}') self.rotate_image(130, 675, 25, f'ships/{rnd.choice(ships)}') pygame.display.flip() # Keeps player on the title screen until the spacebar is pressed waiting = True while waiting: clock.tick(fps) try: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() keystate = pygame.key.get_pressed() if keystate[pygame.K_SPACE]: waiting = False except pygame.error: break # define the lives bar. The three small ships on the right top def lives_bar(self, surface, x, y, lives, img): for i in range(lives): img_rect = img.get_rect() img_rect.x = x + 30 * i img_rect.y = y surface.blit(img, img_rect) # Define the life and shield bar. # Changes color depending on hits taken def status_bar(self, surface, x, y, obj): length = 100 height = 15 fill = (obj/length) * length if fill >= 75: color = green elif fill < 75 and fill >= 60: color = yellow elif fill < 60 and fill >= 30: color = orange elif fill <= 0: color = black else: color = red fill_rect = pygame.Rect(round(x), round(y), round(fill), round(height)) pygame.draw.rect(surface, color, fill_rect) outline_rect = pygame.Rect(round(x), round(y), length, height) pygame.draw.rect(surface, palewhite, outline_rect, 1) # Define the class for the player class Player(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) self.image = pygame.transform.scale(player_img, (50, 50)) self.image.set_colorkey(black) self.rect = self.image.get_rect() self.radius = 25 self.rect.centerx = round(width / 2) self.rect.bottom = height - 10 self.speedx = 0 self.hidden = False self.life = 100 self.lives = 3 self.hide_timer = pygame.time.get_ticks() # Defines the player bullets def shoot(self, bullet_sprites): bullet = Bullets(self.rect.centerx, self.rect.top) bullet_sprites.add(bullet) shoot_sound.play() # Hides the player for a momment when destroyed def hide(self): self.hidden = True self.hide_timer = pygame.time.get_ticks() self.rect.center = (round(width / 2), height + 200) # Restores player after being destroyed and handles player movement. def update(self): if self.hidden and pygame.time.get_ticks() - self.hide_timer > 1000: self.hidden = False self.rect.centerx = round(width / 2) self.rect.bottom = height - 10 self.speedx = 0 keystate = pygame.key.get_pressed() if keystate[pygame.K_LEFT] or keystate[pygame.K_a]: self.speedx = -8 if keystate[pygame.K_RIGHT] or keystate[pygame.K_d]: self.speedx = 8 self.rect.x += self.speedx if self.rect.right > width - 15: self.rect.right = width - 15 if self.rect.left < 15: self.rect.left = 15 # Class for defining the bullets/missile beng shot class Bullets(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.transform.scale(missile_img, (18, 32)) self.image.set_colorkey(black) self.rect = self.image.get_rect() self.rect.bottom = y + 10 self.rect.centerx = x self.speedy = -10 # Update bullet movement def update(self): self.rect.y += self.speedy # Kill it if it moves off the top of the screen if self.rect.bottom < 0: self.kill() # Class for defining the shield class Shield(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) self.shield_img = shield_100 self.image = pygame.transform.scale(self.shield_img, (80, 50)) self.image.set_colorkey(black) self.rect = self.image.get_rect() self.radius = 30 self.rect.centerx = round(width/2) self.rect.top = height + 50 self.strength = 0 self.hidden = True # Defines the shield def raise_shield(self, x, y, sprite): shield = self sprite.add(shield) shield.rect.centerx = x shield.rect.top = y - 20 # Defines shield movement and kills it when not on use def update(self): if self.hidden: self.kill() self.speedx = 0 keystate = pygame.key.get_pressed() if keystate[pygame.K_LEFT] or keystate[pygame.K_a]: self.speedx = -8 if keystate[pygame.K_RIGHT] or keystate[pygame.K_d]: self.speedx = 8 self.rect.x += self.speedx if self.rect.right > width: self.rect.right = width if self.rect.left < 0: self.rect.left = 0 # Class for defining mob attributes class Mob(pygame.sprite.Sprite): def __init__(self): excludes = ['myship.png', 'myship2.png'] pygame.sprite.Sprite.__init__(self) ships = os.listdir(f'{imgdir}/ships') enemy_ships = [] for ship in ships: if ship not in excludes: enemy_ships.append(pygame.image.load(os.path.join(f'{imgdir}/ships', ship))) self.image_orig = pygame.transform.scale(rnd.choice(enemy_ships), (rnd.randint(20, 40), rnd.randint(20, 40))) self.image_orig.set_colorkey(black) self.image = self.image_orig.copy() self.rect = self.image.get_rect() self.radius = int(self.rect.width * .80 / 2) self.rect.x = rnd.randrange(width - self.rect.width) self.rect.y = rnd.randrange(-100, -40) self.speedy = rnd.randrange(1, 5) self.speedx = rnd.randrange(-3, 3) # Can use this to rorate objects self.rot = 0 self.rot_speed = rnd.randrange(-8, 8) self.last_update = pygame.time.get_ticks() # Creates mobs def newmob(self, mob_sprites): mob = self mob_sprites.add(mob) # Update mob movement and placement def update(self): self.rect.x += self.speedx self.rect.y += self.speedy if self.rect.top > height + 10 or self.rect.left < -25 or self.rect.right > width + 25: self.rect.x = rnd.randrange(width - self.rect.width) self.rect.y = rnd.randrange(-100, -40) self.speedy = rnd.randrange(1, 8) self.speedx = rnd.randrange(-3, 3) # Class for the powerups class PowerUps(pygame.sprite.Sprite): def __init__(self, center): pygame.sprite.Sprite.__init__(self) self.type = rnd.choice(['shield_img', 'full_life_img', 'partial_life_img']) self.image = power_imgs[self.type] self.image.set_colorkey(black) self.rect = self.image.get_rect() self.rect.center = center self.speedy = 2 def update(self): self.rect.y += self.speedy # Kill it if it moves off the bottom of the screen if self.rect.top > height: self.kill() # Class for handling writing to the scores.txt file and also reads # the file for displaying the scores on the title screen class Scores: def __init__(self): # Set the text file for writing scores self.score_file = 'scores.txt' # Write scores and name to text file # If the file contains more than 25 lines, the lowest score is deleted. def write_score(self, name, score): with open(self.score_file, 'a')as data: data.write(f'{name[0:3]}:{score}\n') self.get_scores(25) # Display scores def get_scores(self, howmany): mydict = {} with open(self.score_file, 'r') as lines: for line in lines: line = line.split(':') mydict[int(line[1].strip())] = line[0].strip() sorted_dict = sorted(mydict.items(), reverse=True) while len(sorted_dict) > howmany: sorted_dict.pop() return sorted_dict # Check if file exists. If not create it. def check_for_score_file(self): if os.path.isfile(self.score_file): pass else: with open(self.score_file, 'w') as scores: pass # tkinter form for getting player intials def form(self, score): self.root = tk.Tk() self.root.geometry('347x124') self.root.resizable(width=False, height=False) self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) img = tk.PhotoImage(file='images/backgrounds/bg2b.png') img.img = img imlabel = tk.Label(self.root, image=img) imlabel.pack(expand=1, fill='both') self.entry = tk.Entry(imlabel) self.entry.focus() self.entry['bg'] = '#555' self.entry['fg'] = 'whitesmoke' self.entry.place(x=140, y=8) btn = tk.Button(imlabel, text='Submit') btn['fg'] = '#fff' btn['bg'] = '#111' btn['activebackground'] = '#222' btn['activeforeground'] = 'tomato' btn['cursor'] = 'hand2' btn['command'] = partial(self.callback, score) btn.place(x=230, y=60) self.root.bind('<Return>', partial(self.callback, score)) self.root.bind('<KP_Enter>', partial(self.callback, score)) self.root.mainloop() # Callback for writing initials and score to text file def callback(self, score, event=None): name = self.entry.get() self.write_score(name, score) self.root.destroy() # Class for defining all the sprite collisions class Hit: def __init__(self): pass # define the method def hit(self, kwargs): # Change kwargs to simple name variables player = kwargs['player'] shield = kwargs['shield'] shield_sprites = kwargs['shield_sprites'] bullet_sprites = kwargs['bullet_sprites'] player_sprites = kwargs['player_sprites'] mob_sprites = kwargs['mob_sprites'] score = kwargs['score'] power_sprites = kwargs['power_sprites'] explosion_sprites = kwargs['explosion_sprites'] # Mob hits player # Set to false so player does not die in one hit # If player life reaches zero, destroy the ship ship_destroy = False if player.life <= 0: ship_destroy = True # Get all the sprite collisions and loop through for needed information player_hits = pygame.sprite.groupcollide(mob_sprites, player_sprites, True, ship_destroy) for hit in player_hits: # Play the explosion sound and image files player_hit.play() explosion = Explosion(hit.rect.center, 'sm') explosion_sprites.add(explosion) # Create a new mob to replace the one destroyed mob = Mob() mob.newmob(mob_sprites) mob_sprites.add(mob) # Player was destroyed, play sound and animation files # Hide the player for a momment, subtract one life # Restore life bar to full player.life -= hit.radius if player.life <= 0: player_die.play() global death_explosion death_explosion = Explosion(hit.rect.center, 'player') explosion_sprites.add(death_explosion) player.hide() player. lives -= 1 player.life = 100 # Player shoots mob # Same as above. Get loop through sprite information # Player the sound and animation files # Add score to player for destroyed mobs # Replace destroyed mobs hits = pygame.sprite.groupcollide(mob_sprites, bullet_sprites, True, True) for hit in hits: enemy_explode.play() enemy_explosion = Explosion(hit.rect.center, 'lg') explosion_sprites.add(enemy_explosion) score += (20 - hit.radius) mob = Mob() mob.newmob(mob_sprites) mob_sprites.add(mob) # Random drop of power icons if rnd.random() > 0.9: power = PowerUps(hit.rect.center) power_sprites.add(power) # Power icons fall and hit player # Same as above. powerhits = pygame.sprite.spritecollide(player, power_sprites, True) for hit in powerhits: # If the shield hits player, give the shield if hit.type == 'shield_img': shield_up.play() shield.strength = 100 shield.hidden = False shield.raise_shield(player.rect.centerx, player.rect.top, shield_sprites) # If partial life icon hits player, restore part of player life # based on mob size. If the player has full life, it's added to # the score instead elif hit.type == 'partial_life_img': partial_heal.play() if player.life == 100: score += player.life * 0.25 else: player.life += player.life * 0.25 if player.life > 100: player.life = 100 # Sames as the partial life but restores full life instead # and gives 50 points to player score elif hit.type == 'full_life_img': full_heal.play() if player.life == 100: score += 50 else: player.life = 100 # Mob hits shield # Same as above, collects the sprite collision information # Plays the sound and animation files shield_hits = pygame.sprite.groupcollide(mob_sprites, shield_sprites, True, False) for hit in shield_hits: player_hit.play() explosion = Explosion(hit.rect.center, 'sm') explosion_sprites.add(explosion) # Create new mob to replace the one destroyed mob = Mob() mob.newmob(mob_sprites) mob_sprites.add(mob) # Subtract from the shield. If it reaches zero, hide and destroy shield.strength -= 20 if shield.strength <= 0: shield.strength = 0 shield.hidden = True # Return the score for displaying on the screen return round(score) # Define the main program def main(): # Initialize the Display class and set some variables status = Display() game_over = True running = True while running: # Not playing the game, stay on the title screen # Reset the score to zero if game_over: status.title_screen() game_over = False score = 0 # Initialize player and shield sprite groups # Initialize player and shield class player_sprites = pygame.sprite.Group() player = Player() player_sprites.add(player) shield_sprites = pygame.sprite.Group() shield = Shield() # Initialize the rest of the sprite groups power_sprites = pygame.sprite.Group() mob_sprites = pygame.sprite.Group() bullet_sprites = pygame.sprite.Group() explosion_sprites = pygame.sprite.Group() # Put the mobs on the screen for i in range(how_many_enemy_ships): mob = Mob() mob.newmob(mob_sprites) mob_sprites.add(mob) # Try except to prevent the console error when exiting game try: # Loop through pygame events for event in pygame.event.get(): # Exit game if event.type == pygame.QUIT: running = False # Spacebar pressed, play game elif event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: player.shoot(bullet_sprites) except pygame.error: break # Set some dict variables needed in the Hits class kwargs = {} kwargs['player'] = player kwargs['shield'] = shield kwargs['player_sprites'] = player_sprites kwargs['mob'] = mob kwargs['bullet_sprites'] = bullet_sprites kwargs['mob_sprites'] = mob_sprites kwargs['power_sprites'] = power_sprites kwargs['shield_sprites'] = shield_sprites kwargs['explosion_sprites'] = explosion_sprites kwargs['score'] = score # Initialize the Hit class. Put in a variable to get the score # for display score = Hit().hit(kwargs) # Update all sprites player_sprites.update() mob_sprites.update() shield_sprites.update() bullet_sprites.update() power_sprites.update() explosion_sprites.update() # Player lost last life, write score to text file if player.lives == 0 and not death_explosion.alive(): game_over = True write_score = Scores() write_score.form(score) # Fill the pygame screen background with color and image screen.fill(black) screen.blit(background, background_rect) # Draw all sprites to the screen player_sprites.draw(screen) mob_sprites.draw(screen) shield_sprites.draw(screen) bullet_sprites.draw(screen) power_sprites.draw(screen) explosion_sprites.draw(screen) # Draw all bars and text to the screen draw_text(screen, 18, 15, f'Life', palewhite, 20, 'comic.ttf', freetype.STYLE_NORMAL) status.status_bar(screen, 90, 15, player.life) draw_text(screen, 18, 45, f'Shield', palewhite, 20, 'comic.ttf', freetype.STYLE_NORMAL) status.status_bar(screen, 90, 45, shield.strength) draw_text(screen, width/2-20, 15, f'Score: {score}', palewhite, 20, 'comic.ttf', freetype.STYLE_NORMAL) status.lives_bar(screen, width - 95, 15, player.lives, mini_img) pygame.display.flip() clock.tick(fps) if __name__ == '__main__': main() RE: Shmup - BashBedlam - Feb-11-2021 To get it to work on a linux system, I changed # Set the path for the image, music and sound directories imgdir = os.path.join(os.path.dirname(__file__), 'images') snddir = os.path.join(os.path.dirname(__file__), 'sounds') musicdir = os.path.join(os.path.dirname(__file__), 'music')to... # Set the path for the image, music and sound directories current_working_directory = os.getcwd () imgdir = f'{current_working_directory}/images' snddir = f'{current_working_directory}/sounds' musicdir = f'{current_working_directory}/music'and explosions in player_explosion all started with a capital E so I changed filename = f'explosion_{i}.png' img = pygame.image.load(os.path.join(f'{imgdir}/animation/player_explosion', filename))to... filename = f'Explosion_{i}.png' img = pygame.image.load(os.path.join(f'{imgdir}/animation/player_explosion', filename))Now it all works on my system (ubuntu) and I must say that I am VERY impressed! Nice Job! |