Pygame Latency Issue using clock - python

I made a snake game with pygame and it works fine. However, it's sometimes not very responsive to user inputs. I'm using a clock and delay to set how fast the snake should move.
Movement code:
pygame.time.delay(1)
clock.tick(7)
player.move()
render_screen()
Render Screen:
screen.fill((0, 0, 0))
draw_grid()
player.draw()
food.draw()
screen.fill((0, 0, 0), (0, 513, 512, 50))
start.draw()
screen.blit(comic_font.render('Score: ' + str(score), False, (255, 100, 0)), (10, size + 5))
pygame.display.update()
Full Code:
import pygame
import random
pygame.init()
class Snake:
def __init__(self, x, y):
self.x = x
self.y = y
self.speed_x = 1
self.speed_y = 0
segments = []
for a in range(1, 4):
segments.append(Segment(self.x - a, self.y))
self.segments = segments
def move(self):
global score, game_mode
# handle key inputs
keys = pygame.key.get_pressed()
for key in keys:
if self.speed_x == 0:
if keys[pygame.K_a]:
self.speed_x = -1
self.speed_y = 0
if keys[pygame.K_d]:
self.speed_x = 1
self.speed_y = 0
elif self.speed_y == 0:
if keys[pygame.K_w]:
self.speed_x = 0
self.speed_y = -1
if keys[pygame.K_s]:
self.speed_x = 0
self.speed_y = 1
# check for wall collisions
if self.x + self.speed_x < 0 or self.x + self.speed_x > rows - 1 or self.y + self.speed_y > rows - 1 or \
self.y + self.speed_y < 0:
game_mode = "end"
start.text = "RETRY"
return None
# moves snake
self.segments.insert(0, Segment(self.x, self.y))
self.x += self.speed_x
self.y += self.speed_y
# checks for self collisions
for segment in self.segments:
if self.x == segment.x and self.y == segment.y and self.segments.index(segment)!=len(self.segments)-1:
game_mode = "end"
start.text = "RETRY"
break
# checks for eating food
if self.x == food.x and self.y == food.y:
food.x = random.randrange(0, 16)
food.y = random.randrange(0, 16)
score += 1
else:
self.segments.pop()
def draw(self):
for segment in self.segments:
segment.draw()
pygame.draw.rect(screen, (255, 0, 0), (self.x * size / rows, self.y * size / rows, size / rows, size / rows))
class Segment:
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
pygame.draw.rect(screen, (0, 255 - player.segments.index(self), 0),
(self.x * size / rows, self.y * size / rows, size / rows, size / rows))
class Food:
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
pygame.draw.rect(screen, (255, 255, 0), (self.x * size / rows, self.y * size / rows, size / rows, size / rows))
class button:
def __init__(self, x, y, r, g, b, text, font, width, height):
self.x = x
self.y = y
self.r = r
self.b = b
self.g = g
self.text = text
self.font = font
self.width = width
self.height = height
def draw(self):
pygame.draw.rect(screen, (self.r, self.g, self.b), (self.x, self.y, self.width, self.height))
screen.blit(self.font.render(self.text, False, (0, 100, 0)), (self.x + 10, self.y))
def mouse_over(self, pos):
if self.x < pos[0] < self.x + self.width and self.y < pos[1] < self.y + self.height:
return True
def draw_grid():
margin = size / rows
x = 0
y = 0
for line in range(rows):
x = x + margin
y = y + margin
pygame.draw.line(screen, (255, 255, 255), (x, 0), (x, size))
pygame.draw.line(screen, (255, 255, 255), (0, y), (size, y))
def render_screen():
screen.fill((0, 0, 0))
draw_grid()
player.draw()
food.draw()
screen.fill((0, 0, 0), (0, 513, 512, 50))
start.draw()
screen.blit(comic_font.render('Score: ' + str(score), False, (255, 100, 0)), (10, size + 5))
pygame.display.update()
size = 512
rows = 16
screen = pygame.display.set_mode((size, size + 50))
pygame.display.set_caption("Snake")
comic_font = pygame.font.SysFont("Comic Sans MS", 30)
clock = pygame.time.Clock()
running = True
game_mode = "wait"
player = Snake(8, 8)
food = Food(random.randrange(0, 16), random.randrange(0, 16))
start = button(size - 155, size + 5, 0, 255, 0, "PLAY", comic_font, 150, 40)
score = 0
render_screen()
while running:
for event in pygame.event.get():
pos = pygame.mouse.get_pos()
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if start.mouse_over(pos):
if game_mode == "wait":
game_mode = "play"
start.text = "PAUSE"
elif game_mode == "play":
game_mode = "wait"
start.text = "RESUME"
elif game_mode == "end":
game_mode = "play"
start.text = "PAUSE"
score = 0
running = True
game_mode = "wait"
player = Snake(8, 8)
food = Food(random.randrange(0, 16), random.randrange(0, 16))
render_screen()
if game_mode == "play":
pygame.time.delay(1)
clock.tick(7)
player.move()
render_screen()
The full code is above to reproduce the problem.
After experimenting with this a bit, it seems that when the pygame.time.delay(1) is smaller, there will be less delay.
I think that the user gets the input read only between the tics so they have to input exactly on the spot.
I think maybe this is why the input is less behind when the delay is smaller as the delay will affect how long between each input.
Does anyone know why the game doesn't receive inputs well at certain times?

pygame.key.get_pressed() returns a list, with the states of all keyboard buttons. It makes no sense to iterate through this list, because you just want to evaluate the current state of the keys wasd.
Set the speed dependent on the pressed keys:
class Snake:
# [...]
def move(self):
# [...]
# handle key inputs
keys = pygame.key.get_pressed()
if self.speed_x == 0:
if keys[pygame.K_a]:
self.speed_x, self.speed_y = -1, 0
elif keys[pygame.K_d]:
self.speed_x, self.speed_y = 1, 0
elif self.speed_y == 0:
if keys[pygame.K_w]:
self.speed_x, self.speed_y = 0, -1
elif keys[pygame.K_s]:
self.speed_x, self.speed_y = 0, 1
# [...]
pygame.time.Clock.tick delays to keep the game running slower than the given ticks per second. The states are returned by pygame.key.get_pressed() are evaluated, when the events are handled (pygame.event.get()).
Delay the game, after the events have been handled, the snake has been moved and the display was updated:
while running:
for event in pygame.event.get():
# [...]
if game_mode == "play":
player.move()
render_screen()
clock.tick(7) # <---

Related

My collision detection isn't working and way to laggy

So here's just the base function:
def xCollision(self):
for tile in tiles:
if tile.colliderect(self.rect):
if self.x_vel >= 1:
self.rect.right = tile.left
elif self.x_vel <= -1:
self.rect.left = tile.right
def yCollision(self):
for tile in tiles:
if tile.colliderect(self.rect):
if self.y_vel >= 1:
self.rect.bottom = tile.top
elif self.y_vel <= -1:
self.rect.top = tile.bottom
This is inside the Player class.
Now, when i try this my frames go down to around 4 fps and it also isn't working.
About it not working it has to do something with the self.rect.something = tile.something because when i replace it with print() it prints whatever.
Here is the entire code for reference, And yes i just started out so don't think its gonna be any good ok?
import pygame
import random
import math
import time
# Initialize Pygame
pygame.init()
pygame.mixer.init()
# Set the size of the window
size = (900, 600)
screen = pygame.display.set_mode(size)
# Set the title of the window
pygame.display.set_caption("Classes testing")
clock = pygame.time.Clock()
fps = 60
pygame.mixer.music.load('PewPewProject/Retro Platform.wav')
pygame.mixer.music.set_volume(0.1)
pygame.mixer.music.play(-1)
pop_sound = pygame.mixer.Sound("PewPewProject/vine-boom.wav")
pop_sound.set_volume(0.5)
level = [
['1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1'],
['1','E','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','E','0','0','0','1'],
['1','0','0','0','1','1','1','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','1','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','1','0','1','0','0','0','0','0','0','0','0','E','0','0','0','0','1'],
['1','0','0','1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','E','1','1','1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','E','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','1','1','1','0','1','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','1','0','0','0','1','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','1','0','1','1','1','0','E','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','1','0','1','S','1','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','1','0','0','0','1','0','0','0','0','0','1'],
['1','0','0','E','0','0','0','0','0','1','1','1','1','1','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1'],
]
tilex, tiley = [0, 0]
tilex_offset, tiley_offset = [0, 0]
tile_size = 50
tilemap = pygame.image.load('PewPewProject/tilemap.png')
tiles = []
collision_tiles = []
enemy_spawns = []
def Build_Level():
global tiley,tilex
tiley = -1
for rows in level:
tilex = -1
tiley += 1
for tile in rows:
tilex += 1
if tile == '1':
tile_rect = pygame.draw.rect(screen, (100, 100, 100), (tilex*tile_size+tilex_offset, tiley*tile_size+tiley_offset, tile_size, tile_size))
tiles.append(tile_rect)
elif tile == '0':
pass
elif tile == 'E':
enemy_spawns.append([tilex, tiley])
elif tile == 'S':
pygame.draw.rect(screen, (50, 255, 100), (tilex*tile_size+tilex_offset, tiley*tile_size+tiley_offset, tile_size, tile_size))
elif tile == 'R':
pygame.draw.rect(screen, (255, 50, 50), (tilex*tile_size+tilex_offset, tiley*tile_size+tiley_offset, tile_size, tile_size))
else:
print("Wrong Map Input")
def random_except(start, end, exception_start, exception_end):
num = random.randint(start, end)
while num >= exception_start and num <= exception_end:
num = random.randint(start, end)
return num
class Player:
def __init__(self, health, speed, x, y, size, healthboost, collision_tiles):
self.speed = speed
self.x = x
self.y = y
self.y_vel = 0
self.x_vel = 0
self.size = size
self.hitbox = pygame.draw.rect(screen, (230, 100, 100), (self.x, self.y, self.size, self.size))
self.gunsurface = pygame.Surface((90, 25), pygame.SRCALPHA)
self.healthboost = healthboost
self.health = health * healthboost
self.rotated_gunsurface = self.gunsurface
self.gunsurface.fill((150, 150, 150))
self.gunrect = self.gunsurface.get_rect()
self.rect = self.hitbox
self.projectiles = [Projectile(-100, 0, 0)]
self.last_shot_time = 0
self.cooldown = 0.5
self.is_slowed = False
self.alive = True
def xCollision(self):
for tile in tiles:
if tile.colliderect(self.rect):
if self.x_vel >= 1:
self.rect.right = tile.left
elif self.x_vel <= -1:
self.rect.left = tile.right
def yCollision(self):
for tile in tiles:
if tile.colliderect(self.rect):
if self.y_vel >= 1:
self.rect.bottom = tile.top
elif self.y_vel <= -1:
self.rect.top = tile.bottom
def Render(self):
self.rect = pygame.draw.rect(screen, (230, 100, 100), (self.x, self.y, self.size, self.size))
self.Gun()
def Alive(self):
if self.health <= 0:
self.alive = False
def GUI(self):
self.empty_health_bar = pygame.draw.rect(screen, (200, 20, 20), (screen.get_width()/4, 10,466, 25))
self.health_bar = pygame.draw.rect(screen, (20, 200, 20), (screen.get_width()/4, 10, self.health*4.66/self.healthboost, 25))
pygame.draw.line(screen, (0, 0, 0), (screen.get_width()/4, 10),(screen.get_width()/4+466, 10), 2)
pygame.draw.line(screen, (0, 0, 0), (screen.get_width()/4, 35),(screen.get_width()/4+466, 35), 2)
pygame.draw.line(screen, (0, 0, 0), (screen.get_width()/4, 10), (screen.get_width()/4, 35), 2)
pygame.draw.line(screen, (0, 0, 0), (screen.get_width()/4+466, 10), (screen.get_width()/4+466, 35), 2)
def Gun(self):
cursor_x, cursor_y = pygame.mouse.get_pos()
dx = cursor_x - (self.x + self.size)
dy = cursor_y - (self.y + self.size)
angle = math.atan2(dy, dx)
self.rotated_gunsurface = pygame.transform.rotate(self.gunsurface, math.degrees(angle * -1))
self.gunrect = self.rotated_gunsurface.get_rect(center = (self.x+self.size/2, self.y+self.size/2))
screen.blit(self.rotated_gunsurface, (self.gunrect.x, self.gunrect.y))
def Shoot(self):
if pygame.key.get_pressed()[pygame.K_e]:
current_time = time.time()
if current_time - self.last_shot_time > self.cooldown:
pop_sound.play()
cursor_x, cursor_y = pygame.mouse.get_pos()
dx = cursor_x - (self.x + self.size/2)
dy = cursor_y - (self.y + self.size/2)
angle = math.atan2(dy, dx)
self.projectiles.append(Projectile(self.x+self.size/2, self.y+self.size/2, angle))
self.last_shot_time = current_time
self.cooldown = 0.1
def Movement(self):
global tiley_offset, tilex_offset
keys = pygame.key.get_pressed()
if self.x >= screen.get_width()-100-self.size: # Move to the RIGHT
if keys[pygame.K_d]:
tilex_offset -= self.speed
if keys[pygame.K_a]:
self.x_vel = -self.speed
if not self.y <= 100:
if keys[pygame.K_w]:
self.y_vel = -self.speed
else:
if keys[pygame.K_w]:
tiley_offset += self.speed
if not self.y >= screen.get_height()-100-self.size:
if keys[pygame.K_s]:
self.y_vel = self.speed
else:
if keys[pygame.K_s]:
tiley_offset -= self.speed
elif self.x <= 100: # Move to the LEFT
if keys[pygame.K_a]:
tilex_offset += self.speed
if keys[pygame.K_d]:
self.x_vel = self.speed
if not self.y <= 100:
if keys[pygame.K_w]:
self.y_vel = -self.speed
else:
if keys[pygame.K_w]:
tiley_offset += self.speed
if not self.y >= screen.get_height()-100-self.size:
if keys[pygame.K_s]:
self.y_vel = self.speed
else:
if keys[pygame.K_s]:
tiley_offset -= self.speed
elif self.y >= screen.get_height()-100-self.size: # Move DOWN
if keys[pygame.K_s]:
tiley_offset -= self.speed
if keys[pygame.K_w]:
self.y_vel = -self.speed
if keys[pygame.K_a]:
self.x_vel =- self.speed
if keys[pygame.K_d]:
self.x_vel = self.speed
elif self.y <= 100: # Move UP
if keys[pygame.K_w]:
tiley_offset += self.speed
if keys[pygame.K_s]:
self.y_vel = self.speed
if keys[pygame.K_a]:
self.x_vel = -self.speed
if keys[pygame.K_d]:
self.x_vel = self.speed
else: # Default Movement
if keys[pygame.K_w]:
self.y_vel = -self.speed
if keys[pygame.K_s]:
self.y_vel = self.speed
if keys[pygame.K_a]:
self.x_vel = -self.speed
if keys[pygame.K_d]:
self.x_vel = self.speed
self.x += self.x_vel
self.xCollision()
self.y += self.y_vel
self.yCollision()
self.y_vel = 0
self.x_vel = 0
class Projectile:
def __init__(self, x, y, angle):
self.x = x
self.y = y
self.angle = angle
self.speed = 8
self.hitbox = pygame.Rect(self.x, self.y, 5,5)
def Move(self):
self.x += math.cos(self.angle) * self.speed
self.y += math.sin(self.angle) * self.speed
def Render(self):
self.hitbox = pygame.draw.circle(screen, (255, 255, 0), (int(self.x), int(self.y)), 7)
class Enemy:
def __init__(self, health, main_speed, tag, size, player, x, y):
self.health = health
self.main_speed = 3
self.tag = tag
self.size = size
self.player = player
self.speed = 3
self.x = x
self.y = y
self.alive = True
def Destroy(self):
self.alive = False
def Spawning(self):
self.hitbox = pygame.draw.rect(screen, (100, 240, 100), (self.x + tilex_offset, self.y + tiley_offset, self.size, self.size))
def Movement(self):
dx = self.player.x - self.x
dy = self.player.y - self.y
angle = math.atan2(dy - tiley_offset, dx - tilex_offset)
self.x += self.speed * math.cos(angle)
self.y += self.speed * math.sin(angle)
def main():
player = Player(100, 3, screen.get_width()/2-50, screen.get_height()/2-50, 50, 1, tiles)
enemies = []
def Spawn_enemies(num_enemies, player):
for i in range(num_enemies):
x, y = random.choice(enemy_spawns)
enemy = Enemy(1, 1, i, 25, player, x*tile_size, y*tile_size)
enemies.append(enemy)
print(x, y)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Main Loop
screen.fill((100, 100, 200))
# usage
spawn_enemies_chance = random.randint(0, 200)
number_of_enemies = len(enemies)
if spawn_enemies_chance == 1 and number_of_enemies <= 4:
Spawn_enemies(1,player)
Build_Level()
# enemy
for enemy in enemies:
if enemy.alive:
enemy.Movement()
enemy.Spawning()
for projectile in player.projectiles:
if enemy.hitbox.colliderect(projectile.hitbox):
enemy.health -= 1
pop_sound.play()
if enemy.health <= 0:
enemy.alive = False
enemies.remove(enemy)
player.projectiles.remove(projectile)
keys = pygame.key.get_pressed()
if keys[pygame.K_LSHIFT]: # Slow things down by 3 times
player.is_slowed = True
for enemy in enemies:
enemy.speed = enemy.main_speed / 3
for projectile in player.projectiles:
projectile.speed = 2.6
player.speed = 1
player.cooldown = 1.5
spawn_enemies_chance = random.randint(0, 1500)
else:
player.is_slowed = False
for enemy in enemies:
enemy.speed = enemy.main_speed
for projectile in player.projectiles:
projectile.speed = 8
player.speed = 3
player.cooldown = 0.5
spawn_enemies_chance = random.randint(0, 500)
# Get Hurt
for enemy in enemies:
if enemy.hitbox.colliderect(player):
player.health -= 10
enemies.remove(enemy)
# Projectiles
for i, projectile in enumerate(player.projectiles):
projectile.Move()
projectile.Render()
if projectile.x < 0 or projectile.x > screen.get_width() or projectile.y < 0 or projectile.y > screen.get_height():
player.projectiles.pop(i)
# player
player.Alive()
player.Movement()
player.Shoot()
player.Render()
# Renders the UI ontop of everything
player.GUI()
pygame.display.update()
clock.tick(fps)
pygame.quit()
# DO NOT DISTURB!
if __name__ == "__main__":
main()
I honestly don't know why its not working but i think i know why its lagging.
It has to check for collision with every tile every single frame which causes some lag if you have a big map i belive, that's just my thought tho.
The actual problem is not the collision detection, but that Build_Level is called in the application loop. Therefore the tiles and enemy_spawns list contain more and more elements over time and your code gets slower and slower. To solve your problem, separate the function into two functions, build_level and draw_level. Call build_level before the application loop, but draw_level in the application loop:
def build_level():
for tiley, rows in enumerate(level):
for tilex, tile in enumerate(rows):
if tile == '1':
tile_rect = pygame.Rect(tilex*tile_size+tilex_offset, tiley*tile_size+tiley_offset, tile_size, tile_size)
tiles.append(tile_rect)
elif tile == '0':
pass
elif tile == 'E':
enemy_spawns.append([tilex, tiley])
elif tile == 'S':
pass
elif tile == 'R':
pass
else:
print("Wrong Map Input")
def draw_level():
for tiley, rows in enumerate(level):
for tilex, tile in enumerate(rows):
color = None
if tile == '1':
color = (100, 100, 100)
elif tile == 'S':
color = (50, 255, 100)
elif tile == 'R':
color = (255, 50, 50)
if color:
pygame.draw.rect(screen, color, (tilex*tile_size+tilex_offset, tiley*tile_size+tiley_offset, tile_size, tile_size))
def main():
# [...]
build_level()
running = True
while running:
# [...]
draw_level()

What should I do in order to create multiple instances of enemies?

As a beginner, I am struggling to create multiple enemies in pygame. What could I possibly add or implement to my code in order to do so?
Code:
# WORK IN PROGRESS!
# I followed techwithtim's tutorial
# I do not own the images and sounds used in game
# TODO Create multiple Enemies
import pygame
import random
# Screen parameters
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("SPPACCE")
bg = pygame.image.load("bg.png")
font = pygame.font.SysFont('comicsans', 30, True)
clock = pygame.time.Clock()
score = 0
# Music & Sound effects
bulletsound = pygame.mixer.Sound('sounds/bullet_soundeffect.wav')
explosion = pygame.mixer.Sound('sounds/explosion_effect.wav')
explosion2 = pygame.mixer.Sound('sounds/torpedo_explosion.wav')
# Player parameters
class Player(object):
def __init__(self, x, y, height, width):
self.x = x
self.y = y
self.height = height
self.width = width
self.player_vel = 5
def draw(self, screen):
screen.blit(player_char, (self.x, self.y))
# Enemy parameters
class Enemy(object):
def __init__(self, x, y, height, width, end):
self.x = x
self.y = y
self.height = height
self.width = width
self.enemy_vel = 1.5
self.end = end
self.path = [self.x, self.end]
self.hitbox = (self.x + 17, self.y + 2, 65, 65)
self.health = 5
self.visible = True
def draw(self, screen):
self.move()
if self.visible:
self.hitbox = (self.x + 0, self.y, 65, 65)
pygame.draw.rect(screen, (255, 0, 0), self.hitbox, 2)
screen.blit(enemy_char, (self.x, self.y))
# Health bars
pygame.draw.rect(screen, (0, 0, 0), (self.hitbox[0], self.hitbox[1] - 20, 65, 7))
pygame.draw.rect(screen, (255, 0, 0), (self.hitbox[0], self.hitbox[1] - 20, 65 - (12 * (5 - self.health)), 7))
def move(self):
if self.enemy_vel > 0:
if self.x < self.path[1] + self.enemy_vel:
self.x += self.enemy_vel
else:
self.enemy_vel = self.enemy_vel * -1
self.x += self.enemy_vel
else:
if self.x > self.path[0] - self.enemy_vel:
self.x += self.enemy_vel
else:
self.enemy_vel = self.enemy_vel * -1
self.x += self.enemy_vel
def hit(self):
if self.health > 0:
self.health -= 1
else:
self.visible = False
explosion.play()
global score
score += 1
# Player Projectile parameters
class Projectile(object):
def __init__(self, x, y, color, radius):
self.x = x
self.y = y
self.color = color
self.radius = radius
self.vel = 12.5
def draw(self, screen):
pygame.draw.circle(screen, self.color, (self.x, self.y), self.radius)
# Images
player_char = pygame.image.load('sprites/hotdog.png')
enemy_char = pygame.image.load('sprites/hamburger.png')
def blit(): # This draws the sprites
player.draw(screen)
enemy.draw(screen)
for projectile in projectiles:
projectile.draw(screen)
score_text = font.render("Score: " + str(score), 1, (0, 109, 255))
version = font.render("Version 01 ", 1, (51, 153, 255))
screen.blit(score_text, (0, 0))
screen.blit(version, (520, 0))
shootloop = 0
if shootloop > 0:
shootloop += 1
if shootloop > 2:
shootloop = 0
player = Player(300, 400, 64, 64)
enemy = Enemy(random.randint(10, 100), random.randint(20, 100), 64, 64, 480)
enemy_count = random.randint(1, 10)
projectiles = []
run = True
while run:
clock.tick(60)
screen.fill((0, 0, 0))
screen.blit(bg, (0, 0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# Movement keys with playeborders
keys = pygame.key.get_pressed()
if keys[pygame.K_s] and player.y < 480 - player.height - player.player_vel:
player.y += player.player_vel
if keys[pygame.K_w] and player.y > 280:
player.y -= player.player_vel
if keys[pygame.K_d] and player.x < 640 - player.width - player.player_vel:
player.x += player.player_vel
if keys[pygame.K_a] and player.x > player.player_vel:
player.x -= player.player_vel
for projectile in projectiles:
if projectile.y - projectile.radius < enemy.hitbox[1] + enemy.hitbox[3] and projectile.y + projectile.radius > enemy.hitbox[1]:
if enemy.visible == True:
if projectile.x + projectile.radius > enemy.hitbox[0] and projectile.x - projectile.radius < enemy.hitbox[0] + enemy.hitbox[2]:
enemy.hit()
explosion2.play()
projectiles.pop(projectiles.index(projectile))
if projectile.y < 640 and projectile.y > 0:
projectile.y -= projectile.vel
else:
projectiles.pop(projectiles.index(projectile))
# Player shooting
if keys[pygame.K_SPACE] and shootloop == 0:
if len(projectiles) < 1:
projectiles.append(Projectile(round(player.x + player.width //2),
round(player.y + player.height //2), [255, 150, 0], 7))
blit()
pygame.display.update()
I tried to recycle code from my last game project, but it doesn't work because the code structures are way too different from each other. This project is OOP meanwhile the last one has scattered variables.
Similar to your projectiles make a list of Enemy objects:
enemies = []
enemy_count = random.randint(3, 10)
for i in range( enemy_count ):
new_enemy = Enemy(random.randint(10, 100), random.randint(20, 100), 64, 64, 480)
enemies.append( new_enemy )
projectiles = []
Update your blit() to draw a list of enemies:
def blit(): # This draws the sprites
player.draw(screen)
for enemy in enemies:
enemy.draw(screen)
for projectile in projectiles:
projectile.draw(screen)
score_text = font.render("Score: " + str(score), 1, (0, 109, 255))
version = font.render("Version 01 ", 1, (51, 153, 255))
screen.blit(score_text, (0, 0))
screen.blit(version, (520, 0))
And check them all for collisions:
for enemy in enemies:
for projectile in projectiles:
if projectile.y - projectile.radius < enemy.hitbox[1] + enemy.hitbox[3] and projectile.y + projectile.radius > enemy.hitbox[1]:
if enemy.visible == True:
if projectile.x + projectile.radius > enemy.hitbox[0] and projectile.x - projectile.radius < enemy.hitbox[0] + enemy.hitbox[2]:
enemy.hit()
explosion2.play()
projectiles.pop(projectiles.index(projectile))
That should get you started.
The changes are relatively simple because you already have the data compartmentalised in objects. Good job.
EDIT: Re-spawning an enemy is simply a matter of adding another Enemy object to the enemies list:
new_enemy = Enemy(random.randint(10, 100), random.randint(20, 100), 64, 64, 480)
enemies.append( new_enemy )

How can I make objects permanently stick in window without being refreshed in Pygame?

I am trying to recreate Atari Breakout using Pygame. I met with a problem, I put all three rows of the tiles in three lists and I want to print them and make them stay at the location before the ball hits them.
This is the code:
import pygame
import random
pygame.init()
screenWidth = 1200
screenHeight = 700
window = pygame.display.set_mode((screenWidth,screenHeight))
pygame.display.set_caption('Atari Breakout')
pygame.mouse.set_pos(-500,650)
class Plate():
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
def draw_plate(self):
pygame.mouse.set_visible(True)
pos = pygame.mouse.get_pos()
self.x = pos[0]-100
pygame.draw.rect(window, (00,00,255), (self.x, self.y ,self.width, self.height))
class Circle():
def __init__(self, x, y, radius):
self.x = x
self.y = y
self.radius = radius
self.vel_x = 5
self.vel_y = 5
class Tiles():
def __init__(self, x, y, width, height, color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def draw(self):
if self.color == 'red':
pygame.draw.rect(window, (255,0,0), (self.x, self.y, self.width, self.height))
elif self.color == 'green':
pygame.draw.rect(window, (44,176,55), (self.x, self.y, self.width, self.height))
elif self.color == 'blue':
pygame.draw.rect(window, (0,191,255), (self.x, self.y, self.width, self.height))
pygame.display.update()
def draw_titles():
first_row = []
second_row = []
third_row = []
preset_width1 = [70, 120, 200, 30, 240, 140, 130, 120, 80] # nine elements
preset_width2 = [70, 120, 200, 30, 240, 140, 130, 120, 80]
preset_width3 = [70, 120, 200, 30, 240, 140, 130, 120, 80]
random.shuffle(preset_width1)
random.shuffle(preset_width2)
random.shuffle(preset_width3)
#print(f'preset_width1 is: {preset_width1}')
put_width1 = []
put_width2 = []
put_width3 = []
for t in range(1,10):
if t==1:
width = preset_width1.pop(0)
put_width1.append(width)
#print(f'put_width1 is: {put_width1}')
if t==1:
x = 0 + 5
else:
add = sum(put_width1)
#print(f'add is: {add}')
x = t*5 + add
#print(f'x is: {x}')
if t>1:
width = preset_width1.pop(0)
put_width1.append(width)
#print(f'put_width1 is: {put_width1}')
y = 125
height = 35
first_row.append(Tiles(x,y,width,height,'red'))
if t == 9:
break
for t in range(1,10):
if t==1:
width = preset_width2.pop(0)
put_width2.append(width)
if t==1:
x = 0 + 5
else:
add = sum(put_width2)
x = t*5 + add
if t>1:
width = preset_width2.pop(0)
put_width2.append(width)
y = 170
height = 35
second_row.append(Tiles(x,y,width,height,'green'))
if t == 9:
break
for t in range(1,10):
if t==1:
width = preset_width3.pop(0)
put_width3.append(width)
if t==1:
x = 0 + 5
else:
add = sum(put_width3)
x = t*5 + add
if t>1:
width = preset_width3.pop(0)
put_width3.append(width)
y = 215
height = 35
third_row.append(Tiles(x,y,width,height,'blue'))
if t == 9:
break
for num in range(0,9):
first_row[num].draw()
for num in range(0,9):
second_row[num].draw()
for num in range(0,9):
third_row[num].draw()
keys = pygame.key.get_pressed()
if keys[pygame.K_BACKSPACE]:
run = False
# main loop
plate = Plate(10,650,200,40)
ball = Circle(600,300,10)
run = True
start = False
bounds = pygame.Rect(0, 0, 1200, 700)
while run:
pygame.time.Clock().tick(120)
for event in pygame.event.get():
if event == pygame.QUIT:
run = False
plate.draw_plate()
keys = pygame.key.get_pressed()
# bounce algorithem
if keys[pygame.K_SPACE]:
start = True
if start:
ball.y -= ball.vel_y
ball.x += ball.vel_x
if ball.x - ball.radius < bounds.left or ball.x + ball.radius > bounds.right:
ball.vel_x *= -1
if ball.y - ball.radius < bounds.top or ball.y + ball.radius > bounds.bottom:
ball.vel_y *= -1
pygame.draw.rect(window, (0, 0, 0), bounds, 1)
pygame.draw.circle(window, (44,176,55), (ball.x, ball.y), ball.radius)
#pygame.display.update()
draw_titles()
# close call
if keys[pygame.K_BACKSPACE]:
run = False
break
window.fill((0,0,0))
pygame.display.update()
pygame.quit()
This is the ideal situation:
But instead, it refreshes like crazy. I know the problem is I put the draw_titles() function inside of the main While loop. But I believe is the way I code the draw_tiles() function that made it not work. If I place draw_titles() before the loop, the tiles will appear and instantly disappear and both the ball and the plate will not display.
I did some research online and I see tutorial for text and images. For images, they use .blit() but I believe it is only for images.
I had tried many variations to fix this but to no avail. Please help.
Thank you.
Here's a quick fix:
import pygame
import random
pygame.init()
screenWidth = 1200
screenHeight = 700
window = pygame.display.set_mode((screenWidth,screenHeight))
pygame.display.set_caption('Atari Breakout')
pygame.mouse.set_pos(-500,650)
class Plate():
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
def draw_plate(self):
pygame.mouse.set_visible(True)
pos = pygame.mouse.get_pos()
self.x = pos[0]-100
pygame.draw.rect(window, (00,00,255), (self.x, self.y ,self.width, self.height))
class Circle():
def __init__(self, x, y, radius):
self.x = x
self.y = y
self.radius = radius
self.vel_x = 5
self.vel_y = 5
class Tiles():
def __init__(self, x, y, width, height, color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def draw(self):
if self.color == 'red':
pygame.draw.rect(window, (255,0,0), (self.x, self.y, self.width, self.height))
elif self.color == 'green':
pygame.draw.rect(window, (44,176,55), (self.x, self.y, self.width, self.height))
elif self.color == 'blue':
pygame.draw.rect(window, (0,191,255), (self.x, self.y, self.width, self.height))
first_row = []
second_row = []
third_row = []
def create_titles():
preset_width1 = [70, 120, 200, 30, 240, 140, 130, 120, 80] # nine elements
preset_width2 = [70, 120, 200, 30, 240, 140, 130, 120, 80]
preset_width3 = [70, 120, 200, 30, 240, 140, 130, 120, 80]
random.shuffle(preset_width1)
random.shuffle(preset_width2)
random.shuffle(preset_width3)
#print(f'preset_width1 is: {preset_width1}')
put_width1 = []
put_width2 = []
put_width3 = []
for t in range(1,10):
if t==1:
width = preset_width1.pop(0)
put_width1.append(width)
#print(f'put_width1 is: {put_width1}')
if t==1:
x = 0 + 5
else:
add = sum(put_width1)
#print(f'add is: {add}')
x = t*5 + add
#print(f'x is: {x}')
if t>1:
width = preset_width1.pop(0)
put_width1.append(width)
#print(f'put_width1 is: {put_width1}')
y = 125
height = 35
first_row.append(Tiles(x,y,width,height,'red'))
if t == 9:
break
for t in range(1,10):
if t==1:
width = preset_width2.pop(0)
put_width2.append(width)
if t==1:
x = 0 + 5
else:
add = sum(put_width2)
x = t*5 + add
if t>1:
width = preset_width2.pop(0)
put_width2.append(width)
y = 170
height = 35
second_row.append(Tiles(x,y,width,height,'green'))
if t == 9:
break
for t in range(1,10):
if t==1:
width = preset_width3.pop(0)
put_width3.append(width)
if t==1:
x = 0 + 5
else:
add = sum(put_width3)
x = t*5 + add
if t>1:
width = preset_width3.pop(0)
put_width3.append(width)
y = 215
height = 35
third_row.append(Tiles(x,y,width,height,'blue'))
if t == 9:
break
# main loop
plate = Plate(10,650,200,40)
ball = Circle(600,300,10)
run = True
start = False
bounds = pygame.Rect(0, 0, 1200, 700)
create_titles()
while run:
pygame.time.Clock().tick(120)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
# bounce algorithem
if keys[pygame.K_SPACE]:
start = True
# close call
if keys[pygame.K_BACKSPACE]:
run = False
break
if start:
ball.y -= ball.vel_y
ball.x += ball.vel_x
if ball.x - ball.radius < bounds.left or ball.x + ball.radius > bounds.right:
ball.vel_x *= -1
if ball.y - ball.radius < bounds.top or ball.y + ball.radius > bounds.bottom:
ball.vel_y *= -1
window.fill((0,0,0))
plate.draw_plate()
pygame.draw.rect(window, (0, 0, 0), bounds, 1)
pygame.draw.circle(window, (44,176,55), (ball.x, ball.y), ball.radius)
for tile in first_row:
tile.draw()
for tile in second_row:
tile.draw()
for tile in third_row:
tile.draw()
pygame.display.update()
pygame.quit()
When drawing to the screen, clear the screen surface first, then draw all objects, like this:
...
window.fill((0,0,0))
plate.draw_plate()
pygame.draw.rect(window, (0, 0, 0), bounds, 1)
pygame.draw.circle(window, (44,176,55), (ball.x, ball.y), ball.radius)
for tile in first_row:
tile.draw()
for tile in second_row:
tile.draw()
for tile in third_row:
tile.draw()
...
Note how all drawing related stuff is in one place. This way, it's clearer and less confusing.
You should make sure to only call pygame.display.flip() (or .update()) only once per frame (as Rabbid76 already said in a comment).
I also moved the lists outside the draw_titles function, which I renamed to create_titles. The function creates the tiles, so it should only be done once, not every frame.
You should also probably look into pygame's Sprite and Group class. Here's an example I hacked together which makes use of some pygame features:
import pygame
import random
class Paddle(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, bounds, *grps):
super().__init__(*grps)
self.image = pygame.Surface((width, height))
self.image.fill((0,0,255))
self.rect = self.image.get_rect(topleft=(x, y))
self.bounds = bounds
def update(self, dt):
pos = pygame.mouse.get_pos()
self.rect.centerx = pos[0]
self.rect.clamp_ip(self.bounds)
class Circle(pygame.sprite.Sprite):
def __init__(self, x, y, radius, bounds, *grps):
super().__init__(*grps)
self.image = pygame.Surface((radius, radius))
self.image.set_colorkey((1, 2, 3))
self.image.fill((1, 2, 3))
self.rect = self.image.get_rect(topleft=(x, y))
pygame.draw.circle(self.image, (44,176,55), (radius//2, radius//2), 5)
self.vel = pygame.Vector2((5, 5))
self.pos = self.rect.center
self.bounds = bounds
def update(self, dt):
self.pos += self.vel * min(dt/15, 10)
self.rect.center = self.pos
if self.rect.left < self.bounds.left or self.rect.right > self.bounds.right:
self.vel.x *= -1
if self.rect.top < self.bounds.top or self.rect.bottom > self.bounds.bottom:
self.vel.y *= -1
self.rect.clamp_ip(self.bounds)
def bounce(self, sprite):
if self.rect.top <= sprite.rect.top or sprite.rect.bottom >= sprite.rect.bottom:
self.vel.y *= -1
elif self.rect.left <= sprite.rect.left or sprite.rect.right >= sprite.rect.right:
self.vel.x *= -1
class Tiles(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, color, *grps):
super().__init__(*grps)
self.image = pygame.Surface((width, height))
self.image.fill(color)
self.rect = self.image.get_rect(topleft=(x, y))
def hit(self):
self.kill()
def main():
pygame.init()
screen = pygame.display.set_mode((1200, 700))
screen_rect = screen.get_rect()
pygame.display.set_caption('Atari Breakout')
sprites = pygame.sprite.Group()
tiles = pygame.sprite.Group()
paddle = Paddle(10,650,200,40, screen_rect, sprites)
ball = Circle(600,300,10, screen_rect, sprites)
preset = [70, 120, 200, 30, 240, 140, 130, 120, 80]
y = 215
for color in ['blue', 'green', 'red']:
x = 5
line = preset[:]
random.shuffle(line)
for width in line:
Tiles(x, y, width, 35, pygame.Color(color), sprites, tiles)
x += width + 5
y -= 45
dt = 0
clock = pygame.time.Clock()
while True:
pygame.time.Clock().tick(120)
# events
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_BACKSPACE:
return
# game logic
tile = pygame.sprite.spritecollideany(ball, tiles)
if tile:
tile.hit()
ball.bounce(tile)
if pygame.sprite.collide_rect(paddle, ball):
ball.bounce(paddle)
sprites.update(dt)
# drawing
screen.fill((0,0,0))
sprites.draw(screen)
pygame.draw.rect(screen, (0, 0, 0), screen_rect, 1)
pygame.display.update()
dt = clock.tick(120)
pygame.quit()
if __name__ == '__main__':
main()

Collision between masks in pygame

I have a problem with collisions in my game, spaceship's mask doesn't collide properly with a background and I believe that offset is a problem, however I'm not sure.
I've tried multiple collision techniques and checked a lot of answers to my problem, but none of them helped me.
import pygame as pg
import os
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (2, 29)
pg.init()
def gameLoop():
display_width = 1800
display_height = 1000
pg.display.set_caption("Kerbal Space Landing Simulator")
clock = pg.time.Clock()
display = pg.display.set_mode((display_width, display_height))
sprsp = pg.image.load('C:/Users/PC/PycharmProjects/untitled/sprspaceship.png').convert_alpha()
cosbg = pg.image.load('C:/Users/PC/PycharmProjects/untitled/cosmos bg.png').convert_alpha()
done = False
class Spaceship:
def __init__(self, x, y, mass):
self.x = x
self.y = y
self.width = 139
self.height = 106
self.velx = 0
self.vely = 0
self.mass = mass
self.color = (255, 255, 255)
self.spr = sprsp
self.fuel = 500
self.mask = pg.mask.from_surface(self.spr)
self.angle = 0
self.changerot = 0
def check_controls(self):
if keys[pg.K_SPACE] and self.fuel > 0:
if self.angle > 0:
self.vely += 0.005 * (self.angle - 90)
self.velx += -0.005 * self.angle
else:
self.vely += -0.005 * (self.angle + 90)
self.velx += -0.005 * self.angle
self.fuel += -3
if keys[pg.K_LEFT] and self.angle < 90:
self.angle += 2
if keys[pg.K_RIGHT] and self.angle > -90:
self.angle += -2
def update_pos(self):
self.vely += 0.01
self.x += self.velx
self.y += self.vely
self.mask = pg.mask.from_surface(self.spr)
def update_rotation(self):
self.rspr = pg.transform.rotate(self.spr, self.angle)
self.changerot -= self.angle
def draw(self):
if self.fuel > 0:
pg.draw.rect(display, (255, 255, 255), (display_width - 100, 100 + 500 - self.fuel, 10, self.fuel), 0)
display.blit(self.rspr, (int(self.x), int(self.y)))
self.changerot = 0
class Terrain(object):
def __init__(self, x, y):
self.x = x
self.y = y
self.mask = pg.mask.from_threshold(display, (160, 160, 160))
self.hitbox = pg.Rect(self.x, self.y, display_width, 500)
self.ox = display_width // 2 - self.x // 2
self.oy = display_height // 2 - self.y // 2
def draw(self):
pg.draw.rect(display, (160, 160, 160), (self.x, self.y, display_width, 500), 0)
spaceship = (Spaceship(500, 100, 1))
terrain = (Terrain(0, 800))
def redrawGameWindow():
display.blit(cosbg, (0, 0))
spaceship.draw()
terrain.draw()
pg.display.update()
def check_for_collisions():
offset = (int(spaceship.x - terrain.ox), int(spaceship.y - terrain.oy))
print(offset)
print(spaceship.mask.overlap(terrain.mask, offset))
return spaceship.mask.overlap(terrain.mask, offset)
# return spaceship.hitbox.colliderect(terrain.hitbox)
# return pg.sprite.spritecollide(spaceship.spr, terrain.mask, False, pg.sprite.collide_mask)
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
keys = pg.key.get_pressed()
mouse_pressed = pg.mouse.get_pressed()
x, y = pg.mouse.get_pos()
spaceship.check_controls()
spaceship.update_pos()
spaceship.update_rotation()
if check_for_collisions() is not None:
print('Hit! You\'ve hit the ground with the speed:', spaceship.vely)
exit()
redrawGameWindow()
clock.tick(60)
gameLoop()
Spaceship doesn't collide with the surface. I know how to fix it using simpler code, but in future I want to use randomly generated terrain. Could you help me with these collisions?
The mask for the Terrain is never set. Crate a proper Terrain mask:
class Terrain(object):
def __init__(self, x, y):
self.x = x
self.y = y
maskSurf = pg.Surface((display_width, display_height)).convert_alpha()
maskSurf.fill(0)
pg.draw.rect(maskSurf, (160, 160, 160), (self.x, self.y, display_width, 500), 0)
self.mask = pg.mask.from_surface(maskSurf)
print(self.mask.count())
# [...]
When using pygame.mask.Mask.overlap(), then you've to check the overlapping of the Spaceship and the Terrain, rather than the Terrain and the Spaceship.
Since the Terrain mask is a mask of the entire screen, the offset for the overlap() test is the position of the Spaceship:
def check_for_collisions():
offset = (int(spaceship.x), int(spaceship.y))
collide = terrain.mask.overlap(spaceship.mask, offset)
print(offset, collide)
return collide
Minimal example: repl.it/#Rabbid76/PyGame-SurfaceMaskIntersect
See also: Mask

How to trigger an event when at least one round in a for loop is "True"

I am making a space invader/dodging game in pygame. I have items that you can touch to e.g. increase health. When the sprite touches the health item, I want the background to be green momentarily.
Below is what I have at the moment. Since I have more than 1 item on the screen at a time, I check for each time using (line 1). You can see that when touching potion, the fillcolor is set to green
The problem is if the sprite is only touching 1 of the 2 items in the screen, the background will be set to black when checking for the second one.
How do I make it so that if at least 1 of the 2 items is being touched, the background becomes green?
EDIT:
I had made the question too vague in fear of making it too long, so here are more details.
fillcolor is the variable that sets the background color of the window. There are three types of items that you can touch, and a maximum of 2 items can appear at once, regardless of the type of item.
This might be a bit long, but what is going wrong is:
By using the for loop, I am checking the 2 items on the screen, and checking if the item you hit is a potion, ammunition or "fever mode"(powerup item). As you can see if it is "potion", your health is increased and if it is "ammo" your ammo count is being increased. For example if there are two items on the screen and you are touching one of them, which is a potion. Then the background becomes green, however in the next round of the for loop when checking the second item, fillcolor instantly becomes black because you are not touching both of the items, only one of them. What is want to do is to make the background green if you are touching one of them, even if the second one is not touched.
for e in items:
ship.checkItemCollision(e, ship)
if ship.checkItemCollision(e, ship) == 'potion':
print('potion')
ship.health += 0.5
fillcolor = (0, 255, 0)
touchDatItem = True
elif ship.checkItemCollision(e, ship) == 'ammo':
print('ammo')
ammoCount += 1
fillcolor = (255, 255, 0)
touchDatItem = True
elif ship.checkItemCollision(e, ship) == 'fever':
print('fever')
feverMode = True
fillcolor = (255, 0, 0)
touchDatItem = True
elif not touchDatItem:
fillcolor = black
Here's the whole code:
import pygame as pg
import time
import random
import math
pg.init()
display_width = 800
display_height = 600
black = (0, 0, 0)
white = (255, 255, 255)
red = (200, 0, 0)
bllue = (0, 0, 255)
green = (0, 200, 0)
bright_red =(255, 0, 20)
bright_green = (0, 255, 0)
yellow = (255,255,0)
dark_yellow = (150, 150, 0)
clock = pg.time.Clock()
potion = pg.image.load('revive.png')
ammo = pg.image.load('ammo.png')
fever = pg.image.load('fever.png')
gameDisplay = pg.display.set_mode((display_width, display_height))
pg.display.set_caption('Object Oriented')
class Item:
def __init__(self):
self.items = [potion, potion, potion, ammo, ammo,ammo, ammo, ammo, fever, fever, fever, fever, fever, fever, fever, fever, fever, fever, fever, fever, fever, ]
self.images = potion
self.speed = 3
self.width = 30
self.height = 30
self.x = 30
self.y = random.randrange(-1000, -300)
def move(self):
self.y += self.speed
if self.y > display_height:
self.x = random.randrange(0, (display_width - self.width))
self.y = random.randrange(-5000, -1000)
self.images = random.choice(self.items)
def draw(self):
gameDisplay.blit(self.images, (self.x, self.y))
class Thing:
def __init__(self):
self.width = 20
self.height = 20
self.x = random.randrange(0, (display_width - self.width))
self.y = random.randrange(-500, 0)
self.speedY = 3
self.speedX = 3
self.color = bright_red
self.ratio = random.randrange(-3, 3)
def move(self, count):
if self.ratio == 0:
self.y += self.speedY
else:
self.y += self.speedY
## self.x += random.randint(-5, 5)
self.x += self.ratio
if self.y > display_height:
self.x = random.randrange(0, (display_width - self.width))
self.y = random.randrange(-500, 0)
self.ratio = random.randrange(-3, 3)
return True
def draw(self):
pg.draw.rect(gameDisplay, self.color, [self.x, self.y, self.height, self.width])
## def randomizeX(self):
## self.x = random.randrange(0, (display_width - self.width))
## def resetY(self):
## self.y = 05
##def checkQuit():
## for event in pg.event.get():
## if event.type == pg.QUIT:
## pg.quit()
## quit()
class Ship:
def __init__(self):
self.x = display_width / 2
self.y = display_height / 2
self.speed = 10
self.height = 20
self.width = 20
self.color = yellow
self.changeX = 0
self.changeY = 0
self.health = 100
## def move(self, event):
##
## if event.type == pg.KEYDOWN:
## print(event.key)
## if event.key == pg.K_LEFT:
## self.change = -(self.speed)
## if event.key == pg.K_RIGHT:
## self.change = self.speed
## if event.type == pg.KEYUP:
## if event.key == pg.K_LEFT or event.key == pg.K_RIGHT:
## self.change = 0
## self.x += self.change
def draw(self):
pg.draw.rect(gameDisplay, self.color, [self.x, self.y, self.height, self.width])
def moveShip(self, event):
if event.type == pg.KEYDOWN:
## print(self.changeY)
## print(self.changeX)
if event.key == pg.K_LEFT:
self.changeX = -(self.speed)
if event.key == pg.K_RIGHT:
self.changeX = self.speed
if event.key == pg.K_UP:
self.changeY = -(self.speed)
if event.key == pg.K_DOWN:
self.changeY = self.speed
if event.type == pg.KEYUP:
if event.key == pg.K_LEFT or event.key == pg.K_RIGHT or event.key == pg.K_UP or event.key == pg.K_DOWN:
self.changeX = 0
self.changeY = 0
def testWallCollision(self):
if self.x > (display_width - self.width) or self.x < 0:
self.health = self.health/2
def checkThingCollision(self, t, ship, fillcolor, red, count):
# if thing_starty < (y + car_height) and y < (thing_starty+thing_height):
if (t.y - (t.height/2)) < (ship.y + ship.height) and ship.y < ((t.y - (t.height/2)) + t.height):
if (self.x > t.x and self.x < (t.x + t.width) or ((self.x + t.width) > t.x and (self.x + t.width) < t.x + t.width)):
self.health -= 0.5
t.x = random.randrange(0, (display_width - t.width))
t.y = random.randrange(-500, 0)
t.ratio = random.randrange(-10, 10)
def checkItemCollision(self, e, ship):
if e.y < (ship.y + ship.height) and ship.y < (e.y + e.height):
if (self.x > e.x and self.x < (e.x + e.width) or ((self.x + e.width) > e.x and (self.x + e.width) < e.x + e.width)):
if e.images == potion:
return 'potion'
elif e.images == ammo:
return 'ammo'
elif e.images == fever:
return 'fever'
class Bullet:
def __init__(self, ship):
self.speed = 20
self.color = white
self.x = ship.x + (ship.width / 2)
self.y = ship.y + (ship.width / 2)
self.height = 5
self.width = 5
def draw(self):
## print('IN DRAAAAAW')
## if event.key == pg.K_SPACE:
pg.draw.rect(gameDisplay, self.color, [self.x, self.y, self.height, self.width])
def move(self, ship):
self.y -= self.speed
## if self.y < 0:
## self.x = ship.x + (ship.width / 2)
## self.y = ship.y + (ship.width / 2)
def checkCollision(self, t, ship, count):
if t.y < (self.y + self.height) and self.y < (t.y + t.height):
if (self.x > t.x and self.x < (t.x + t.width) or ((self.x + t.width) > t.x and (self.x + t.width) < t.x + t.width)):
t.x = random.randrange(0, (display_width - t.width))
t.y = random.randrange(-500, 0)
t.ratio = random.randrange(-10, 10)
self.y = -self.height
return True
def healthNum(health, color):
font = pg.font.SysFont(None, 25)
text = font.render('health:' + str(health) + '/100', True, color)
gameDisplay.blit(text, (500, 0))
def ammoNum(ammoCount, color):
font = pg.font.SysFont(None, 25)
text = font.render('ammo:' + str(ammoCount), True, color)
gameDisplay.blit(text, (300, 0))
def things_dodged(count):
font = pg.font.SysFont(None, 25)
text = font.render('score: ' + str(count), True, white)
gameDisplay.blit(text, (0, 0))
def main_loop():
touchDatItem = False
feverTimer = 0
gameExit = False
allItems = [potion, ammo]
things = []
ship = Ship()
bullets = []
fillcolor = black
count = 0
items = []
ammoCount = 20
FEVER = False
LIST = []
feverMode = False
for t in range (30):
things.append(Thing())
for e in range(2):
items.append(Item())
while not gameExit:
print(fillcolor)
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
quit()
ship.moveShip(event)
if event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
## FEVER = True
if ammoCount > 0:
bullets.append(Bullet(ship))
if not feverMode:
ammoCount -= 1
if feverMode:
FEVER = True
else:
FEVER = False
if event.type == pg.KEYUP:
if event.key == pg.K_SPACE:
FEVER = False
if FEVER == True:
feverTimer += 1
if not feverTimer > 100:
if ammoCount > 0:
bullets.append(Bullet(ship))
else:
print('STAAAAAAAAAP')
FEVER = False
feverTimer = 0
feverMode = False
ship.x += ship.changeX
ship.y += ship.changeY
ship.testWallCollision()
gameDisplay.fill(fillcolor)
LIST = []
healthNum(ship.health, white)
ammoNum(ammoCount, white)
for t in things:
ship.checkThingCollision(t, ship, fillcolor, red, count)
if ship.checkThingCollision(t, ship, fillcolor, red, count) == True:
print('###########################')
ship.color = red
else:
ship.color = yellow
t.draw()
ship.draw()
t.move(count)
if t.move(count) == True:
count+= 1
for b in bullets:
b.draw()
for t in things:
b.checkCollision(t, ship, count)
if b.checkCollision(t, ship, count) == True:
count += 10
b.move(ship)
for e in items:
ship.checkItemCollision(e, ship)
if ship.checkItemCollision(e, ship) == 'potion':
LIST.append('potion')
print('potion')
ship.health += 0.5
touchDatItem = True
elif ship.checkItemCollision(e, ship) == 'ammo':
LIST.append('ammo')
print('ammo')
ammoCount += 1
touchDatItem = True
elif ship.checkItemCollision(e, ship) == 'fever':
LIST.append('fever')
print('fever')
feverMode = True
touchDatItem = True
if 'potion' in LIST:
fillcolor = (0, 255, 0)
elif 'ammo' in LIST:
fillcolor = (255, 255, 0)
elif 'fever' in LIST:
fillcolor = (255, 0, 0)
else:
fillcolor = black
e.draw()
e.move()
print('fillcolor = ' + str(fillcolor))
if ship.health < 1:
ship.health = 0
pg.quit()
quit()
things_dodged(count)
pg.display.update()
clock.tick(60)
pg.quit()
quit()
main_loop()
That happens when you invent your own collision detection function instead of using pygame's collision detection methods. ;) Your checkItemCollision method is incorrect.
Change this line ...
if (self.x > e.x and self.x < (e.x + e.width) or ((self.x + e.width) > e.x and (self.x + e.width) < e.x + e.width)):
to this:
if (self.x > e.x and self.x < e.x + e.width or self.x + self.width > e.x and self.x + self.width < e.x + e.width):
I'm still not 100% sure if everything is correct now. I'd give your objects a self.rect attribute and use it for the collision detection instead, e.g.:
# In __init__:
self.rect = pg.Rect(self.x, self.y, self.width, self.height)
# Then check if they collide with the `colliderect` method.
self.rect.colliderect(e.rect)
The rect needs to be moved as well when you update the positions.

Categories