User Tools

Site Tools


bullet_game

Differences

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

Link to this comparison view

Next revision
Previous revision
bullet_game [2024/08/31 01:35] – created appledogbullet_game [2024/09/11 06:43] (current) – created appledog
Line 2: Line 2:
 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. 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.
  
-== Screen and Window +We began by "just writing" gamethen the second part was refactoring the gameI wanted to make refactoring it's own project. What are the goals of this refactoring?
-The first thing we always do is have Screen class and a Window class. The Screen class defines things like WIDTH and HEIGHTand the Window class holds the pygame screen and other pygame initialization.+
  
-Initially, this code would appear at the start of a main.py. Later we moved it into class Game's __init__. However, to keep things clean we made it into it's own class.+* Refactoring 
 +** Encapsulation 
 +** Light Commentary (code commentary) exIN and OUT for functions.
  
-=== class Screen:+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> <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> </Code>
  
-=== class Window: +=== Screen,py 
-==== def __init__ +<Code:Python>
-This calls pygame.init(). See [[main]]+
  
 +class Screen:
 +    WIDTH = 800
 +    HEIGHT = 600
 +    FPS = 60
  
-==== full code+    MARGIN 96 
 +    GAMETOP MARGIN 
 +    GAMEBOT HEIGHT - MARGIN 
 +</Code> 
 + 
 + 
 +=== Game.py
 <Code:Python> <Code:Python>
 import pygame import pygame
 +import random
 +import pickle
  
-class Window: +from Screen import * 
-    def __init__(self): +from Window import * 
-        pygame.init()+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 *
  
-    def setLogo(self, filename): 
-        self.logo = pygame.image.load(filename) 
-        pygame.display.set_icon(self.logo) 
-        return self.logo 
  
-    def setCaption(self, cap): +class Game: 
-        pygame.display.set_caption(cap)+    def __init__(self, window): 
 +        self.window = window 
 +        self.sound = Sound()
  
-    def setSize(self, width, height):+        # 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.width = width
         self.height = height         self.height = height
-        self.size (width, height) +        self.name name
-        self.screen = pygame.display.set_mode(self.size) +
-        return self.screen+
  
-    def setFont(self, filenamesize): +    def draw(self, winoffset_x, offset_y): 
-        pygame.font.init(+        win.blit(self.image, (self.rect.x - offset_xself.rect.y + offset_y))
-        self.font = pygame.font.Font(filenamesize)+
  
-        font_width, font_height = self.font.size("@"+</Code>
-        self.fontwidth = font_width +
-        self.fontheight = size+
  
-        return self.font</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.1725068113.txt.gz · Last modified: 2024/08/31 01:35 by appledog

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki