p5bg:bullet_game
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revision | |||
p5bg:bullet_game [2024/09/04 08:54] – appledog | p5bg:bullet_game [2024/09/11 06:42] (current) – removed appledog | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | = Bullet Game | ||
- | Bullet Game is our near-end stage pygame 2d game framework example. We've already done at least 6 or 7 other games using pygame. So this will document the patterns we have learned. | ||
- | We began by "just writing" | ||
- | |||
- | * Refactoring | ||
- | ** Encapsulation | ||
- | ** Light Commentary (code commentary) ex. IN and OUT for functions. | ||
- | |||
- | The primary goal is encapsulation. So, we're going to break up the old main into several new classes. | ||
- | |||
- | == PyGame Framework | ||
- | The first part of this is the use of [[pygame framework]]. We'll describe the additions here. | ||
- | |||
- | == Bullet Game | ||
- | Here is the code we added, starting with main.py: | ||
- | |||
- | === main.py | ||
- | < | ||
- | from Window import * | ||
- | from Game import * | ||
- | |||
- | def main(): | ||
- | window = Window() | ||
- | window.setCaption(" | ||
- | window.setSize(Screen.WIDTH, | ||
- | window.setFont(" | ||
- | |||
- | game = Game(window) | ||
- | game.start() | ||
- | |||
- | if __name__ == " | ||
- | main() | ||
- | </ | ||
- | |||
- | === Screen,py | ||
- | < | ||
- | |||
- | class Screen: | ||
- | WIDTH = 800 | ||
- | HEIGHT = 600 | ||
- | FPS = 60 | ||
- | |||
- | MARGIN = 96 | ||
- | GAMETOP = MARGIN | ||
- | GAMEBOT = HEIGHT - MARGIN | ||
- | </ | ||
- | |||
- | |||
- | === Game.py | ||
- | < | ||
- | import pygame | ||
- | import random | ||
- | import pickle | ||
- | |||
- | from Screen import * | ||
- | from Window import * | ||
- | from Bullet import * | ||
- | from Color import * | ||
- | from Screen import * | ||
- | from Zombie import * | ||
- | from SpaceShip import * | ||
- | from Sound import * | ||
- | from Player import * | ||
- | from Constants import * | ||
- | from Level import * | ||
- | from Star import * | ||
- | |||
- | |||
- | class Game: | ||
- | def __init__(self, | ||
- | self.window = window | ||
- | self.sound = Sound() | ||
- | |||
- | # Initialize Game World Data | ||
- | # The Player & Level | ||
- | self.player = Player() | ||
- | self.player.hiscore = self.load_hiscore(" | ||
- | |||
- | self.level = Level(1, self.player) | ||
- | |||
- | # Bullet list | ||
- | self.bullets = pygame.sprite.Group() | ||
- | |||
- | # Zombies list | ||
- | self.zombies = pygame.sprite.Group() | ||
- | self.spaceships = pygame.sprite.Group() | ||
- | |||
- | # 30 random stars. | ||
- | self.stars = [] | ||
- | for n in range(30): | ||
- | x = random.randint(0, | ||
- | y = random.randint(0, | ||
- | self.stars.append((x, | ||
- | |||
- | # All member variables are now globals: | ||
- | # Try commenting it out to see what happens! | ||
- | globals().update(self.__dict__) | ||
- | |||
- | ################################# | ||
- | ## Main Loop ## | ||
- | ################################# | ||
- | |||
- | def start(self): | ||
- | |||
- | # Clock for controlling the frame rate | ||
- | clock = pygame.time.Clock() | ||
- | |||
- | # Main game loop | ||
- | running = True | ||
- | frame_counter = 0 | ||
- | |||
- | ################## | ||
- | # GAME LOOP ORDER OF EVENTS: | ||
- | ################## | ||
- | sound.play(" | ||
- | # pygame.mixer.init() | ||
- | # pygame.mixer.music.load(' | ||
- | # pygame.mixer.music.play() | ||
- | |||
- | # Start of Game Loop | ||
- | time_start = time.time() * 1000 | ||
- | LEVEL_DELAY = 3000 | ||
- | while running: | ||
- | time_clock = (time.time() * 1000) - time_start | ||
- | frame_counter += 1 | ||
- | if frame_counter > 60: | ||
- | frame_counter = 1 | ||
- | |||
- | ######################## | ||
- | # 1. EVENT HANDLING | ||
- | ######################## | ||
- | for event in pygame.event.get(): | ||
- | if event.type == pygame.QUIT: | ||
- | running = False | ||
- | quit_game() | ||
- | if event.type == pygame.KEYDOWN: | ||
- | if event.key == pygame.K_z: | ||
- | self.create_spaceship() | ||
- | if event.key == pygame.K_ESCAPE: | ||
- | self.quit_game() | ||
- | if event.key == pygame.K_q: | ||
- | self.quit_game() | ||
- | |||
- | if event.key == pygame.K_w: | ||
- | x = self.player.rect.x + self.player.SIZE / 2 | ||
- | y = self.player.rect.y + self.player.SIZE / 2 | ||
- | self.create_bullet(x, | ||
- | |||
- | # Continuous (frame-by-frame) Key press handling | ||
- | keys = pygame.key.get_pressed() | ||
- | if keys[pygame.K_LEFT]: | ||
- | self.player.rect.x -= self.player.SPEED | ||
- | if keys[pygame.K_RIGHT]: | ||
- | self.player.rect.x += self.player.SPEED | ||
- | |||
- | ####################### | ||
- | # 2. RULES SECTION | ||
- | ####################### | ||
- | ### Level-specifc stuff: | ||
- | if time_clock > LEVEL_DELAY: | ||
- | commands = self.level.tick_hook() | ||
- | |||
- | for cmd in commands: | ||
- | if cmd == " | ||
- | self.create_zombie() | ||
- | |||
- | if cmd == " | ||
- | self.create_spaceship() | ||
- | |||
- | if cmd == "go to level2": | ||
- | self.kill_enemies() | ||
- | time_start = time.time() * 1000 | ||
- | self.level = Level(2, player) | ||
- | |||
- | if cmd == "go to level3": | ||
- | self.kill_enemies() | ||
- | time_start = time.time() * 1000 | ||
- | self.level = Level(3, self.player) | ||
- | if cmd == "go to level4": | ||
- | self.kill_enemies() | ||
- | time_start = time.time() * 1000 | ||
- | self.level = Level(4, self.player) | ||
- | |||
- | # 2b. rules for bullets | ||
- | # 2b.1. Update the bullets position. | ||
- | self.bullets.update() | ||
- | |||
- | # Update all stars. | ||
- | for i in range(len(self.stars)): | ||
- | (x, y) = self.stars[i] | ||
- | y = y + 1 | ||
- | |||
- | if y > Screen.HEIGHT: | ||
- | x = random.randint(0, | ||
- | y = 0 | ||
- | |||
- | self.stars[i] = (x, y) | ||
- | |||
- | if time_clock > LEVEL_DELAY: | ||
- | # 2a rules for the square | ||
- | # Keep the square within bounds | ||
- | self.player.update() | ||
- | |||
- | # 2c. rules for zombies | ||
- | # 2c.1. Update the zombies position. | ||
- | self.zombies.update() | ||
- | self.spaceships.update() | ||
- | |||
- | # Clear the screen | ||
- | self.window.screen.fill(Color.BLACK) | ||
- | |||
- | # Draw stars first (because its a background) | ||
- | for s in self.stars: | ||
- | pygame.draw.rect(self.window.screen, | ||
- | |||
- | # Draw the square | ||
- | # pygame.draw.rect(screen, | ||
- | self.window.screen.blit(self.player.image, | ||
- | |||
- | if time_clock > LEVEL_DELAY: | ||
- | # Draw the zombies. | ||
- | self.zombies.draw(self.window.screen) | ||
- | self.spaceships.draw(self.window.screen) | ||
- | |||
- | # ships make a sound, if... | ||
- | if self.spaceships: | ||
- | if frame_counter == 30: | ||
- | self.sound.play(" | ||
- | |||
- | # Draw the bullets. | ||
- | self.bullets.draw(self.window.screen) | ||
- | |||
- | # write score. | ||
- | if frame_counter < 30: | ||
- | s = " | ||
- | text_surface = self.window.font.render(s, | ||
- | self.window.screen.blit(text_surface, | ||
- | |||
- | s = f" | ||
- | text_surface = self.window.font.render(s, | ||
- | self.window.screen.blit(text_surface, | ||
- | |||
- | # do high score. | ||
- | s = "HIGH SCORE" | ||
- | text_surface = self.window.font.render(s, | ||
- | self.window.screen.blit(text_surface, | ||
- | |||
- | s = f" | ||
- | text_surface = self.window.font.render(s, | ||
- | self.window.screen.blit(text_surface, | ||
- | |||
- | s = f" | ||
- | text_surface = self.window.font.render(s, | ||
- | self.window.screen.blit(text_surface, | ||
- | |||
- | if time_clock < LEVEL_DELAY: | ||
- | s = f" | ||
- | text_surface = self.window.font.render(s, | ||
- | tr = text_surface.get_rect() | ||
- | tr.center = (Screen.WIDTH // 2, Screen.HEIGHT // 2 - 100) | ||
- | self.window.screen.blit(text_surface, | ||
- | |||
- | # Update the display | ||
- | pygame.display.flip() | ||
- | |||
- | if time_clock > LEVEL_DELAY: | ||
- | # 3. INTERSPRITE COLLISION | ||
- | # 3a. bullets kill zombies | ||
- | collisions = pygame.sprite.groupcollide(self.bullets, | ||
- | collisions2 = pygame.sprite.groupcollide(self.bullets, | ||
- | |||
- | if collisions: | ||
- | self.player.score = self.player.score + len(collisions) | ||
- | if self.player.score > self.player.hiscore: | ||
- | self.player.hiscore = self.player.score | ||
- | |||
- | if collisions or collisions2: | ||
- | n = random.randint(1, | ||
- | if n == 1: | ||
- | self.sound.play(" | ||
- | else: | ||
- | self.sound.play(" | ||
- | |||
- | if collisions2: | ||
- | self.player.score = self.player.score + (len(collisions2) * 10) | ||
- | if self.player.score > self.player.hiscore: | ||
- | self.player.hiscore = self.player.score | ||
- | |||
- | # Players may not touch zombies: | ||
- | collided_enemies = pygame.sprite.spritecollide(self.player, | ||
- | |||
- | if collided_enemies: | ||
- | print(" | ||
- | print(" | ||
- | print(" | ||
- | self.quit_game() | ||
- | |||
- | # Control the frame rate | ||
- | clock.tick(60) | ||
- | |||
- | ################################# | ||
- | ## Support Functions | ||
- | ################################# | ||
- | |||
- | def create_bullet(self, | ||
- | self.bullet = Bullet(x, y, dx, dy) | ||
- | self.bullets.add(self.bullet) | ||
- | self.sound.play(" | ||
- | if Constants.VERBOSE: | ||
- | print(" | ||
- | |||
- | def create_zombie(self): | ||
- | sx = random.randint(50, | ||
- | sy = 0 + Screen.MARGIN | ||
- | dx = 0 | ||
- | dy = 1 | ||
- | z = Zombie(sx, sy, dx, dy) | ||
- | self.zombies.add(z) | ||
- | if Constants.VERBOSE: | ||
- | print(" | ||
- | |||
- | def create_spaceship(self): | ||
- | sx = Screen.WIDTH | ||
- | sy = 0 + Screen.MARGIN | ||
- | dx = -1 | ||
- | dy = 0 | ||
- | |||
- | if random.randint(1, | ||
- | dx = 1 | ||
- | sx = 0 | ||
- | |||
- | s = SpaceShip(sx, | ||
- | self.spaceships.add(s) | ||
- | if Constants.VERBOSE: | ||
- | print(" | ||
- | |||
- | def kill_enemies(self): | ||
- | for z in self.zombies: | ||
- | z.kill() | ||
- | for s in self.spaceships: | ||
- | s.kill() | ||
- | |||
- | # Save the score to a text file. | ||
- | def save_hiscore(self, | ||
- | with open(filename, | ||
- | file.write(str(score)) | ||
- | |||
- | # Load the score from a text file. | ||
- | def load_hiscore(self, | ||
- | try: | ||
- | with open(filename, | ||
- | score = int(file.read()) | ||
- | return score | ||
- | except: | ||
- | print(f" | ||
- | return 0 | ||
- | |||
- | def quit_game(): | ||
- | self.save_hiscore(" | ||
- | pygame.quit() | ||
- | quit() | ||
- | </ | ||
- | |||
- | === Level.py | ||
- | < | ||
- | import random | ||
- | |||
- | class Level: | ||
- | |||
- | def __init__(self, | ||
- | self.level_number = n | ||
- | self.player = p | ||
- | |||
- | match self.level_number: | ||
- | case 1: | ||
- | self.tick_hook = self.level1 | ||
- | |||
- | case 2: | ||
- | self.tick_hook = self.level2 | ||
- | |||
- | case 3: | ||
- | self.tick_hook = self.level3 | ||
- | |||
- | case 4: | ||
- | self.tick_hook = self.level4 | ||
- | |||
- | case default: | ||
- | print(" | ||
- | print(" | ||
- | self.tick_hook = self.level1 | ||
- | |||
- | return | ||
- | |||
- | |||
- | ##### Tick Hooks | ||
- | def level1(self): | ||
- | commands = [] | ||
- | |||
- | if random.randint(1, | ||
- | commands.append(" | ||
- | |||
- | if self.player.score >= 20: | ||
- | commands.append(" | ||
- | |||
- | return commands | ||
- | |||
- | |||
- | def level2(self): | ||
- | commands = [] | ||
- | |||
- | if random.randint(1, | ||
- | commands.append(" | ||
- | |||
- | if self.player.score >= 40: | ||
- | commands.append(" | ||
- | |||
- | return commands | ||
- | |||
- | def level3(self): | ||
- | commands = [] | ||
- | |||
- | if random.randint(1, | ||
- | commands.append(" | ||
- | |||
- | if random.randint(1, | ||
- | commands.append(" | ||
- | |||
- | if self.player.score >= 80: | ||
- | commands.append(" | ||
- | |||
- | return commands | ||
- | |||
- | def level4(self): | ||
- | commands = [] | ||
- | |||
- | if random.randint(1, | ||
- | commands.append(" | ||
- | |||
- | if random.randint(1, | ||
- | commands.append(" | ||
- | |||
- | #if self.player.score > 320: | ||
- | # commands.append(" | ||
- | |||
- | return commands | ||
- | </ | ||
- | |||
- | === Object.py | ||
- | < | ||
- | import pygame | ||
- | |||
- | class Object(pygame.sprite.Sprite): | ||
- | def __init__(self, | ||
- | super().__init__() | ||
- | self.rect = pygame.Rect(x, | ||
- | self.image = pygame.Surface((width, | ||
- | self.width = width | ||
- | self.height = height | ||
- | self.name = name | ||
- | |||
- | def draw(self, win, offset_x, offset_y): | ||
- | win.blit(self.image, | ||
- | |||
- | </ | ||
- | |||
- | === Player.py | ||
- | < | ||
- | import pygame | ||
- | import time | ||
- | from Color import * | ||
- | from Screen import * | ||
- | |||
- | # Define the Bullet Sprite class | ||
- | class Player(pygame.sprite.Sprite): | ||
- | SPEED = 5 | ||
- | SIZE = 20 | ||
- | COLOR = Color.WHITE | ||
- | |||
- | def __init__(self): | ||
- | super().__init__() | ||
- | self.ticks = 0 | ||
- | self.loops = 0 | ||
- | |||
- | # Create a surface for the bullet | ||
- | self.image = pygame.Surface((self.SIZE, | ||
- | |||
- | # Fill the surface with a color | ||
- | self.image.fill(self.COLOR) | ||
- | |||
- | # Get the rectangle for positioning | ||
- | self.rect = self.image.get_rect() | ||
- | |||
- | # Set the initial position | ||
- | self.rect.center = (Screen.WIDTH// | ||
- | |||
- | self.score = 0 | ||
- | self.hiscore = 0 | ||
- | |||
- | def update(self): | ||
- | # Restrict to screen. | ||
- | if self.rect.x < 0: | ||
- | self.rect.x = 0 | ||
- | if self.rect.x > Screen.WIDTH - self.SIZE: | ||
- | self.rect.x = Screen.WIDTH - self.SIZE | ||
- | if self.rect.y < 0: | ||
- | self.rect.y = 0 | ||
- | if self.rect.y > Screen.HEIGHT - self.SIZE: | ||
- | self.rect.y = Screen.HEIGHT - self.SIZE | ||
- | |||
- | # Easier to read, but, slower. | ||
- | # | ||
- | # | ||
- | </ | ||
- | |||
- | === Sound.py | ||
- | < | ||
- | import pygame as a | ||
- | |||
- | class Sound: | ||
- | def __init__(self): | ||
- | a.mixer.init() | ||
- | dir = ' | ||
- | self.sounds = {} | ||
- | |||
- | self.sounds[" | ||
- | self.sounds[" | ||
- | self.sounds[" | ||
- | self.sounds[" | ||
- | self.sounds[" | ||
- | self.sounds[" | ||
- | |||
- | def play(self, sound): | ||
- | if sound in self.sounds: | ||
- | self.sounds[sound].play() | ||
- | else: | ||
- | print(f" | ||
- | </ |
p5bg/bullet_game.1725440099.txt.gz · Last modified: 2024/09/04 08:54 by appledog