p5bg:bullet_game
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous 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. | ||
| - | # | ||
| - | # | ||
| - | </ | ||
p5bg/bullet_game.1725440068.txt.gz · Last modified: 2024/09/04 08:54 by appledog
