p5bg:bullet_game
This is an old revision of the document!
Table of Contents
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.
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("Bullet Game")
window.setSize(Screen.WIDTH, Screen.HEIGHT)
window.setFont("assets/ps2p.ttf", 32)
game = Game(window)
game.start()
if __name__ == "__main__":
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, 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()
Level.py
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
Object.py
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))
p5bg/bullet_game.1725440023.txt.gz · Last modified: 2024/09/04 08:53 by appledog
