User Tools

Site Tools


bullet_game

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
bullet_game [2024/08/31 01:47] – removed appledogbullet_game [2024/09/11 06:43] (current) – created 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" a game, then the second part was refactoring the game. I wanted to make refactoring it's own project. What are the goals of this refactoring?
 +
 +* 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.
 +
 +=== Also See
 +* [[Box Sprites]] -- the kind of sprites first used in this game.
 +
 +== Bullet Game
 +Here is the code we added, starting with main.py:
 +
 +=== main.py
 +<Code:Python>
 +from Window import *
 +from Game import *
 +
 +def main():
 +    window = Window()
 +    window.setCaption("Bullet Game")
 +    window.setSize(Screen.WIDTH, Screen.HEIGHT)
 +    window.setFont("assets/ps2p.ttf", 32)
 +
 +    game = Game(window)
 +    game.start()
 +
 +if __name__ == "__main__":
 +    main()
 +</Code>
 +
 +=== Screen,py
 +<Code:Python>
 +
 +class Screen:
 +    WIDTH = 800
 +    HEIGHT = 600
 +    FPS = 60
 +
 +    MARGIN = 96
 +    GAMETOP = MARGIN
 +    GAMEBOT = HEIGHT - MARGIN
 +</Code>
 +
 +
 +=== Game.py
 +<Code:Python>
 +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, window):
 +        self.window = window
 +        self.sound = Sound()
 +
 +        # Initialize Game World Data
 +        # The Player & Level
 +        self.player = Player()
 +        self.player.hiscore = self.load_hiscore("score.txt")
 +
 +        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, Screen.WIDTH)
 +            y = random.randint(0, Screen.HEIGHT)
 +            self.stars.append((x, y))
 +
 +        # 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("start")
 +        # pygame.mixer.init()
 +        # pygame.mixer.music.load('assets/sound/waveafterwave.mp3')
 +        # 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, y, 0, -1)
 +
 +            # 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 == "create zombie":
 +                        self.create_zombie()
 +
 +                    if cmd == "create spaceship":
 +                        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, Screen.WIDTH)
 +                    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, "white", pygame.Rect(s[0], s[1], 2, 2))
 +
 +            # Draw the square
 +            # pygame.draw.rect(screen, square_color, (square_x, square_y, square_size, square_size))
 +            self.window.screen.blit(self.player.image, self.player.rect)
 +
 +            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("pellet")
 +
 +            # Draw the bullets.
 +            self.bullets.draw(self.window.screen)
 +
 +            # write score.
 +            if frame_counter < 30:
 +                s = "1UP"
 +                text_surface = self.window.font.render(s, True, "white")
 +                self.window.screen.blit(text_surface, (96, 10))
 +
 +            s = f"{self.player.score:02}"
 +            text_surface = self.window.font.render(s, True, "red")
 +            self.window.screen.blit(text_surface, (160, 42))
 +
 +            # do high score.
 +            s = "HIGH SCORE"
 +            text_surface = self.window.font.render(s, True, "white")
 +            self.window.screen.blit(text_surface, (320, 10))
 +
 +            s = f"{self.player.hiscore:02}"
 +            text_surface = self.window.font.render(s, True, "red")
 +            self.window.screen.blit(text_surface, (512, 42))
 +
 +            s = f"{self.level.level_number}"
 +            text_surface = self.window.font.render(s, True, "red")
 +            self.window.screen.blit(text_surface, (700, 42))
 +
 +            if time_clock < LEVEL_DELAY:
 +                s = f"Level {self.level.level_number}"
 +                text_surface = self.window.font.render(s, True, "white")
 +                tr = text_surface.get_rect()
 +                tr.center = (Screen.WIDTH // 2, Screen.HEIGHT // 2 - 100)
 +                self.window.screen.blit(text_surface, tr)
 +
 +            # Update the display
 +            pygame.display.flip()
 +
 +            if time_clock > LEVEL_DELAY:
 +                # 3. INTERSPRITE COLLISION
 +                # 3a. bullets kill zombies
 +                collisions = pygame.sprite.groupcollide(self.bullets, self.zombies, True, True)
 +                collisions2 = pygame.sprite.groupcollide(self.bullets, self.spaceships, True, True)
 +
 +                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, 2)
 +                    if n == 1:
 +                        self.sound.play("boom")
 +                    else:
 +                        self.sound.play("boom7")
 +
 +                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, self.zombies, False)
 +
 +                if collided_enemies:
 +                    print("You are DEAD!")
 +                    print(" High score: " + str(self.player.hiscore))
 +                    print(" Your score: " + str(self.player.score))
 +                    self.quit_game()
 +
 +            # Control the frame rate
 +            clock.tick(60)
 +
 +    #################################
 +    ##      Support Functions      ##
 +    #################################
 +
 +    def create_bullet(self, x, y, dx, dy):
 +        self.bullet = Bullet(x, y, dx, dy)
 +        self.bullets.add(self.bullet)
 +        self.sound.play("laser")
 +        if Constants.VERBOSE:
 +            print("bullet created")
 +
 +    def create_zombie(self):
 +        sx = random.randint(50, Screen.WIDTH - 50)
 +        sy = 0 + Screen.MARGIN
 +        dx = 0
 +        dy = 1
 +        z = Zombie(sx, sy, dx, dy)
 +        self.zombies.add(z)
 +        if Constants.VERBOSE:
 +            print("zombie created")
 +
 +    def create_spaceship(self):
 +        sx = Screen.WIDTH
 +        sy = 0 + Screen.MARGIN
 +        dx = -1
 +        dy = 0
 +
 +        if random.randint(1, 2) == 1:
 +            dx = 1
 +            sx = 0
 +
 +        s = SpaceShip(sx, sy, dx, dy)
 +        self.spaceships.add(s)
 +        if Constants.VERBOSE:
 +            print("spaceship created")
 +
 +    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, filename, score):
 +        with open(filename, 'w') as file:
 +            file.write(str(score))
 +
 +    # Load the score from a text file.
 +    def load_hiscore(self, filename):
 +        try:
 +            with open(filename, 'r') as file:
 +                score = int(file.read())
 +                return score
 +        except:
 +            print(f"Error. Resetting high score to 0.")
 +            return 0
 +
 +    def quit_game():
 +        self.save_hiscore("score.txt", str(self.player.hiscore))
 +        pygame.quit()
 +        quit()
 +</Code>
 +
 +=== Level.py
 +<Code:Python>
 +import random
 +
 +class Level:
 +
 +    def __init__(self, n, p):
 +        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("Unknown Level Number: " + str(n))
 +                print("Defaulting to Level 1!")
 +                self.tick_hook = self.level1
 +
 +        return
 +
 +
 +##### Tick Hooks
 +    def level1(self):
 +        commands = []
 +
 +        if random.randint(1,45) == 1:
 +            commands.append("create zombie")
 +
 +        if self.player.score >= 20:
 +            commands.append("go to level2")
 +
 +        return commands
 +
 +
 +    def level2(self):
 +        commands = []
 +
 +        if random.randint(1, 35) == 1:
 +            commands.append("create zombie")
 +
 +        if self.player.score >= 40:
 +            commands.append("go to level3")
 +
 +        return commands
 +
 +    def level3(self):
 +        commands = []
 +
 +        if random.randint(1, 30) == 1:
 +            commands.append("create zombie")
 +
 +        if random.randint(1, 500) == 1:
 +            commands.append("create spaceship")
 +
 +        if self.player.score >= 80:
 +            commands.append("go to level4")
 +
 +        return commands
 +
 +    def level4(self):
 +        commands = []
 +
 +        if random.randint(1, 20) == 1:
 +            commands.append("create zombie")
 +
 +        if random.randint(1, 350) == 1:
 +            commands.append("create spaceship")
 +
 +        #if self.player.score > 320:
 +        #    commands.append("go to level5")
 +
 +        return commands
 +</Code>
 +
 +=== Object.py
 +<Code:Python>
 +import pygame
 +
 +class Object(pygame.sprite.Sprite):
 +    def __init__(self, x, y, width, height, name=None):
 +        super().__init__()
 +        self.rect = pygame.Rect(x, y, width, height)
 +        self.image = pygame.Surface((width, height), pygame.SRCALPHA)
 +        self.width = width
 +        self.height = height
 +        self.name = name
 +
 +    def draw(self, win, offset_x, offset_y):
 +        win.blit(self.image, (self.rect.x - offset_x, self.rect.y + offset_y))
 +
 +</Code>
 +
 +=== Player.py
 +<Code:Python>
 +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, 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//2, Screen.HEIGHT-50)
 +
 +        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.
 +        #self.rect.x = max(0, min(self.rect.x, Screen.WIDTH - self.SIZE))
 +        #self.rect.y = max(0, min(self.rect.y, Screen.HEIGHT - self.SIZE))
 +</Code>
 +
 +=== Sound.py
 +<Code:Python>
 +import pygame as a
 +
 +class Sound:
 +    def __init__(self):
 +        a.mixer.init()
 +        dir = 'assets/sound/'
 +        self.sounds = {}
 +
 +        self.sounds["laser"] = a.mixer.Sound(dir+'laser1.wav')
 +        self.sounds["laser"].set_volume(0.15)
 +        self.sounds["boom"] = a.mixer.Sound(dir+'boom6.wav')
 +        self.sounds["boom7"] = a.mixer.Sound(dir+'boom7.wav')
 +        self.sounds["start"] = a.mixer.Sound(dir+'start4.wav')
 +        self.sounds["pellet"] = a.mixer.Sound(dir+'pellet.wav')
 +
 +    def play(self, sound):
 +        if sound in self.sounds:
 +            self.sounds[sound].play()
 +        else:
 +            print(f"There is no {sound} sound!")
 +</Code>
bullet_game.1725068852.txt.gz · Last modified: 2024/08/31 01:47 by appledog

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki