There are two problems with my code (I don't know if they're related).
Problem 1: The zombies sometimes don't die when they're shot and instead just remain unphased.
Problem 2: The Master Zombie bugs out after one hit and It doesn't move or appear but is still there (I can tell as it kills the player.
There is no event handler for when the health goes to 0, but I will add that later.
Here is the code:
import pygame
import sys
import math
from math import sin, cos
import random
from random import randint
pygame.init()
screen_width = 800
screen_height = 800
resolution = 64
scale_factor = screen_width / resolution
screen = pygame.display.set_mode((screen_width, screen_height))
screen_rect = screen.get_rect(topleft = (0, 0))
screen_mask = pygame.mask.from_surface(screen)
pygame.display.set_caption("Endless Onslaught")
icon = pygame.image.load("player.png").convert_alpha()
icon = pygame.transform.rotate(icon, 90)
pygame.display.set_icon(icon)
clock = pygame.time.Clock()
gunshot = pygame.mixer.Sound("gunshot.wav")
gunshot.set_volume(0.7)
empty_ammo_sound = pygame.mixer.Sound("empty_ammo.wav")
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load("player.png").convert_alpha()
self.image = pygame.transform.scale(self.image, (int(self.image.get_width()*2), int(self.image.get_height()*2)))
self.original_image = self.image
self.rect = self.image.get_rect(centerx = screen_width//2, centery = screen_height//2)
self.mask = pygame.mask.from_surface(self.image)
self.rotation = 0
self.rotating_left = False
self.rotating_right = False
self.moving_forwards = False
self.moving_backwards = False
self.dx = 0
self.dy = 0
def update(self):
if self.rotating_left:
self.rotation += 3.5
elif self.rotating_right:
self.rotation -= 3.5
speed = 4
self.dx = cos(math.radians(self.rotation))
self.dy = sin(math.radians(self.rotation))
if self.moving_forwards:
self.rect.x += self.dx * speed
self.rect.y -= self.dy * speed
elif self.moving_backwards:
self.dx = cos(math.radians(self.rotation))
self.dy = sin(math.radians(self.rotation))
self.rect.x -= self.dx * speed
self.rect.y += self.dy * speed
if self.rect.centerx <= 0:
self.rect.centerx = 0
elif self.rect.centerx >= screen_width:
self.rect.centerx = screen_width
elif self.rect.centery <= 0:
self.rect.centery = 0
elif self.rect.centery >= screen_height:
self.rect.centery = screen_height
self.image = pygame.transform.rotate(self.original_image, self.rotation)
self.rect = self.image.get_rect(centerx = self.rect.centerx, centery = self.rect.centery)
self.mask = pygame.mask.from_surface(self.image)
def spawn(self):
player_group.add(self)
def despawn(self):
player_group.remove(self)
def shoot(self):
bullet = Bullet(self.rect.centerx,self.rect.centery, self.dx, self.dy, self.rotation)
bullet.spawn()
pygame.mixer.Sound.play(gunshot)
self.rect.x -= self.dx * 2
self.rect.y += self.dy * 2
ammo_bar.ammo -= 1
class Bullet(pygame.sprite.Sprite):
def __init__ (self, x, y, dx, dy, rotation):
super().__init__()
self.image = pygame.image.load("bullet.png").convert_alpha()
self.image = pygame.transform.scale(self.image, (int(self.image.get_width()*1.6), int(self.image.get_height()*1.6)))
self.image = pygame.transform.rotate(self.image, rotation)
self.rect = self.image.get_rect(centerx=x, centery=y)
self.mask = pygame.mask.from_surface(self.image)
self.dx = dx
self.dy = dy
def spawn(self):
bullet_group.add(self)
def despawn(self):
bullet_group.remove(self)
class Zombie(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load("zombie.png").convert_alpha()
self.image = pygame.transform.scale(self.image, (int(self.image.get_width()*1.8), int(self.image.get_height()*1.8)))
self.original_image = self.image
posx = random.choice([randint(-50, -5), randint(805, 850)])
posy = random.choice([randint(-50, -5), randint(805, 850)])
self.rect = self.image.get_rect(centerx=posx, centery= posy)
self.mask = pygame.mask.from_surface(self.image)
def update(self):
distance = math.sqrt((self.rect.x - player.rect.x) ** 2 + (self.rect.y - player.rect.y) ** 2)
if distance > 0:
self.rect.x += (player.rect.x - self.rect.x) / distance
self.rect.y += (player.rect.y - self.rect.y) / distance
rotation = math.atan2(player.rect.y - self.rect.y, player.rect.x - self.rect.x)
self.image = pygame.transform.rotate(self.original_image, -math.degrees(rotation))
self.mask = pygame.mask.from_surface(self.image)
def spawn(self):
zombie_group.add(self)
def despawn(self):
zombie_group.remove(self)
class MasterZombie(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load("master_zombie.png").convert_alpha()
self.image = pygame.transform.scale(self.image, (int(self.image.get_width()*3), int(self.image.get_height()*3)))
self.original_image = self.image
posx = random.choice([randint(-50, -5), randint(805, 850)])
posy = random.choice([randint(-50, -5), randint(805, 850)])
self.rect = self.image.get_rect(centerx=posx, centery= posy)
self.mask = pygame.mask.from_surface(self.image)
self.hp = 5
def update(self):
distance = math.sqrt((self.rect.x - player.rect.x) ** 2 + (self.rect.y - player.rect.y) ** 2)
if distance > 0:
self.rect.x += 3*(player.rect.x - self.rect.x) / distance
self.rect.y += 3*(player.rect.y - self.rect.y) / distance
rotation = math.atan2(player.rect.y - self.rect.y, player.rect.x - self.rect.x)
self.image = pygame.transform.rotate(self.original_image, -math.degrees(rotation))
self.mask = pygame.mask.from_surface(self.image)
def spawn(self):
master_zombie_group.add(self)
def despawn(self):
master_zombie_group.remove(self)
class HealthBar(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.full_health_bar = pygame.image.load("full_health_bar.png").convert_alpha()
self.full_health_bar = pygame.transform.scale(self.full_health_bar, (int(self.full_health_bar.get_width()*1.3), int(self.full_health_bar.get_height()*1.3)))
self.full_health_bar_rect = self.full_health_bar.get_rect(topleft = (10, 5))
self.empty_health_bar = pygame.image.load("empty_health_bar.png").convert_alpha()
self.empty_health_bar = pygame.transform.scale(self.empty_health_bar, (int(self.empty_health_bar.get_width()*1.3), int(self.empty_health_bar.get_height()*1.3)))
self.empty_health_bar_rect = self.empty_health_bar.get_rect(topleft = (10, 5))
self.hp = 5
self.max_hp = 5
def draw(self):
ratio = self.hp / self.max_hp
remaining_width = int(self.full_health_bar.get_width() * ratio)
self.health_bar = self.full_health_bar.copy().subsurface(pygame.Rect(0, 0, remaining_width, self.full_health_bar.get_height()))
screen.blit(self.empty_health_bar, self.empty_health_bar_rect)
screen.blit(self.health_bar, self.full_health_bar_rect)
class AmmoBar(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.full_ammo_bar = pygame.image.load("full_ammo_bar.png").convert_alpha()
self.full_ammo_bar = pygame.transform.scale(self.full_ammo_bar, (int(self.full_ammo_bar.get_width()*3.5), int(self.full_ammo_bar.get_height()*3.5)))
self.full_ammo_bar_rect = self.full_ammo_bar.get_rect(topright = (790, 5))
self.empty_ammo_bar = pygame.image.load("empty_ammo_bar.png").convert_alpha()
self.empty_ammo_bar = pygame.transform.scale(self.empty_ammo_bar, (int(self.empty_ammo_bar.get_width()*3.5), int(self.empty_ammo_bar.get_height()*3.5)))
self.empty_ammo_bar_rect = self.empty_ammo_bar.get_rect(topright = (790, 5))
self.ammo = 25
self.max_ammo = 25
def draw(self):
ratio = self.ammo / self.max_ammo
remaining_width = int(self.full_ammo_bar.get_width() * ratio)
self.ammo_bar = self.full_ammo_bar.copy().subsurface(pygame.Rect(0, 0, remaining_width, self.full_ammo_bar.get_height()))
screen.blit(self.empty_ammo_bar, self.empty_ammo_bar_rect)
screen.blit(self.ammo_bar, self.full_ammo_bar_rect)
class Ammo(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load("bullet.png").convert_alpha()
self.image = pygame.transform.scale(self.image, (int(self.image.get_width()*3), int(self.image.get_height()*3)))
self.image = pygame.transform.rotate(self.image, 90)
posx = randint(0, 800)
posy = randint(50, 800)
self.rect = self.image.get_rect(centerx = posx, centery = posy)
self.mask = pygame.mask.from_surface(self.image)
def spawn(self):
ammo_group.add(self)
def despawn(self):
ammo_group.remove(self)
class GreenGun(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.image.load("green_gun.png").convert_alpha()
self.image = pygame.transform.scale(self.image, (int(self.image.get_width()*3), int(self.image.get_height()*3)))
self.rect = self.image.get_rect(centerx = x, centery = y)
self.mask = pygame.mask.from_surface(self.image)
def spawn(self):
green_gun_group.add(self)
def despawn(self):
green_gun_group.remove(self)
class RedGun(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.image.load("red_gun.png").convert_alpha()
self.image = pygame.transform.scale(self.image, (int(self.image.get_width()*3), int(self.image.get_height()*3)))
self.rect = self.image.get_rect(centerx = x, centery = y)
self.mask = pygame.mask.from_surface(self.image)
def spawn(self):
red_gun_group.add(self)
def despawn(self):
red_gun_group.remove(self)
player_group = pygame.sprite.Group()
player = Player()
player.spawn()
bullet_group = pygame.sprite.Group()
zombie_group = pygame.sprite.Group()
master_zombie_group = pygame.sprite.Group()
zombies = [Zombie() for i in range(0, 35)]
zombies[0].spawn()
ammo_group = pygame.sprite.Group()
ammo = Ammo()
ammo.spawn()
health_bar = HealthBar()
ammo_bar = AmmoBar()
green_gun_group = pygame.sprite.Group()
red_gun_group = pygame.sprite.Group()
background = pygame.image.load("background.jpg").convert()
background = pygame.transform.scale(background, (int(background.get_width()*scale_factor * 2), int(background.get_height()*scale_factor * 2)))
background_rect = background.get_rect(topleft = (0, 0))
def game():
score = 0
zombie_spawning_cooldown = randint(3, 7)
zombie_index = 1
shot_fired = False
running = True
bullet_cooldown= 0
ammo_collected = False
green_gun_collected = True
red_gun_collected = True
gun_state = "normal"
master_zombie_spawned = False
master_zombie_cooldown = 0
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_a:
player.rotating_left = True
elif event.key == pygame.K_d:
player.rotating_right = True
if event.key == pygame.K_w:
player.moving_forwards = True
elif event.key == pygame.K_s:
player.moving_backwards = True
elif event.key == pygame.K_SPACE and bullet_cooldown <= 0:
if not shot_fired:
shot_fired = True
if ammo_bar.ammo > 0:
player.shoot()
if gun_state == "green":
bullet_cooldown = 10
else:
bullet_cooldown = 20
else:
pygame.mixer.Sound.play(empty_ammo_sound)
elif event.type == pygame.KEYUP:
if event.key == pygame.K_a:
player.rotating_left = False
elif event.key == pygame.K_d:
player.rotating_right = False
if event.key == pygame.K_w:
player.moving_forwards = False
elif event.key == pygame.K_s:
player.moving_backwards = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1 and bullet_cooldown <= 0:
if not shot_fired:
shot_fired = True
if ammo_bar.ammo > 0:
player.shoot()
if gun_state == "green":
bullet_cooldown = 10
else:
bullet_cooldown = 20
else:
pygame.mixer.Sound.play(empty_ammo_sound)
if bullet_cooldown > 0:
bullet_cooldown -= 1
if gun_state == "green":
player.image = pygame.image.load("player_green_gun.png").convert_alpha()
player.image = pygame.transform.scale(player.image, (int(player.image.get_width()*2), int(player.image.get_height()*2)))
player.original_image = player.image
ammo_bar.max_ammo = 35
elif gun_state == "red":
player.image = pygame.image.load("player_red_gun.png").convert_alpha()
player.image = pygame.transform.scale(player.image, (int(player.image.get_width()*2), int(player.image.get_height()*2)))
player.original_image = player.image
ammo_bar.max_ammo = 25
else:
player.image = pygame.image.load("player.png").convert_alpha()
player.image = pygame.transform.scale(player.image, (int(player.image.get_width()*2), int(player.image.get_height()*2)))
player.original_image = player.image
ammo_bar.max_ammo = 25
for bullet in bullet_group:
speed = 10
bullet.rect.x += bullet.dx * speed
bullet.rect.y -= bullet.dy * speed
if bullet.rect.right < 0 or bullet.rect.left > screen_width or bullet.rect.bottom < 0 or bullet.rect.top > screen_height:
bullet.despawn()
for zombie in zombie_group:
zombie.update()
if zombie.mask.overlap(player.mask, (player.rect.x - zombie.rect.x, player.rect.y - zombie.rect.y)):
zombie.rect.centerx = random.choice([randint(-50, -5), randint(805, 850)])
zombie.rect.centery = random.choice([randint(-50, -5), randint(805, 850)])
health_bar.hp -=1
if shot_fired:
if bullet.mask.overlap(zombie.mask, (zombie.rect.x - bullet.rect.x, zombie.rect.y - bullet.rect.y)):
if score > 10:
chance_of_drop = randint(0, 5)
if chance_of_drop == 0 and green_gun_collected and gun_state != "green":
green_gun = GreenGun(zombie.rect.centerx, zombie.rect.centery)
green_gun.spawn()
green_gun_collected = False
elif chance_of_drop == 1 and red_gun_collected and gun_state != "red" and score > 15:
red_gun = RedGun(zombie.rect.centerx, zombie.rect.centery)
red_gun.spawn()
red_gun_collected = False
score += 1
if gun_state != "red":
bullet.despawn()
zombie.rect.centerx = random.choice([randint(-50, -5), randint(805, 850)])
zombie.rect.centery = random.choice([randint(-50, -5), randint(805, 850)])
zombie_spawning_cooldown -=1
if zombie_index <= 34 and zombie_spawning_cooldown <= 0:
zombies[zombie_index].spawn()
zombie_index += 1
zombie_spawning_cooldown = randint(3, 7)
if shot_fired:
if score >= 0 and master_zombie_cooldown <= 0 and not master_zombie_spawned:
master_zombie = MasterZombie()
master_zombie.spawn()
master_zombie_cooldown = randint(1000, 2000)
master_zombie_spawned = True
if master_zombie.mask.overlap(player.mask, (player.rect.x - master_zombie.rect.x, player.rect.y - master_zombie.rect.y)):
master_zombie.despawn()
master_zombie_cooldown = randint(1000, 2000)
health_bar.hp -= 5
if bullet.mask.overlap(master_zombie.mask, (master_zombie.rect.x - bullet.rect.x, master_zombie.rect.y - bullet.rect.y)):
bullet.despawn()
master_zombie.hp -= 1
if master_zombie.hp == 0:
master_zombie.despawn()
master_zombie_cooldown = randint(1000, 2000)
score += 5
for ammo in ammo_group:
if ammo.mask.overlap(player.mask, (player.rect.x - ammo.rect.x, player.rect.y - ammo.rect.y)):
ammo.despawn()
ammo_collected = True
if ammo_bar.ammo <= 25:
ammo_bar.ammo += 1
if ammo_collected:
ammo = Ammo()
ammo.spawn()
ammo_collected = False
for green_gun in green_gun_group:
if green_gun.mask.overlap(player.mask, (player.rect.x - green_gun.rect.x, player.rect.y - green_gun.rect.y)):
green_gun.despawn()
green_gun_collected = True
gun_state = "green"
for red_gun in red_gun_group:
if red_gun.mask.overlap(player.mask, (player.rect.x - red_gun.rect.x, player.rect.y - red_gun.rect.y)):
red_gun.despawn()
red_gun_collected = True
gun_state = "red"
screen.blit(background, background_rect)
player.update()
player_group.draw(screen)
ammo_group.draw(screen)
green_gun_group.draw(screen)
red_gun_group.draw(screen)
zombie.update()
zombie_group.draw(screen)
if master_zombie_spawned:
master_zombie.update()
master_zombie_group.draw(screen)
bullet_group.draw(screen)
health_bar.draw()
ammo_bar.draw()
pygame.display.flip()
clock.tick(60)
pygame.quit()
sys.exit()
game()
The game is meant to be an endless zombie shooter, with master zombies that occasionally spawn and take 5 hits to die and also deal 5x more damage. However, they just bug out and don't update or appear after one shot.