I am teaching myself python 3, and creating a python based game using pygame but am running into two problems.
In order to post here, I stripped my game down to the bare minimum but there is still quite a bit and I apologize for that. You have to actually be able to run my game to see the problem, so the below is the minimum required code to duplicate the problem. I stripped out all non essentials, walls, weapons, types of characters and images and replaced them with blocks so you can just copy, paste, and run it in Python 3.
This game is a "survive each round" type game.
Every level, there should be more enemy's allowed on the screen at a time. When the player shoots them, they are removed, but another one spawns to take its place.
I have 2 main problems.
The only parts of my code that currently (should be) removing enemy sprites from the sprites list, is if they get shot by the player, or they make contact with the player. About every 20 enemy's or so, one will simply disappear and I can't figure out why.
To the best of my knowledge, I have the enemy spawn set so that they will only spawn if they are at least 500 pixels away in any direction, yet they still will occasionally spawn on top of the player.
The best way to replicate these is playing the first few levels, and watch specifically for enemy's disappearing or spawning super close. It seems to be rng so it may take a few attempts.
I included notes in the code to attempt to save you from having to scan the whole thing.
import pygame,math,random,sys
#Colors
black = (0,0,0)
white = (255,255,255)
red = (188,13,13)
orange = (188,13,13)
yellow = (188,188,13)
green = (101,188,13)
blue_green = (13,188,100)
blue = (13,101,188)
purple = (100,13,188)
magenta = (188,13,101)
background1 = (93,58,23)
texture_1_1 = (116,71,25)
texture_1_2 = (75,57,31)
texture_1_3 = (105,77,49)
class Elven_Arrow_up(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface([4,4])
self.image.fill(black)
self.rect = self.image.get_rect()
def update(self):
self.rect.y -= 4
class Elven_Arrow_down(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface([4,4])
self.image.fill(black)
self.rect = self.image.get_rect()
def update(self):
self.rect.y += 4
class Elven_Arrow_left(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface([4,4])
self.image.fill(black)
self.rect = self.image.get_rect()
def update(self):
self.rect.x -= 4
class Elven_Arrow_right(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface([4,4])
self.image.fill(black)
self.rect = self.image.get_rect()
def update(self):
self.rect.x += 4
class Player(pygame.sprite.Sprite):
def __init__(self,health,speed, x, y):
super().__init__()
self.health = health
self.speed = speed
self.image = pygame.Surface([32,32])
self.image.fill(blue)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.change_x = 0
self.change_y = 0
def changespeed(self, x, y):
self.change_x += x
self.change_y += y
def update(self):
self.rect.x += self.change_x
self.rect.y += self.change_y
class Enemy (pygame.sprite.Sprite):
def __init__(self,speed,health,points):
super().__init__()
self.image = pygame.Surface([32,32])
self.image.fill(red)
self.rect = self.image.get_rect()
self.speed = speed
self.health = health
self.points = points
def update(self):
dirvect = pygame.math.Vector2(player.rect.x - self.rect.x,
player.rect.y - self.rect.y)
dirvect.normalize()
dirvect.scale_to_length(self.speed)
self.rect.move_ip(dirvect)
class GameState():
def __init__(self):
self.state = 'intro'
def intro(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
self.state = 'level'
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
self.state = 'level'
screen.fill(white)
score_font = pygame.font.SysFont('Calibri', 25, True, False)
score_text = score_font.render('Click the Space Bar to Begin.',True,black)
screen.blit(score_text,(screen_width/2 - 180,screen_height/2 + 250))
pygame.display.flip()
def game_over(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
global enemy_list
global all_sprites_list
global player
global score
global max_num_of_enemies
global level
global score_needed
global score_needed_constant
global score_needed_add
global score_needed_add_constant
max_num_of_enemies = max_num_of_enemies_constant
enemy_list = pygame.sprite.Group()
all_sprites_list = pygame.sprite.Group()
player = Player(10,3,screen_width/2-3,screen_width/2,cinder_image)
all_sprites_list.add(player)
score_needed = score_needed_constant
score_needed_add = score_needed_add_constant
score = 0
self.state = 'intro'
screen.fill(black)
score_font = pygame.font.SysFont('Calibri', 25, True, False)
score_text = score_font.render("Click to continue.",True,white)
level_text = score_font.render("You got to Level: " + str(level),True,white)
screen.blit(score_text,(screen_width/2 + 200,screen_height/2 + 350))
screen.blit(level_text,(10,10))
pygame.display.flip()
#Below this line ends the level, removes all sprites from display lists, and returns the player to the center of the screen
def level_complete(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
global enemy_list
global all_sprites_list
global player
global score
global max_num_of_enemies
global max_num_of_enemies_constant
global level
enemy_list = pygame.sprite.Group()
all_sprites_list = pygame.sprite.Group()
all_sprites_list.add(player)
player.change_x = 0
player.change_y = 0
player.rect.x = screen_width/2
player.rect.y = screen_width/2
player.update()
self.state = 'level'
screen.fill(blue)
score_font = pygame.font.SysFont('Calibri', 25, True, False)
continue_text = score_font.render("Release all keys, and Press the Spacebar to Continue.",True,white)
level_text = score_font.render("You Completed level " + str(level-1) + "",True,white)
screen.blit(continue_text,(50,screen_height - 30))
screen.blit(level_text,(screen_width/2-100,screen_height/2))
pygame.display.flip()
#Above this line ends the level, removes all sprites from display, and returns the player to the center of the screen
#Below this line is the main game loop.
def level(self):
global Enemy
global enemy_list
global score
global player
global all_sprites_list
global max_num_of_enemies
global level
global score_needed
global score_needed_add
pygame.mouse.set_visible(1)
#Below this line spawns enemy's, if there are less enemy's than the max number of enemies AND they are far enough from the player
if len(enemy_list.sprites()) < max_num_of_enemies:
x = random.randrange(-500,screen_width+500)
y = random.randrange(-500,screen_height+500)
spawn = True
for enemy in enemy_list:
ex, ey = enemy.rect.center
distance = math.hypot(ex - x, ey - y)
if distance < minimum_distance:
spawn = False
break
if spawn:
speed = 1.5
health = 1
points = 1
enemy = Enemy(speed,health,points)
enemy.rect.center = x, y
enemy_list.add(enemy)
all_sprites_list.add(enemy)
#Above this line spawns enemy's, if there are less enemy's than the max number of enemies AND they are far enough from the player
#Below this line determines when the level will end, increases the number of enemies allowed on the screen, and sends you to the level complete page
if level < 1:
level = 1
elif score >= score_needed:
level +=1
max_num_of_enemies += 3
score_needed += score_needed_add
score_needed_add += 5
player.health += 1
self.state = 'level_complete'
#Above this line determines when the level will end, increases the number of enemies allowed on the screen, and sends you to the level complete page
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.quit()
sys.exit()
if player.health <= 0:
self.state = 'game_over'
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_a:
player.changespeed(-3, 0)
elif event.key == pygame.K_d:
player.changespeed(3, 0)
elif event.key == pygame.K_w:
player.changespeed(0, -3)
elif event.key == pygame.K_s:
player.changespeed(0, 3)
elif event.key == pygame.K_LEFT:
projectile = Elven_Arrow_left()
projectile.rect.x = player.rect.x + 3
projectile.rect.y = player.rect.y + 8
projectile_list.add(projectile)
all_sprites_list.add(projectile)
elif event.key == pygame.K_RIGHT:
projectile = Elven_Arrow_right()
projectile.rect.x = player.rect.x + 3
projectile.rect.y = player.rect.y + 8
projectile_list.add(projectile)
all_sprites_list.add(projectile)
elif event.key == pygame.K_UP:
projectile = Elven_Arrow_up()
projectile.rect.x = player.rect.x + 3
projectile.rect.y = player.rect.y + 8
projectile_list.add(projectile)
all_sprites_list.add(projectile)
elif event.key == pygame.K_DOWN:
projectile = Elven_Arrow_down()
projectile.rect.x = player.rect.x + 3
projectile.rect.y = player.rect.y + 8
projectile_list.add(projectile)
all_sprites_list.add(projectile)
elif event.type == pygame.KEYUP:
if event.key == pygame.K_a:
player.changespeed(3, 0)
elif event.key == pygame.K_d:
player.changespeed(-3, 0)
elif event.key == pygame.K_w:
player.changespeed(0, 3)
elif event.key == pygame.K_s:
player.changespeed(0, -3)
all_sprites_list.update()
if player.rect.y > screen_height + 10:
player.rect.y = -10
elif player.rect.y < -10:
player.rect.y = screen_height + 10
if player.rect.x > screen_width + 10:
player.rect.x = -10
elif player.rect.x < -10:
player.rect.x = screen_width + 10
#Below this line removes an enemy if they are shot by the player, and gives them a point
for projectile in projectile_list:
player_hit_list = pygame.sprite.spritecollide(projectile,enemy_list,True)
for enemy in player_hit_list:
projectile_list.remove(projectile)
all_sprites_list.remove(projectile)
score += 1
#Above this line removes an enemy if they are shot by the player, and gives them a point
if projectile.rect.y < -10:
projectile_list.remove(projectile)
all_sprites_list.remove(projectile)
elif projectile.rect.y > screen_height + 10:
projectile_list.remove(projectile)
all_sprites_list.remove(projectile)
elif projectile.rect.x < -10:
projectile_list.remove(projectile)
all_sprites_list.remove(projectile)
elif projectile.rect.x > screen_width + 10:
projectile_list.remove(projectile)
all_sprites_list.remove(projectile)
#Below this line removes an enemy if they make contact with the player.
for block in enemy_list:
enemy_hit_list = pygame.sprite.spritecollide(player, enemy_list, True)
for block in enemy_hit_list:
enemy_list.remove(block)
all_sprites_list.remove(block)
player.health -= block.points
#Above this line removes an enemy if they make contact with the player.
screen.fill(background1)
for i in texture_list1:
texture1 = pygame.draw.rect(screen,texture_1_1,[i[0],i[1],10,10])
for i in texture_list2:
texture1 = pygame.draw.rect(screen,texture_1_2,[i[0],i[1],10,10])
for i in texture_list3:
texture1 = pygame.draw.rect(screen,texture_1_3,[i[0],i[1],10,10])
all_sprites_list.draw(screen)
score_font = pygame.font.SysFont('Calibri', 25, True, False)
score_text = score_font.render("Score: " + str(score),True,blue_green)
noob_text = score_font.render("w,a,s,d to move, arrow keys to shoot. Good luck.",True,white)
level_text = score_font.render("Level: " + str(level),True,blue_green)
screen.blit(score_text,[10,10])
screen.blit(level_text,[screen_width - 150,10])
if score < 10:
screen.blit(noob_text,[100,10])
if player.health >= health_status[0]:
health_text = score_font.render("Health: " + str(player.health),True,blue_green)
elif player.health >= health_status[1]:
health_text = score_font.render("Health: " + str(player.health),True,green)
elif player.health >= health_status[2]:
health_text = score_font.render("Health: " + str(player.health),True,yellow)
elif player.health >= health_status[3]:
health_text = score_font.render("Health: " + str(player.health),True,orange)
elif player.health >= health_status[4]:
health_text = score_font.render("Health: " + str(player.health),True,red)
screen.blit(health_text, [10,40])
pygame.display.flip()
#Above this line is the main game loop
def state_manager(self):
if self.state == 'intro':
self.intro()
if self.state == 'game_over':
self.game_over()
if self.state == 'level':
self.level()
if self.state == 'level_complete':
self.level_complete()
pygame.init()
screen_width = 1000
screen_height = 800
screen = pygame.display.set_mode([screen_width, screen_height])
game_state = GameState()
enemy_list = pygame.sprite.Group()
projectile_list = pygame.sprite.Group()
item_list = pygame.sprite.Group()
texture_list1 = []
texture_list2 = []
texture_list3 = []
all_sprites_list = pygame.sprite.Group()
player = Player(10,3,screen_width/2 - 3,screen_height/2)
all_sprites_list.add(player)
for i in range(50):
x=random.randrange(screen_width)
y=random.randrange(screen_width)
texture_list1.append([x,y])
for i in range(50):
x=random.randrange(screen_width)
y=random.randrange(screen_width)
texture_list2.append([x,y])
for i in range(50):
x=random.randrange(screen_width)
y=random.randrange(screen_width)
texture_list3.append([x,y])
#Below this line sets the starting points, and constants through the game.
score = 0
health_status = (8,6,4,2,-100)
level = 1
minimum_distance = 500
score_needed_constant = 15
score_needed = score_needed_constant
score_needed_add_constant = 15
score_needed_add = score_needed_add_constant
max_num_of_enemies_constant = 8
max_num_of_enemies = max_num_of_enemies_constant
#Above this line sets the starting points, and constants through the game.
#Below this line starts, and ends the game loop
done = False
clock = pygame.time.Clock()
while not done:
game_state.state_manager()
#Above this line starts, and ends the game loop
clock.tick(60)