Pygame - collisions and lists - python

I'm trying to code my first game with Python and Pygame. I'm fairly new to Python and coding in general, so I'm sorry if my code is hard to read.
There are 4 circles, moving from the middle of the screen to the 4 edges.
I'm dropping cubes (enemies) from the 4 edges to the middle. The goal is to stop those cubes from reaching the middle of the screen, by pressing keyboard arrows. I
Now I'm working on the logic of lifes. The player has 5 lifes.
If an enemy reaches the middle of the screen, the player loses a life. I got this part.
The player also loses a life when he misscliks. I'm struggling with this part.
I can see a few scenario. Let's say that the player clicks LEFT:
There are no enemies on the left side --> he loses a life
There is 1 enemy on the left side, but there is no collision --> he loses a life
There are more than 1 enemy on the left side, and he click on one enemy. Right now, the player loses a life per enemy outside the circle even though he clicked right. I can't figure out a way to solve this issue. I solved the 2 scenarios above.
Any idea ?
Here's my code.
# ---------- Packages and Inits ----------
import pygame, random, math
pygame.init()
# ---------- Settings ----------
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600
FPS = 60
SPEED = 1
SPEED_ENEMIES = 0.5
CIRCLE_RADIUS = 50
ENEMY_SIZE = 40
red = (255,000,000)
blue = (000,000,255)
yellow = (255,255,000)
green = (000,128,000)
pink = (255,192,203)
black = (000,000,000)
# ---------- Classes ----------
class Enemies:
def __init__(self, x, y, size=ENEMY_SIZE, thick=5, color=blue, speed=SPEED_ENEMIES, position="top"):
self.rect = pygame.Rect(0, 0, size, size)
if ( x == 0 and y == 0 ):
self.randomise()
self.rect.centerx = x
self.rect.centery = y
self.size = size
self.thick = thick
self.color = color
self.speed = speed
self.calcDirection()
self.position = position
def calcDirection( self ):
self.x_float = 1.0 * self.rect.centerx
self.y_float = 1.0 * self.rect.centery
# Determine direction vector from (x,y) to the centre of the screen
self.position_vector = pygame.math.Vector2( self.x_float, self.y_float )
self.velocity_vector = pygame.math.Vector2( SCREEN_WIDTH/2 - self.x_float, SCREEN_HEIGHT/2 - self.y_float )
self.velocity_vector = self.velocity_vector.normalize()
def update( self ):
x_delta = self.speed * self.velocity_vector[0]
y_delta = self.speed * self.velocity_vector[1]
self.x_float += x_delta
self.y_float += y_delta
self.rect.centerx = int( self.x_float )
self.rect.centery = int( self.y_float )
def draw(self, screen):
pygame.draw.rect(screen, self.color, self.rect )
def reachedPoint( self, x, y ):
return self.rect.collidepoint( x, y )
def randomise( self ):
self.rect.centerx = SCREEN_WIDTH//2
self.rect.centery = SCREEN_HEIGHT//2
side = random.randint( 0, 4 )
if ( side == 0 ):
self.rect.centery = SCREEN_HEIGHT
self.color = green
self.position= "bot"
elif ( side == 1 ):
self.rect.centery = 0
self.color = yellow
self.position= "top"
elif ( side == 2 ):
self.rect.centerx = 0
self.color = blue
self.position= "left"
else:
self.rect.centerx = SCREEN_WIDTH
self.color = red
self.position= "right"
self.calcDirection()
class Circle:
def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=5, color=blue, speed=SPEED, position="top"):
self.rect = pygame.Rect(0, 0, 2*radius, 2*radius)
self.rect.centerx = x
self.rect.centery = y
self.radius = radius
self.thick = thick
self.color = color
self.speed = speed
self.position = position
if speed >= 0:
self.directionX = 'right'
self.direction = 'up'
else:
self.directionX = 'left'
self.direction = 'down'
def draw(self, screen):
pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick)
def swing(self):
if self.position == "top":
self.rect.y -= self.speed
if self.rect.top <= 0 and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom > int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "bot":
self.rect.y -= self.speed
if self.rect.top < int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "left":
self.rect.x -= self.speed
if self.rect.right > int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
elif self.rect.left <= 0 and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
if self.position == "right":
self.rect.x -= self.speed
if self.rect.left < int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
elif self.rect.right >= SCREEN_WIDTH and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
def isCollision(self, enemyX, enemyY, circleX, circleY):
distance = math.sqrt((math.pow(enemyX-circleX,2))+(math.pow(enemyY-circleY,2)))
if distance < 65:
return True
else:
return False
# ---------- Main ----------
def main():
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
screen_rect = screen.get_rect()
clock = pygame.time.Clock()
game_over = False
lifes = 5
score = 0
myFont = pygame.font.SysFont("monospace", 25)
# Start with 4 enemies
all_enemies = [
Enemies(int(SCREEN_WIDTH/2) , 0 , color = yellow, position = "top"),
Enemies(int(SCREEN_WIDTH/2) , SCREEN_HEIGHT-ENEMY_SIZE, color = green , position = "bot"),
Enemies(0 ,int(SCREEN_HEIGHT/2) , color = blue , position = "left"),
Enemies(SCREEN_WIDTH-ENEMY_SIZE, int(SCREEN_HEIGHT/2) , color = red , position = "right")
]
# Start with 4 circles
all_circles = [
Circle(screen_rect.centerx, screen_rect.centery - 2*CIRCLE_RADIUS, position="top"),
Circle(screen_rect.centerx, screen_rect.centery + 2*CIRCLE_RADIUS, position="bot"),
Circle(screen_rect.centerx + 2*CIRCLE_RADIUS, screen_rect.centery, position="right"),
Circle(screen_rect.centerx - 2*CIRCLE_RADIUS, screen_rect.centery, position="left")
]
while not game_over:
screen.fill(black)
# Reference enemy lists
left_enemies = [x for x in all_enemies if x.position == "left"]
right_enemies = [x for x in all_enemies if x.position == "right"]
top_enemies = [x for x in all_enemies if x.position == "top"]
bot_enemies = [x for x in all_enemies if x.position == "bot"]
# Place and swing 4 circles (the player)
for c in all_circles:
c.draw(screen)
c.swing()
# The enemy reaches the middle
for e in all_enemies:
e.update()
if ( e.reachedPoint( SCREEN_WIDTH//2, SCREEN_HEIGHT//2 ) ):
lifes -=1
e.randomise()
e.draw( screen )
# Score points with keyboard arrow
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
for c in all_circles:
if event.type == pygame.KEYDOWN:
# LEFT
if event.key == pygame.K_LEFT and c.position == "left":
if not left_enemies :
lifes -=1
for e in left_enemies:
collision = c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)
if e.position == "left" and collision == True :
score +=1
e.randomise()
else:
lifes -=1
# RIGHT
if event.key == pygame.K_RIGHT and c.position == "right":
if not right_enemies :
lifes -=1
for e in right_enemies:
collision = c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)
if e.position == "right" and collision == True :
score +=1
e.randomise()
else:
lifes -=1
# TOP
if event.key == pygame.K_UP and c.position == "top":
if not top_enemies :
lifes -=1
for e in top_enemies:
collision = c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)
if e.position == "top" and collision == True :
score +=1
e.randomise()
else:
lifes -=1
# BOT
if event.key == pygame.K_DOWN and c.position == "bot":
if not bot_enemies :
lifes -=1
for e in bot_enemies:
collision = c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)
if e.position == "bot" and collision == True :
score +=1
e.randomise()
else:
lifes -=1
print_lifes = myFont.render("Lifes:" + str(lifes), 1, red)
screen.blit(print_lifes, (10, SCREEN_HEIGHT-50))
print_score = myFont.render("Score:" + str(score), 1, red)
screen.blit(print_score, (10, 10))
pygame.display.update()
clock.tick(FPS)
main()
pygame.quit()

Your rules can be broken down to one simply rule:
If the player presses a key, look if there's an enemy under the circle.
So on KEYDOWN, filter the enemy list for all enemies that collide with the circle and just check if the number is > 0.
Replace your checks/loops:
if not left_enemies:
lifes -=1
for e in left_enemies:
collision = c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)
if e.position == "left" and collision == True :
score +=1
e.randomise()
else:
lifes -=1
with something like this:
hits = [e for e in left_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery) and position == "left"]
if not hits:
lifes -=1
for e in hits:
score +=1
e.randomise()

Related

i have a problem: my enemies are stacked vertically, on top of my player which is weird, and they don't move to their predefined positions [duplicate]

This question already exists:
in my shooting game, my players are positioned on top of my head (player) which is very abnormal and also once the game starts, they start shooting [closed]
Closed 6 months ago.
i have earlier tried to format my question properly but i hope this one is better. am creating a shooter game from https://github.com/russs123/Shooter. however my problem is that my enemies are stacked vertically upwards in a straight line when i run my game and the enemies don't start off at their predefined location. below is the full code i wrote. perhaps if you can run it, it will be better understood.
import pygame, sys
from player import Player
import os
import random
import csv
pygame.init()
screen_width = 600
scroll_thresh = 200
screen_scroll = 0
bg_scroll = 0
screen_height = int(screen_width * 0.8)
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Shooter Game")
clock = pygame.time.Clock()
fps = 60
# define game variables
GRAVITY = 0.75
level = 1
ROWS = 16
COLS = 150
TILE_SIZE = screen_height // ROWS
TILE_TYPES = 21
img_list = []
for x in range(TILE_TYPES):
img = pygame.image.load(f"img/tile/{x}.png")
img = pygame.transform.scale(img, (TILE_SIZE, TILE_SIZE))
img_list.append(img)
# color variables
bg = (144, 201, 120)
RED = (255, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
BLACK = (0, 0, 0)
# define player action variables
moving_left = False
moving_right = False
shoot = False
grenade = False
grenade_thrown = False
# load up images
pine1_img = pygame.image.load("img/Background/pine1.png").convert_alpha()
pine2_img = pygame.image.load("img/Background/pine2.png").convert_alpha()
mountain_img = pygame.image.load("img/Background/mountain.png").convert_alpha()
sky_cloud_img = pygame.image.load("img/Background/sky_cloud.png").convert_alpha()
bullet_img = pygame.image.load("img/icons/bullet.png").convert_alpha()
grenade_img = pygame.image.load("img/icons/grenade.png").convert_alpha()
# pick up boxes
health_box_img = pygame.image.load("img/icons/health_box.png").convert_alpha()
ammo_box_img = pygame.image.load("img/icons/ammo_box.png").convert_alpha()
grenade_box_img = pygame.image.load("img/icons/grenade_box.png").convert_alpha()
# name_dt = type(name)
item_boxes = {
"Health": health_box_img,
"Ammo": ammo_box_img,
"Grenade": grenade_img,
}
# define fonts
font = pygame.font.SysFont("Futura", 30)
def draw_text(text, font, text_col, x, y):
img = font.render(text, True, text_col)
screen.blit(img, (x, y))
def draw_bg():
screen.fill(bg)
screen.blit(sky_cloud_img, (0, 0))
screen.blit(mountain_img, (0, screen_height - mountain_img.get_height() - 300))
screen.blit(pine1_img, (0, screen_height - pine1_img.get_height() - 150))
screen.blit(pine2_img, (0, screen_height - pine2_img.get_height()))
class Soldier(pygame.sprite.Sprite):
def __init__(self, char_type, x, y, scale, speed, ammo, grenades):
pygame.sprite.Sprite.__init__(self)
self.alive = True
self.char_type = char_type
self.speed = speed
self.ammo = ammo
self.start_ammo = ammo
self.shoot_cooldown = 0
self.grenades = grenades
self.health = 100
self.max_health = self.health
self.direction = 1
self.vel_y = 0
self.jump = False
self.in_air = True
self.flip = False
self.animation_list = []
self.frame_index = 0
self.action = 0
# ai specific variables
self.move_counter = 0
self.vision = pygame.Rect(0, 0, 150, 20)
self.idling = 0
self.idling_counter = 0
self.update_time = pygame.time.get_ticks()
# load all images for the players
animation_types = ["idle", "run", "jump", "death"]
for animation in animation_types:
temp_list = []
# reset temporary list of images
# count number of files in the folder
num_of_frames = len(os.listdir(f"img/{self.char_type}/{animation}"))
for i in range(num_of_frames):
img = pygame.image.load(f"img/{self.char_type}/{animation}/{i}.png").convert_alpha()
img = pygame.transform.scale(img, (int(img.get_width() * scale), int(img.get_height() * scale)))
temp_list.append(img)
self.animation_list.append(temp_list)
self.image = self.animation_list[self.action][self.frame_index]
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.width = self.image.get_width()
self.height = self.image.get_height()
def update(self):
self.update_animation()
self.check_alive()
# update cooldown
if self.shoot_cooldown > 0:
self.shoot_cooldown -= 1
def move(self, moving_left, moving_right):
screen_scroll = 0
dx = 0
dy = 0
if moving_left:
dx = -self.speed
self.flip = True
self.direction = -1
if moving_right:
dx = self.speed
self.flip = False
self.direction = 1
if self.jump == True and self.in_air == False:
self.vel_y = -11
self.jump = False
self.in_air = True
# apply gravity
self.vel_y += GRAVITY
if self.vel_y > 10:
self.vel_y
dy += self.vel_y
# check for collision
for tile in world.obstacle_list:
# check collision in the x direction
if tile[1].colliderect(self.rect.x + dx, self.rect.y, self.width, self.height):
dx = 0
# check for collision in the y direction
if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.width, self.height):
# check if below the ground, i.e jumping
if self.vel_y < 0:
self.vel_y = 0
dy = tile[1].bottom - self.rect.top
# check if above the ground, ie falling
elif self.vel_y >= 0:
self.vel_y = 0
self.in_air = False
dy = tile[1].top - self.rect.bottom
self.rect.x += dx
self.rect.y += dy
# update scroll based on player position
if self.char_type == "player":
if self.rect.right > screen_width - scroll_thresh or self.rect.left < scroll_thresh:
self.rect.x = -dx
screen_scroll = -dx
return screen_scroll
def shoot(self):
if self.shoot_cooldown == 0 and self.ammo > 0:
self.shoot_cooldown = 20
bullet = Bullet(self.rect.centerx + (0.75 * self.rect.size[0] * self.direction), self.rect.centery,
self.direction)
bullet_group.add(bullet)
# reduce ammo after each shot
self.ammo -= 1
def ai(self):
if self.alive and player.alive:
if self.idling == False and random.randint(1, 200) == 1:
self.update_action(0)
self.idling = True
self.idling_counter = 50
# check if ai is near the player
if self.vision.colliderect(player.rect):
# stop running and face the player
self.update_action(0)
self.shoot()
else:
if self.idling == False:
if self.direction == 1:
ai_moving_right = True
else:
ai_moving_right = False
ai_moving_left = not ai_moving_right
self.move(ai_moving_left, ai_moving_right)
self.update_action(1)
self.move_counter += 1
# update ai vision as the enemy moves
self.vision.center = (self.rect.centerx + 75 * self.direction, self.rect.centery)
if self.move_counter > TILE_SIZE:
self.direction *= -1
self.move_counter *= -1
else:
self.idling_counter -= 1
if self.idling_counter <= 0:
self.idling = False
# scroll
self.rect.x = screen_scroll
def update_animation(self):
ANIMATION_COOLDOWN = 100
# update image depending on current index
self.image = self.animation_list[self.action][self.frame_index]
# check if enough time has passed since the last update
if pygame.time.get_ticks() - self.update_time > ANIMATION_COOLDOWN:
self.update_time = pygame.time.get_ticks()
self.frame_index += 1
# if animation has run out then restart to the first
if self.frame_index >= len(self.animation_list[self.action]):
if self.action == 3:
self.frame_index = len(self.animation_list[self.action]) - 1
else:
self.frame_index = 0
def check_alive(self):
if self.health <= 0:
self.health = 0
self.speed = 0
self.alive = False
self.update_action(3)
def update_action(self, new_action):
# check if new action is different from the previous one
if new_action != self.action:
self.action = new_action
# update animation settings
self.frame_index = 0
self.update_time = pygame.time.get_ticks()
def draw(self):
screen.blit(pygame.transform.flip(self.image, self.flip, False), self.rect)
class HealthBar():
def __init__(self, health, x, y, max_health):
self.x = x
self.y = y
self.health = health
self.max_health = max_health
def draw(self, health):
self.health = health
pygame.draw.rect(screen, BLACK, (self.x - 2, self.y - 2, 154, 24))
pygame.draw.rect(screen, RED, (self.x, self.y, 150, 20))
ratio = self.health / self.max_health
pygame.draw.rect(screen, GREEN, (self.x, self.y, 150 * ratio, 20))
class World():
def __init__(self):
self.obstacle_list = []
def process_data(self, data):
for y, row in enumerate(data):
for x, tile in enumerate(row):
if tile >= 0:
img = img_list[tile]
img_rect = img.get_rect()
img_rect.x = x * TILE_SIZE
img_rect.y = y * TILE_SIZE
tile_data = (img, img_rect)
if 0 <= tile <= 8:
self.obstacle_list.append(tile_data)
elif 9 <= tile <= 10:
# water
water = Water(img, x * TILE_SIZE, y * TILE_SIZE, )
water_group.add(water)
pass
elif 11 <= tile <= 14:
# decoration
decoration = Decoration(img, x * TILE_SIZE, y * TILE_SIZE)
decoration_group.add(decoration)
elif tile == 15:
# create a player
player = Soldier("player", x * TILE_SIZE, y * TILE_SIZE, 1.65, 5, 20, 5)
health_bar = HealthBar(10, 10, player.health, player.health)
elif tile == 16:
# create enemy
enemy = Soldier("enemy", x * TILE_SIZE, y * TILE_SIZE, 1.65, 2, 20, 0)
enemy_group.add(enemy)
elif tile == 17:
# create ammo box
item_box = ItemBox("Ammo", x * TILE_SIZE, y * TILE_SIZE)
item_box_group.add(item_box)
elif tile == 18:
# create grenade box
item_box = ItemBox("Grenade", x * TILE_SIZE, y * TILE_SIZE)
item_box_group.add(item_box)
elif tile == 19:
# create health box
item_box = ItemBox("Health", x * TILE_SIZE, y * TILE_SIZE)
item_box_group.add(item_box)
elif tile == 20:
# create exit point
exit = Exit(img, x * TILE_SIZE, y * TILE_SIZE, )
exit_group.add(exit)
return player, health_bar
def draw(self):
for tile in self.obstacle_list:
tile[1][0] += screen_scroll
screen.blit(tile[0], tile[1])
class Decoration(pygame.sprite.Sprite):
def __init__(self, img, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = img
self.rect = self.image.get_rect()
self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height()))
def update(self):
self.rect.x += screen_scroll
class Water(pygame.sprite.Sprite):
def __init__(self, img, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = img
self.rect = self.image.get_rect()
self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height()))
def update(self):
self.rect.x += screen_scroll
class Exit(pygame.sprite.Sprite):
def __init__(self, img, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = img
self.rect = self.image.get_rect()
self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height()))
class ItemBox(pygame.sprite.Sprite):
def __init__(self, item_type, x, y):
pygame.sprite.Sprite.__init__(self)
self.item_type = item_type
self.image = item_boxes[self.item_type]
self.rect = self.image.get_rect()
self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height()))
def update(self):
# scroll
self.rect.x += screen_scroll
# check if player has picked up the box
if pygame.sprite.collide_rect(self, player):
# check what kind of box it was
if self.item_type == "Health":
player.health += 25
if player.health > player.max_health:
player.health = player.max_health
elif self.item_type == "Ammo":
player.ammo += 15
elif self.item_type == "Grenade":
player.grenades += 3
# delete the item box
self.kill()
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, direction):
pygame.sprite.Sprite.__init__(self)
self.speed = 10
self.image = bullet_img
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.direction = direction
def update(self):
self.rect.x += self.direction * self.speed
# check if bullet has left the screen
if self.rect.right < 0 and self.rect.left > screen_width:
self.kill()
# check for collision with level
for tile in world.obstacle_list:
if tile[1].colliderect(self.rect):
self.kill()
# check collision with characters
if pygame.sprite.spritecollide(player, bullet_group, False):
if player.alive:
player.health -= 5
self.kill()
for enemy in enemy_group:
if pygame.sprite.spritecollide(enemy, bullet_group, False):
if enemy.alive:
enemy.health -= 25
self.kill()
class Grenade(pygame.sprite.Sprite):
def __init__(self, x, y, direction):
pygame.sprite.Sprite.__init__(self)
self.timer = 100
self.vel_y = -11
self.speed = 7
self.image = grenade_img
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.direction = direction
self.width = self.image.get_width()
self.height = self.image.get_height()
def update(self):
self.vel_y += GRAVITY
dx = self.direction * self.speed
dy = self.vel_y
# check for collision with level
for tile in world.obstacle_list:
# check if grenade has hit a wall
if tile[1].colliderect(self.rect.x + dx, self.rect.y, self.width, self.height):
self.direction *= -1
dx = self.direction * self.speed
if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.width, self.height):
self.speed = 0
# check if below the ground, i.e thrown
if self.vel_y < 0:
self.y = 0
dy = tile[1].bottom - self.rect.top
# check if above the ground, ie falling
elif self.vel_y >= 0:
self.vel_y = 0
dy = tile[1].top - self.rect.bottom
# update grenade position
self.rect.x += dx
self.rect.y += dy
# explosion countdown
self.timer -= 1
if self.timer <= 0:
self.kill()
explosion = Explosion(self.rect.x, self.rect.y, 0.5)
explosion_group.add(explosion)
# do damage to anyone nearby
if abs(self.rect.centerx - player.rect.centerx) < TILE_SIZE * 2 and \
abs(self.rect.centery - player.rect.centery) < TILE_SIZE * 2:
player.health -= 50
for enemy in enemy_group:
if abs(self.rect.centerx - enemy.rect.centerx) < TILE_SIZE * 2 and \
abs(self.rect.centery - enemy.rect.centery) < TILE_SIZE * 2:
enemy.health -= 50
class Explosion(pygame.sprite.Sprite):
def __init__(self, x, y, scale):
pygame.sprite.Sprite.__init__(self)
self.images = []
for num in range(1, 6):
img = pygame.image.load(f"img/explosion/exp{num}.png").convert_alpha()
img = pygame.transform.scale(img, (int(img.get_width() * scale), (int(img.get_height() * scale))))
self.images.append(img)
self.frame_index = 0
self.image = self.images[self.frame_index]
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.counter = 0
def update(self):
EXPLOSION_SPEED = 4
# update explosion animation
self.counter += 1
if self.counter >= EXPLOSION_SPEED:
self.counter = 0
self.frame_index += 1
# if animation is complete, then delete the explosion
if self.frame_index >= len(self.images):
self.kill()
else:
self.image = self.images[self.frame_index]
# create sprite groups
enemy_group = pygame.sprite.Group()
bullet_group = pygame.sprite.Group()
grenade_group = pygame.sprite.Group()
explosion_group = pygame.sprite.Group()
item_box_group = pygame.sprite.Group()
decoration_group = pygame.sprite.Group()
water_group = pygame.sprite.Group()
exit_group = pygame.sprite.Group()
# create empty tile list
world_data = []
for row in range(ROWS):
r = [-1] * COLS
world_data.append(r)
# load in level data and create world
with open(f"level{level}_data.csv", newline="") as csvfile:
reader = csv.reader(csvfile, delimiter=",")
for x, row in enumerate(reader):
for y, tile in enumerate(row):
world_data[x][y] = int(tile)
print(world_data)
world = World()
player, health_bar = world.process_data(world_data)
run = True
while run:
clock.tick(fps)
draw_bg()
world.draw()
health_bar.draw(player.health)
# show ammo
draw_text("AMMO:", font, WHITE, 10, 35)
for x in range(player.ammo):
screen.blit(bullet_img, (90 + (x * 10), 40))
# show grenade
draw_text("GRENADES: ", font, WHITE, 10, 65)
for x in range(player.grenades):
screen.blit(grenade_img, (130 + (x * 15), 66))
player.update()
player.draw()
for enemy in enemy_group:
enemy.ai()
enemy.update()
enemy.draw()
# update and draw groups
bullet_group.update()
grenade_group.update()
explosion_group.update()
item_box_group.update()
decoration_group.update()
water_group.update()
exit_group.update()
bullet_group.draw(screen)
grenade_group.draw(screen)
explosion_group.draw(screen)
item_box_group.draw(screen)
decoration_group.draw(screen)
water_group.draw(screen)
exit_group.draw(screen)
# update player actions, 1 is run, 0 = idle, 2 = in air
if player.alive:
# shoot bullet
if shoot:
player.shoot()
elif grenade and grenade_thrown == False and player.grenades > 0:
grenade = Grenade(player.rect.centerx + (0.5 * player.rect.size[0] * player.direction),
player.rect.top, player.direction)
grenade_group.add(grenade)
# reduce grenades
player.grenades -= 1
grenade_thrown = True
if player.in_air:
player.update_action(2)
elif moving_left or moving_right:
player.update_action(1)
else:
player.update_action(0)
screen_scroll = player.move(moving_left, moving_right)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# keyboard presses
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_a:
moving_left = True
if event.key == pygame.K_d:
moving_right = True
if event.key == pygame.K_SPACE:
shoot = True
if event.key == pygame.K_q:
grenade = True
if event.key == pygame.K_w and player.alive:
player.jump = True
if event.key == pygame.K_ESCAPE:
run = False
# keyboard button released
if event.type == pygame.KEYUP:
if event.key == pygame.K_a:
moving_left = False
if event.key == pygame.K_d:
moving_right = False
if event.key == pygame.K_SPACE:
shoot = False
if event.key == pygame.K_q:
grenade = False
grenade_thrown = False
pygame.display.update()
pygame.quit()
i have looked at this code for days but cant seem to find out the problem exactly. so, the question once again is how to properly arrange the enemies on the screen at their various predefined positions and not directly on top of my player.

Detecting collision with the sides of a GroupSingle and Group

When the frisk sprite is colliding with any sprite in npc_group, I want the former to move back the opposite direction of the side it's moving into, so that it doesn't move through it. I already have the collision detection down, but how do I detect the side of the sprites in npc_group being collided with to achieve this effect?
import pygame
from pygame import sprite
pygame.init()
screen = pygame.display.set_mode((640,480))
pygame.display.set_caption("UNDERRUNE")
clock = pygame.time.Clock()
keys_pressed = pygame.key.get_pressed() #updates which keys are being pressed
class Frisk(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
friskd1 = pygame.transform.scale(pygame.image.load('assets/frisk_d1.png').convert_alpha(),(38,58))
friskd2 = pygame.transform.scale(pygame.image.load('assets/frisk_d2.png').convert_alpha(),(38,58))
friskd3 = pygame.transform.scale(pygame.image.load('assets/frisk_d3.png').convert_alpha(),(38,58))
friskd4 = pygame.transform.scale(pygame.image.load('assets/frisk_d4.png').convert_alpha(),(38,58))
self.frisk_d = [friskd1,friskd2,friskd3,friskd4]
friskr1 = pygame.transform.scale(pygame.image.load('assets/frisk_r1.png').convert_alpha(),(34,58))
friskr2 = pygame.transform.scale(pygame.image.load('assets/frisk_r2.png').convert_alpha(),(34,58))
self.frisk_r = [friskr1,friskr2,friskr1,friskr2]
frisku1 = pygame.transform.scale(pygame.image.load('assets/frisk_u1.png').convert_alpha(),(38,58))
frisku2 = pygame.transform.scale(pygame.image.load('assets/frisk_u2.png').convert_alpha(),(38,58))
frisku3 = pygame.transform.scale(pygame.image.load('assets/frisk_u3.png').convert_alpha(),(38,58))
frisku4 = pygame.transform.scale(pygame.image.load('assets/frisk_u4.png').convert_alpha(),(38,58))
self.frisk_u = [frisku1,frisku2,frisku3,frisku4]
friskl1 = pygame.transform.scale(pygame.image.load('assets/frisk_l1.png').convert_alpha(),(34,58))
friskl2 = pygame.transform.scale(pygame.image.load('assets/frisk_l2.png').convert_alpha(),(34,58))
self.frisk_l = [friskl1,friskl2,friskl1,friskl2]
self.walkframe = 0
self.image = self.frisk_d[self.walkframe]
self.rect = self.image.get_rect(center = (320,240))
#self.collisionbox = self.get_rect(bottom = (frisk.x,f))
self.facing = 1
self.vel = 3
self.moving = False
self.moving_x = False
self.moving_y = False
#self.movement = {moveup: False, movedown: False, moveleft: False, moveright:}
def player_input(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_UP] and self.rect.top > 0:
self.rect.y -= self.vel
self.facing = 0
self.moving_y = True
elif keys[pygame.K_DOWN] and self.rect.bottom < 480:
self.rect.y += self.vel
self.facing = 1
self.moving_y = True
else:
self.moving_y = False
if keys[pygame.K_LEFT] and self.rect.left > 0:
self.rect.x -= self.vel
self.facing = 2
self.moving_x = True
elif keys[pygame.K_RIGHT] and self.rect.right < 640:
self.rect.x += self.vel
self.facing = 3
self.moving_x = True
else:
self.moving_x = False
# RUNNING
if keys[pygame.K_LSHIFT]: self.vel = 10
else: self.vel = 6
def frisk_anim(self):
if self.facing == 0:
self.image = self.frisk_u[int(self.walkframe)]
elif self.facing == 1:
self.image = self.frisk_d[int(self.walkframe)]
elif self.facing == 2:
self.image = self.frisk_l[int(self.walkframe)]
elif self.facing == 3:
self.image = self.frisk_r[int(self.walkframe)]
self.walkframe += 0.2
if self.walkframe >= len(self.frisk_d): self.walkframe = 0
if self.moving_x == False and self.moving_y == False: self.walkframe = 0
def collision(self):
if pygame.sprite.spritecollide(self,npc_group,False):
self.rect.x += self.vel
def update(self):
self.player_input()
self.frisk_anim()
self.collision()
class NPC(pygame.sprite.Sprite):
def __init__(self, type):
super().__init__()
if type == 'npc1':
npc1_1 = pygame.transform.scale(pygame.image.load('assets/npc1_1.png').convert_alpha(),(38,58))
npc1_2 = pygame.transform.scale(pygame.image.load('assets/npc1_2.png').convert_alpha(),(38,58))
self.npc_image = [npc1_1,npc1_2]
self.location = (100,100)
if type == 'npc2':
npc2 = pygame.transform.scale(pygame.image.load('assets/npc1_2.png').convert_alpha(),(55,58))
self.npc_image = [npc2]
self.location = (250,50)
self.image = self.npc_image[0]
self.rect = self.image.get_rect(topleft = self.location)
# GROUPS
frisk = pygame.sprite.GroupSingle() # group contains sprite
frisk.add(Frisk())
npc_group = pygame.sprite.Group()
npc_group.add(NPC('npc1'))
# GAME LOOP
while True:
# BACKGROUND
#screen.blit(pygame.image.load('assets/bg.png').convert_alpha(),(0,0))
screen.fill((0,0,0))
# QUIT
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
# FRISK
frisk.draw(screen)
frisk.update()
npc_group.draw(screen)
pygame.display.update() # updates the display surface
clock.tick(30) # tells pygame that this while True loop shouldn't run faster then 60 fps
You know which direction you are moving, so you also know which side you hit an object on. The side of the collision depends on the relative movement of the 2 objects. The relative movement is the difference in movement between the player and the obstacle. If the obstacle stands still, it is the player's movement.
if pygame.sprite.spritecollide(self,npc_group,False):
if self.facing == 0:
self.rect.y += self.vel
elif self.facing == 1:
self.rect.y -= self.vel
elif self.facing == 2:
self.rect.y += self.vel
elif self.facing == 3:
self.rect.y -= self.vel
Alternatively, you can estimate the side by calculating the difference in object position and finding the side with the minimum distance. Note pygame.sprite.spritecollide return a list containing all intersecting sprites:
hit = pygame.sprite.spritecollide(self, npc_group, False):
if hit:
hit_rect = hit[0].rect
dr = abs(self.rect.right - hit_rect.left)
dl = abs(self.rect.left - hit_rect.right)
db = abs(self.rect.bottom - hit_rect.top)
dt = abs(self.rect.top - hit_rect.bottom)
if min(dl, dr) < min(dt, db):
if dl < dr:
self.rect.x -= self.vel
else:
self.rect.x += self.vel
else:
if dt < db:
if dl < dr:
self.rect.y -= self.vel
else:
self.rect.y += self.vel

How to use groupcollide?

So I've been wondering how to use the pygame groupcollide. And I'm utterly stumped right now. As I am using collide_rect and it is fine. But for groupcollide I can't seem to figure out how to call the properties of the item inside of that group. And I can't do collide rect because there's going to be a lot of bullets.
def check_blast_collisions(player,bullet):
hits = pg.sprite.groupcollide(player,bullet,False,True)
for hit in hits:
print (hits)
if hit.vx == 20:
player.vx += 40
elif hit.vx == -20:
player.vx += -40
Here is a snippet of where I'm trying to use groupcollide.
After I made this function, the bullets don't even show up. (The bullets are supposed to be called blasts but I forgot about it in this function.)
import pygame as pg
#settings
CAPTION = "Knockback Arena"
resolution = 1600,900
WIDTH = resolution[0]
HEIGHT = resolution[1]
FPS = 60
player_jump_height = 30
player_max_fall_speed = 30
player_fall_speed_increase = 2
player_lives = 5
shoot_cooldown = 500
#initialize pygame
pg.init()
pg.mixer.init()
pg.font.init
screen = pg.display.set_mode(resolution)
pg.display.set_caption(CAPTION)
clock = pg.time.Clock()
#sprites
class Platform(pg.sprite.Sprite):
def __init__(self,x,y,width,height,r,g,b):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((width,height))
self.image.fill((r,g,b))
self.rect = self.image.get_rect()
self.rect.center = (x,y)
class Player(pg.sprite.Sprite):
def __init__(self,r,g,b,x,y):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((40, 100))
self.image.fill((r,g,b))
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.startx = x
self.starty = y
self.vx = 0
self.vy = 5
self.vy_max = player_max_fall_speed
self.vy_increase = player_fall_speed_increase
self.lives = player_lives
self.last_shot = 0
self.facing_right = False
self.facing_left = False
def update(self):
self.rect.x += self.vx
self.rect.y += self.vy
if self.vy >= self.vy_max:
self.vy = self.vy_max
self.vy_increase = 0
if self.vy < self.vy_max:
self.vy_increase = player_fall_speed_increase
if self.rect.bottom < HEIGHT:
self.vy += self.vy_increase
if self.rect.top >= HEIGHT:
self.rect.x = self.startx
self.rect.y = self.starty
self.lives -= 1
if self.lives <= 0:
self.kill()
if self.rect.right >= WIDTH:
self.rect.right = WIDTH
self.vx = 0
if self.rect.left <= 0:
self.rect.left = 0
self.vx = 0
def jump(self):
if self.rect.bottom >= main_platform.rect.top:
self.vy -= player_jump_height
if self.rect.bottom >= HEIGHT:
self.vy -= player_jump_height
def shoot(self):
if pg.time.get_ticks() - self.last_shot >= shoot_cooldown:
if self.facing_left == True:
return "shoot_left"
elif self.facing_right == True:
return "shoot_right"
else:
return "cd_not_done"
class Blast(pg.sprite.Sprite):
def __init__(self,player,direction):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((20,10))
self.image.fill((0,255,255))
self.rect = self.image.get_rect()
self.rect.center = (player.rect.center)
self.direction = direction
if self.direction == 0:
self.vx = -20
elif self.direction == 1:
self.vx = 20
def update(self):
self.rect.x += self.vx
if self.rect.right < 0:
self.kill()
if self.rect.left > WIDTH:
self.kill()
#functions
def check_for_collisions(player,platform):
hits = pg.sprite.collide_rect(player,platform)
if hits:
if hits and player.vy > 0:
player.rect.bottom = platform.rect.top
player.vy = 0
def check_blast_collisions(player,bullet):
hits = pg.sprite.groupcollide(player,bullet,False,True)
for hit in hits:
print (hits)
if hit.vx == 20:
player.vx += 40
elif hit.vx == -20:
player.vx += -40
font = pg.font.Font('font/Roboto-Light.ttf', 30)
all_sprites = pg.sprite.Group()
players = pg.sprite.Group()
platforms = pg.sprite.Group()
blasts = pg.sprite.Group()
main_platform = Platform(WIDTH/2,650,1000,100,0,200,0)
player_1 = Player(0,0,255,WIDTH/2 + -100,200)
player_2 = Player(255,0,0,WIDTH/2 + 100,200)
platforms.add(main_platform)
players.add(player_1)
players.add(player_2)
all_sprites.add(player_1)
all_sprites.add(player_2)
all_sprites.add(main_platform)
menu = True
run = True
while run:
#check for closing window
for event in pg.event.get():
if event.type == pg.KEYDOWN:
if event.key == pg.K_w:
player_1.jump()
if event.key == pg.K_a:
player_1.vx = -10
player_1.facing_left = True
player_1.facing_right = False
elif event.key == pg.K_d:
player_1.vx = 10
player_1.facing_right = True
player_1.facing_left = False
if event.key == pg.K_UP:
player_2.jump()
if event.key == pg.K_LEFT:
player_2.vx = -10
player_2.facing_left = True
player_2.facing_right = False
elif event.key == pg.K_RIGHT:
player_2.vx = 10
player_2.facing_right = True
player_2.facing_left = False
if event.key == pg.K_j:
if player_1.shoot() == "shoot_left":
b = Blast(player_1,0)
all_sprites.add(b)
blasts.add(b)
elif player_1.shoot() == "shoot_right":
b = Blast(player_1,1)
all_sprites.add(b)
blasts.add(b)
if event.key == pg.K_KP1:
if player_2.shoot() == "shoot_left":
b = Blast(player_2,0)
all_sprites.add(b)
blasts.add(b)
elif player_2.shoot() == "shoot_right":
b = Blast(player_2,1)
all_sprites.add(b)
blasts.add(b)
elif event.type == pg.KEYUP:
if event.key == pg.K_a:
player_1.vx = 0
if event.key == pg.K_d:
player_1.vx = 0
if event.key == pg.K_LEFT:
player_2.vx = 0
if event.key == pg.K_RIGHT:
player_2.vx = 0
if event.type == pg.QUIT:
pg.quit()
exit()
#update all sprites
all_sprites.update()
check_for_collisions(player_1,main_platform)
check_for_collisions(player_2,main_platform)
check_blast_collisions(players,blasts)
#draw sprites
screen.fill((255,255,255))
all_sprites.draw(screen)
#draw other stuff
p1lives = font.render(str(player_1.lives), False, (0,0,255))
screen.blit(p1lives,(20,50))
p2lives = font.render(str(player_2.lives), False, (255,0,0))
screen.blit(p2lives,(1580,50))
clock.tick(FPS)
pg.display.flip()
Here is the entire code.
Any help is much appreciated. Thanks.
You cannot use pygame.sprite.groupcollide() here, because the bullets collide with the player that shoots the bullets.
You have to use pygame.sprite.spritecollide(), with one player and the bullets of the opponent. Call it once for each player.

Repeat drawing in pygame

I am making a BrickBreaker/Breakout game using pygame. I want my game to create rows of bricks until the a brick hits the paddle but I can't figure out how to do so. Currently it only creates 1 row of bricks. I also want to make the bricks dissappear when the ball hits them. Currently when the bricks are hit they just move off the screen, but I want them to get permanently deleted.
Thanks!
My current code:
# Brick Breaker Game
import pygame
import random
# Initialize the pygame
pygame.init()
# create the screen
screen = pygame.display.set_mode((800, 600))
# background
background = pygame.image.load("realBackground.png")
# Title and Icon
pygame.display.set_caption("Brick Breaker")
icon = pygame.image.load("Brick Breaker Icon.png")
pygame.display.set_icon(icon)
# Paddle
paddleImage = pygame.image.load("scaledPaddle.png")
paddleX = 335
paddleY = 550
paddleX_change = 0
# BrickCoordinates
brickX = []
brickY = []
brickX_change = []
brickY_change = []
numOfBricks = 6
brickXValue = 15
for i in range(numOfBricks):
brickX.append(brickXValue)
brickY.append(0)
brickX_change.append(0.3)
brickY_change.append(0)
# Add 120 if thick lines in middle bricks
# Add 110 if uniform thickness
brickXValue += 130
#Bricks
yellowBrickImage = pygame.image.load("yellowBrick.png")
greenBrickImage = pygame.image.load("greenBrick.png")
blueBrickImage = pygame.image.load("blueBrick.png")
pinkBrickImage = pygame.image.load("pinkBrick.png")
# ball
ballImage = pygame.image.load("Ball.png")
ballX = 380
ballY = 280
ballX_change = 1.5
ballY_change = 1.5
#Score
scoreValue = 0
font = pygame.font.Font("Neufreit-ExtraBold.otf",24)
textX = 10
textY = 10
def showScore(x,y):
score = font.render("Score : " + str(scoreValue), True, (255,255,255))
screen.blit(score,(x,y))
def paddle(x, y):
screen.blit(paddleImage, (x, y))
def yellowBrick(x, y, i):
screen.blit(yellowBrickImage, (x, y))
def greenBrick(x, y, i):
screen.blit(greenBrickImage, (x, y))
def blueBrick(x, y, i):
screen.blit(blueBrickImage, (x, y))
def pinkBrick(x, y, i):
screen.blit(pinkBrickImage, (x, y))
def ball(x, y):
screen.blit(ballImage, (x, y))
#To pick random brick colours
colourOfBrick = []
for i in range(numOfBricks):
colourOfBrick.append(random.randint(1,4))
# Game Loop (makes sure game is always running)
running = True
while running:
# To change background colour
screen.fill((128, 128, 128))
# background image
screen.blit(background, (0, 0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# If keystroke is pressed check whether left or right
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
paddleX_change = -5
if event.key == pygame.K_RIGHT:
paddleX_change = 5
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
paddleX_change = 0
# Checking boudries of paddle
paddleX += paddleX_change
if paddleX <= 0:
paddleX = 0
elif paddleX >= 669:
paddleX = 669
#Draw Rectangles around bricks
brickRect = []
for i in range(numOfBricks):
brickRect.append(pygame.draw.rect(screen, (0, 0, 0), (brickX[i], brickY[i], 120, 42),1))
# Brick Movement
for i in range(numOfBricks):
brickY[i] += brickY_change[i]
if brickY[i] <= 0:
brickY_change[i] = 0.3
elif brickY[i] >= 500:
brickY_change[i] = -0.3
# Makes brick show up on screen
if colourOfBrick[i] == 1:
yellowBrick(brickX[i], brickY[i], i)
elif colourOfBrick[i] == 2:
greenBrick(brickX[i], brickY[i], i)
elif colourOfBrick[i] == 3:
blueBrick(brickX[i], brickY[i], i)
elif colourOfBrick[i] == 4:
pinkBrick(brickX[i], brickY[i], i)
# Ball Movement and boundary checking
ballX += ballX_change
if ballX <= 0:
ballX_change *= -1
elif ballX >= 760:
ballX_change *= -1
ballY += ballY_change
if ballY <= 0:
ballY_change *= -1
elif ballY >= 560:
ballX = 380
ballY = 280
# Paddle and Ball Collision
if ballY > 530 and ballY < 535 and (ballX+20) < paddleX + 131 and (ballX+20) > paddleX:
ballY_change *= -1
paddle(paddleX, paddleY)
ballCircle = pygame.draw.circle(screen, (255,0,0), (int(ballX+20),int(ballY+20)) ,20)
ball(ballX, ballY)
#Ball and Brick Collision
for i in range (numOfBricks):
if ballCircle.colliderect(brickRect[i]):
if abs(ballCircle.top - brickRect[i].bottom < 10) and ballY_change < 0:
brickX[i] = -400
ballY_change *= -1
scoreValue += 1
showScore(textX,textY)
pygame.display.update()
In your code all bricks have brickY.append(0) so all bricks are in one row. You have to create bricks with different Y values to create other rows.
You may need nested for-loops for this - like this
row_number = 3
brickYValue = 0
for row in range(row_number):
brickXValue = 15
for column in range(numOfBricks):
brickX.append(brickXValue)
brickY.append(brickYValue)
brickX_change.append(0.3)
brickY_change.append(0)
brickXValue += 130
# after `for column`
brickYValue += 15 # row height
But it will create more bricks then numOfBricks - you will have numOfBricks*row_number bricks so you would have to change other for-loops and use range(numOfBricks*row_number) instead of range(numOfBricks)
Or you should learn how to use for-loop without range()
brickRect = []
for x, y in zip(brickX, brickY):
brickRect.append(pygame.draw.rect(screen, (0, 0, 0), x, y, 120, 42),1))
BTW: you should also learn how to use pygame.Rect() to keep size and position of brick, paddle and ball. Rect() has methods to check collisions and you would no need long if ... and ... and ...
EDIT: I added rows in this code but I made many other changes so it may not be good example.
I draw surfaces instead of loading images so everyone can run it without images.
import pygame
import random
# --- classes ---
class Brick():
def __init__(self, x, y, image):
self.image = image
self.rect = self.image.get_rect(x=x, y=y)
self.x = x
self.y = y
self.x_change = 0
self.y_change = 1
def draw(self, screen):
self.rect.x = int(self.x)
self.rect.y = int(self.y)
screen.blit(self.image, self.rect)
pygame.draw.rect(screen, (0, 0, 0), self.rect, 1)
def update(self):
self.y += self.y_change
self.rect.y = int(self.y)
if self.rect.y <= 0:
self.y_change = 1
elif self.rect.y >= 500:
self.y_change = -1
class Ball():
def __init__(self):
#self.image = pygame.image.load("Ball.png")
self.image = pygame.Surface((16, 16)).convert_alpha()
self.image.fill((0,0,0,0)) # transparent background
pygame.draw.circle(self.image, (255,255,255), (8, 8), 8)
self.rect = self.image.get_rect(centerx=380, centery=280)
self.x = 380
self.y = 280
self.x_change = 3
self.y_change = 3
def reset(self):
self.x = 380
self.y = 280
def draw(self, screen):
self.rect.centerx = int(self.x)
self.rect.centery = int(self.y)
screen.blit(self.image, self.rect)
def update(self):
# Ball Movement and boundary checking
self.x += self.x_change
self.rect.centerx = int(self.x)
if self.rect.left <= 0:
self.x_change *= -1
elif self.rect.right >= 800:
self.x_change *= -1
self.y += self.y_change
self.rect.centery = int(self.y)
if self.rect.top <= 0:
self.y_change *= -1
elif self.rect.bottom >= 600:
self.reset()
class Paddle():
def __init__(self):
#self.image = pygame.image.load("scaledPaddle.png")
self.image = pygame.Surface((100, 30))
self.image.fill((255,0,0))
self.rect = self.image.get_rect(x=335, y=550)
self.x_change = 0
self.y_change = 0
def reset(self):
self.rect.x = 335
self.rect.y = 550
def draw(self, screen):
screen.blit(self.image, self.rect)
def update(self):
# Checking boudries of paddle
self.rect.x += self.x_change
if self.rect.left <= 0:
self.rect.left = 0
elif self.rect.right >= 800:
self.rect.right = 800
class Score():
def __init__(self):
#self.font = pygame.font.Font("Neufreit-ExtraBold.otf", 24)
self.font = pygame.font.SysFont(None, 24)
self.value = 0
self.x = 10
self.y = 10
def reset(self):
self.value = 0
def draw(self, screen):
self.image = self.font.render("Score : " + str(self.value), True, (255,255,255))
self.rect = self.image.get_rect(x=self.x, y=self.y)
screen.blit(self.image, self.rect)
# --- functions ---
# empty
# --- main ---
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Brick Breaker")
#icon = pygame.image.load("Brick Breaker Icon.png")
#pygame.display.set_icon(icon)
# Background Image
#background_image = pygame.image.load("realBackground.png")
background_image = pygame.Surface((800,600))
for y in range(5, 600, 25):
for x in range(5, 800, 25):
color = random.choice([(255,128,128), (128,255,128), (128,128,255)])
background_image.fill(color, [x,y,15,15])
# Brick Images
#brick_images = [
# pygame.image.load("yellowBrick.png"),
# pygame.image.load("greenBrick.png"),
# pygame.image.load("blueBrick.png"),
# pygame.image.load("pinkBrick.png"),
#]
brick_images = [
pygame.Surface((100, 30)),
pygame.Surface((100, 30)),
pygame.Surface((100, 30)),
pygame.Surface((100, 30)),
pygame.Surface((100, 30)),
pygame.Surface((100, 30)),
]
brick_images[0].fill((255,0,0))
brick_images[1].fill((0,255,0))
brick_images[2].fill((0,0,255))
brick_images[3].fill((255,255,0))
brick_images[4].fill((255,0,255))
brick_images[5].fill((0,255,255))
# Objects
paddle = Paddle()
ball = Ball()
score = Score()
# bricks
rows_number = 5
cols_number = 7
all_bricks = []
y = 0
for row in range(rows_number):
x = 50
for col in range(cols_number):
color_image = random.choice(brick_images)
brick = Brick(x, y, color_image)
all_bricks.append(brick)
x += 100
y += 30
# Game Loop
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# If keystroke is pressed check whether left or right
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
paddle.x_change = -5
if event.key == pygame.K_RIGHT:
paddle.x_change = 5
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
paddle.x_change = 0
# --- updates ---
paddle.update()
ball.update()
# Bricks Update
for brick in all_bricks:
brick.update()
# Ball and Paddle Collision
if ball.rect.colliderect(paddle):
ball.y_change *= -1
# Ball and Bricks Collision
for brick in all_bricks:
if ball.rect.colliderect(brick):
brick.x = -400
ball.y_change *= -1
score.value += 1
# --- draws ---
# To change background colour
# screen.fill((128, 128, 128)) # you don't need it if background fill all screen
# background image
screen.blit(background_image, (0, 0))
for brick in all_bricks:
brick.draw(screen)
paddle.draw(screen)
ball.draw(screen)
score.draw(screen)
pygame.display.flip()
clock.tick(60) # 60 FPS (Frames Per Second) on all computers
# --- end ---
pygame.quit()

Changing Sprite animation Speed (pygame)

I managed to animate my player using my 'animate' function that I had created - Cycles through the list of character images per frame while the character is moving. However, I also found that this happens at the same speed in which the game is running; and was hoping there was a simple way to change it so that the sprite animates at a slower speed than the game FPS.
Here is my sprite class code:
class Civilian(pg.sprite.Sprite):
def __init__(self, game, x, y):
self.groups = game.all_sprites, game.player1group, game.bothplayers
pg.sprite.Sprite.__init__(self, self.groups)
self.game = game
self._layer = PLAYER1_LAYER
self.image = pg.Surface((61, 67))
self.rect = self.image.get_rect()
self.hit_rect = PLAYER_HIT_RECT
self.hit_rect.center = self.rect.center
self.playerspeed = 90
self.vel = vec(0, 0)
self.pos = vec(x , y)
self.move = 0
self.speedboost = False
self.last_dir = 'down'#
self.anim_speed = 0
def animate(self, direction):
if direction == 'right':
self.spritesheet = pg.image.load('walk right civ.png') # Loading the right directional movement spritesheet into the variable
if direction == 'left':
self.spritesheet = pg.image.load('walk left civ.png')
if direction == 'up':
self.spritesheet = pg.image.load('walk up civ.png')
if direction == 'down':
self.spritesheet = pg.image.load('walk down civ.png')
self.frames = [] # List which will contain each cell of the spritesheet
# Adding the cells to the list #
self.frames.append(self.spritesheet.subsurface(pg.Rect(0, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(61, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(122, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(183, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(244, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(305, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(366, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(427, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(488, 0, 61, 67)).convert_alpha())
# Number of frames/cells
self.frames_number = len(self.frames)
# Current animation frame
self.current_frame = 0
# Frame rectangle
self.frame_rect = self.frames[0].get_rect()
self.last_dir = direction
def get_keys(self):
self.vel= vec(0, 0)
keys = pg.key.get_pressed()
if keys[pg.K_a]: # Const. subtracts player speed from velocity (E.g. Moves sprite to the left)
self.vel.x= -self.playerspeed
self.move += 1
self.moving = 'left' # Uses different spritesheet depending on direction
elif keys[pg.K_d]: # Const. adds player speed value to velocity (E.g. Moves sprite to the right)
self.vel.x= self.playerspeed
self.move += 1
self.moving = 'right'
elif keys[pg.K_w]: # Const. subtracts player speed value from y velocity (Moves player upwards; opposite)
self.vel.y= -self.playerspeed
self.move += 1
self.moving = 'up'
elif keys[pg.K_s]: # Const. adds player speed value to y velocity (Moves player downwards; opposite)
self.vel.y= self.playerspeed
self.move += 1
self.moving = 'down'
def add_speed(self):
pass
def collide_with_player2(self, dir, ifColliding):
if dir == 'x':
collides = pg.sprite.spritecollide(self, self.game.player2group, False, collide_player_hit_rect)
if collides:
if self.vel.x > 0:
self.pos.x = collides[0].hit_rect.left - self.hit_rect.width / 2
if self.vel.x < 0:
self.pos.x = collides[0].hit_rect.right + self.hit_rect.width / 2
self.vel.x = 0
self.hit_rect.centerx = self.pos.x
print("collide x")
if random.randint(0, 100) <= 4:
random.choice(self.game.thief_hit_sounds).play()
self.ifColliding = True
if dir == 'y':
collides = pg.sprite.spritecollide(self, self.game.player2group, False, collide_player_hit_rect)
if collides:
if self.vel.y > 0:
self.pos.y = collides[0].hit_rect.top - self.hit_rect.height / 2
if self.vel.y < 0:
self.pos.y = collides[0].hit_rect.bottom + self.hit_rect.height / 2
self.vel.y = 0
self.hit_rect.centery = self.pos.y
print("collide y")
if random.randint(0, 100) <= 4:
random.choice(self.game.thief_hit_sounds).play()
self.ifColliding = True
def collide_with_walls(self, dir):
if dir == 'x':
collides = pg.sprite.spritecollide(self, self.game.walls, False, collide_hit_rect)
if collides:
if self.vel.x > 0:
self.pos.x = collides[0].rect.left - self.hit_rect.width / 2
if self.vel.x < 0:
self.pos.x = collides[0].rect.right + self.hit_rect.width / 2
self.vel.x = 0
self.hit_rect.centerx = self.pos.x
if dir == 'y':
collides = pg.sprite.spritecollide(self, self.game.walls, False, collide_hit_rect)
if collides:
if self.vel.y > 0:
self.pos.y = collides[0].rect.top - self.hit_rect.height / 2
if self.vel.y < 0:
self.pos.y = collides[0].rect.bottom + self.hit_rect.height / 2
self.vel.y = 0
self.hit_rect.centery = self.pos.y
def update(self):
# frame updates
self.anim_speed += 1
self.moving = 'idle'
self.animate(self.last_dir) # Sets the down spritesheet as default
self.get_keys()
if self.moving == 'up':
self.animate(self.moving) # Uses the up-movement spritesheet if char moving upwards
if self.moving == 'down':
self.animate(self.moving) # Same as above, different direction
if self.moving == 'left':
self.animate(self.moving)
if self.moving == 'right':
self.animate(self.moving)
self.ifColliding = False
self.rect.center = self.pos
self.pos += self.vel * self.game.dt
self.hit_rect.centerx = self.pos.x
self.collide_with_walls('x'), self.collide_with_player2('x', self.ifColliding)
self.hit_rect.centery = self.pos.y
self.collide_with_walls('y'), self.collide_with_player2('y', self.ifColliding)
self.rect.center = self.hit_rect.midtop
if self.ifColliding == True:
Thief.health -= COL_DAMAGE
print(Thief.health)
self.current_frame = (self.current_frame + self.move) % self.frames_number
if self.moving == 'idle':
self.current_frame = 0
self.image = self.frames[self.current_frame] # Image of sprite changes as program cycles through the sheet
You can decouple the animation rate from the frame rate by tying it to time in some way.
One way is to use pygame.time.get_ticks() to return the number of milliseconds since pygame.init() was called. If you store this value, you can measure how much time has elapsed and animate appropriately.
def update(self):
self.elapsed = pygame.time.get_ticks() - self.elapsed
if self.elapsed > 500: # animate every half second
self.animate()
Note: you'll also need to initialise self.elapsed in the constructor.
This is the code I made to animate a sprite having the frame rate at 60 and the sprite animating slower than frame rate
import pygame
import glob
def fps():
fr = "V.3 Fps: " + str(int(clock.get_fps()))
frt = font.render(fr, 1, pygame.Color("coral"))
return frt
class MySprite(pygame.sprite.Sprite):
def __init__(self, action):
super(MySprite, self).__init__()
self.action = action
# This is to slow down animation # takes the frame now and...
self.elapsed = 0
self.images = []
self.temp_imgs = []
self.load_images()
self.count = 0
def load_images(self):
l_imgs = glob.glob(f"png\\{self.action}*.png")
for img in l_imgs:
if len(img) == len(l_imgs[0]):
self.images.append(pygame.image.load(img))
else:
self.temp_imgs.append(pygame.image.load(img))
self.images.extend(self.temp_imgs)
self.index = 0
self.rect = pygame.Rect(5, 5, 150, 198)
def update(self):
self.count += 1
if self.index == len(self.images):
self.index = 0
self.image = self.images[self.index]
if self.count > 2:
#self.image = self.images[self.index]
self.index += 1
self.count = 0
def group_sprites(self):
return pygame.sprite.Group(self)
def group():
"Dictionary of group of sprites"
dici = {}
actions = "idle walk run jump dead"
actions = actions.split()
for action in actions:
dici[action] = MySprite(action).group_sprites()
return dici
def main():
global clock
global font
SIZE = 600, 600
FPS = 60
pygame.init()
action = group()
my_group = action["idle"]
screen = pygame.display.set_mode(SIZE)
pygame.display.set_caption("Game v.3")
font = pygame.font.SysFont("Arial", 60)
clock = pygame.time.Clock()
loop = 1
while loop:
for event in pygame.event.get():
if event.type == pygame.QUIT:
loop = 0
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
my_group = action["walk"]
if event.key == pygame.K_UP:
my_group = action["jump"]
if event.key == pygame.K_SPACE:
my_group = action["idle"]
if event.key == pygame.K_RIGHT:
my_group = action["run"]
if event.key == pygame.K_DOWN:
my_group = action["dead"]
my_group.update()
screen.fill((0, 0, 0))
my_group.draw(screen)
screen.blit(fps(), (10, 0))
pygame.display.update()
clock.tick(FPS)
pygame.quit()
if __name__ == '__main__':
main()
Read this article... slow down animation without affecting the frame rate
Dowload images and put the in a directory called png

Categories