Detecting collision with the sides of a GroupSingle and Group - python

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

Related

Pygame sprite moves up and left faster than down and right [duplicate]

This question already has answers here:
Pygame doesn't let me use float for rect.move, but I need it
(2 answers)
Ship moves up and left faster than down and right when rotating in pygame
(1 answer)
Closed last month.
I am working on a simple game. I created a pygame sprite and moved it by adding its rect position with a direction (from wasd input). However, it seems to be moving left and up (where all the directions are negative) quicker than right and down (where all the directions are positive)
Here is the code:
import pygame
from settings import *
from support import import_folder
class Player(pygame.sprite.Sprite):
def __init__(self, pos, groups,obstacle_sprites,create_attack,destroy_attack,create_magic):
#for sprites
super().__init__(groups)
self.image = pygame.image.load(f'{abs_path}/graphics/test/player.png').convert_alpha()
self.rect = self.image.get_rect(topleft = pos)
self.hitbox = self.rect.inflate(0,-26)
# graphics setup
self.import_player_assets()
self.status = 'down'
self.frame_index = 0
self.animation_speed = 0.15
#movement
self.direction = pygame.math.Vector2()
self.attacking = False
self.attack_cooldown = 400
self.attack_time = None
self.obstacle_sprites = obstacle_sprites
#weapon
self.create_attack = create_attack
self.destory_attack = destroy_attack
#gets the correct data from weapon dictionary in settings.py also turns keys into list
self.weapon_index = 0
self.weapon = list(weapon_data.keys())[self.weapon_index]
self.can_switch_weapon = True
self.weapon_switch_time = None
self.switch_duration_cooldown = 200
# Magic
self.create_magic = create_magic
self.magic_index = 0
self.magic = list(magic_data.keys())[self.magic_index]
self.can_switch_magic = True
self.magic_switch_time = None
#Stats
self.stats = {'health': 100, 'energy':60, 'attack': 10, 'magic': 4, 'speed': 5}
self.health = self.stats['health']
self.energy = self.stats['energy']
self.exp = 0
self.speed = self.stats['speed']
#For the player animations
def import_player_assets(self):
character_path = f'{abs_path}/graphics/player2/'
self.animations = {'up': [], 'down': [], 'left': [], 'right': [],
'right_idle': [], 'left_idle': [], 'up_idle': [], 'down_idle': [],
'right_attack': [], 'left_attack': [], 'up_attack': [], 'down_attack': []}
for animation in self.animations.keys():
full_path = character_path + animation
self.animations[animation] = import_folder(full_path)
def input(self):
keys = pygame.key.get_pressed()
#Movement Input
#The extra if statement is to make the speed the same even when its going in diagonals, some are set to .8
#because pygame is wierd
if not self.attacking:
if (keys[pygame.K_w] and keys[pygame.K_s]):
self.direction.y = 0
elif keys[pygame.K_w]:
if (keys[pygame.K_a] or keys[pygame.K_d]):
self.direction.y = -.7
else:
self.direction.y = -1
self.status = 'up'
elif keys[pygame.K_s]:
if (keys[pygame.K_a] or keys[pygame.K_d]):
self.direction.y = .7
else:
self.direction.y = 1
self.status = 'down'
else:
self.direction.y = 0
if (keys[pygame.K_d] and keys[pygame.K_a]):
self.direction.x = 0
elif keys[pygame.K_d]:
if (keys[pygame.K_w] or keys[pygame.K_s]):
self.direction.x = .7
else:
self.direction.x = 1
self.status = 'right'
elif keys[pygame.K_a]:
if (keys[pygame.K_w] or keys[pygame.K_s]):
self.direction.x = -.7
else:
self.direction.x = -1
self.status = 'left'
else:
self.direction.x = 0
#Attack Input
#if keys[pygame.K_LSHIFT] and not self.attacking
if keys[pygame.K_SPACE]:
self.attacking = True
self.attack_time = pygame.time.get_ticks()
self.create_attack()
#Spell Input
if keys[pygame.K_LSHIFT]:
self.attacking = True
self.attack_time = pygame.time.get_ticks()
style = list(magic_data.keys())[self.magic_index]
strength = list(magic_data.values())[self.magic_index]['strength'] + self.stats['magic']
cost = list(magic_data.values())[self.magic_index]['cost']
self.create_magic(style, strength, cost)
#Switch weapons Input
if keys[pygame.K_q] and self.can_switch_weapon:
self.can_switch_weapon = False
self.weapon_switch_time = pygame.time.get_ticks()
if self.weapon_index < len(list(weapon_data.keys())) - 1:
self.weapon_index += 1
else:
self.weapon_index = 0
self.weapon = list(weapon_data.keys())[self.weapon_index]
#Switch spells Input
if keys[pygame.K_e] and self.can_switch_magic:
self.can_switch_magic = False
self.magic_switch_time = pygame.time.get_ticks()
if self.magic_index < len(list(magic_data.keys())) - 1:
self.magic_index += 1
else:
self.magic_index = 0
self.magic = list(magic_data.keys())[self.magic_index]
def get_status(self):
#Idle Status:
if self.direction.x == 0 and self.direction.y == 0:
if not 'idle' in self.status and not 'attack' in self.status:
self.status = self.status + '_idle'
if self.attacking:
self.direction.x = 0
self.direction.y = 0
if not 'attack' in self.status:
if 'idle' in self.status:
self.status = self.status.replace('_idle','_attack')
else:
self.status = self.status + '_attack'
else:
if 'attack' in self.status:
self.status = self.status.replace('_attack','')
def move(self, speed):
#Moving the player feat. collision detection
self.hitbox.x += self.direction.x * speed
self.collision('horizontal')
self.hitbox.y += self.direction.y * speed
self.collision('vertical')
self.rect.center = self.hitbox.center
#Pygame cannot tell if something collides but only if it overlaps
#So i check for horizontal/vertical movement because it cant tell where it overlaps
def collision(self, direction):
if direction == 'horizontal':
for sprite in self.obstacle_sprites:
#Checking the hitbox of sprite with hitbox of obstacle
if sprite.hitbox.colliderect(self.hitbox):
if self.direction.x > 0: #checks if player is moving right
#moves the right side of player to left side of obstacle
self.hitbox.right = sprite.hitbox.left
if self.direction.x < 0:
#vice versa
self.hitbox.left = sprite.hitbox.right
if direction == 'vertical':
for sprite in self.obstacle_sprites:
#Checking the hitbox of sprite with hitbox of obstacle
if sprite.hitbox.colliderect(self.hitbox):
if self.direction.y > 0: #checks if player is moving down
#moves the bottom side of player to top side of obstacle
self.hitbox.bottom = sprite.hitbox.top
if self.direction.y < 0: #move up
#vice versa
self.hitbox.top = sprite.hitbox.bottom
#Cooldown function so people cant spam attacks
def cooldowns(self):
current_time = pygame.time.get_ticks()
if self.attacking:
if current_time - self.attack_time >= self.attack_cooldown:
self.attacking = False
self.destory_attack()
if not self.can_switch_weapon:
if current_time - self.weapon_switch_time >= self.switch_duration_cooldown:
self.can_switch_weapon = True
if not self.can_switch_magic:
if current_time - self.magic_switch_time >= self.switch_duration_cooldown:
self.can_switch_magic = True
#Loop the images to make moving sprites
def animate(self):
#Goes into the image dictionary and puts player status as key, the animation = the item
animation = self.animations[self.status]
#loop over frame index
self.frame_index += self.animation_speed
if self.frame_index >= len(animation):
self.frame_index = 0
#Set the image
self.image = animation[int(self.frame_index)]
self.rect = self.image.get_rect(center = self.hitbox.center)
def update(self):
self.input()
self.cooldowns()
self.get_status()
self.animate()
self.move(self.speed)

Why does flipping my sprites cause it to move weird

I'm making a pyGame game and instead of getting sprites to both sides I decided to just flip it horizontally, the thing is it move perfect on the right side but on the left side it moves wrong like the center of the image changes every frame, is there anyway I can controll the images center.
for the record the function draw is being called from Main.py
This is my Player Class:
class Player(pygame.sprite.Sprite):
def __init__(self, char_type, x, y, scale, speed):
pygame.sprite.Sprite.__init__(self)
self.alive = True
self.char_type = char_type
self.speed = speed
self.direction = 1
self.vel_y = 0
self.jump = False
self.in_air = True
self.flip = False
self.attack = False
self.moving_left = False
self.moving_right = False
self.animation_list = []
self.frame_index = 0
self.action = 0
self.update_time = pygame.time.get_ticks()
#load all images for the players
animation_types = ['stand', 'walk', 'jump', 'attack1', 'attack2', 'attack3']
for animation in animation_types:
#reset temporary list of images
temp_list = []
#count number of files in the folder
num_of_frames = len(os.listdir(f'sprites/{self.char_type}/{animation}'))
for i in range(num_of_frames):
img = pygame.image.load(f'sprites/{self.char_type}/{animation}/{i}.png')
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)
def move(self, GRAVITY):
#reset movement variables
dx = 0
dy = 0
#assign movement variables if moving left or right
if self.moving_left:
dx = -self.speed
self.flip = True
self.direction = -1
if self.moving_right:
dx = self.speed
self.flip = False
self.direction = 1
# if self.attack:
# self.play_sound("attack")
#jump
if self.jump == True and self.in_air == False:
self.vel_y = -11
self.jump = False
self.in_air = True
# if self.attack == True:
# self.attack = False
#apply gravity
self.vel_y += GRAVITY
if self.vel_y > 10:
self.vel_y
dy += self.vel_y
#check collision with floor
if self.rect.bottom + dy > 450:
dy = 450 - self.rect.bottom
self.in_air = False
#update rectangle position
self.rect.x += dx
self.rect.y += dy
def update_animation(self):
#update animation
if self.action == 0:
animation_cooldown = 800
elif self.action == 1:
animation_cooldown = 170
elif self.action == 2:
animation_cooldown = 50
elif self.action == 3:
self.moving_left = False
self.moving_right = False
animation_cooldown = 250
# self.action = random.randint(3, 5)
#update image depending on current frame
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 the animation has run out the reset back to the start
if self.frame_index >= len(self.animation_list[self.action]):
if self.action == 3:
self.attack = False
self.frame_index = 0
def update_action(self, new_action):
#check if the new action is different to the previous one
if new_action != self.action:
self.action = new_action
#update the animation settings
self.frame_index = 0
self.update_time = pygame.time.get_ticks()
def play_sound(self, sound):
soundObj = pygame.mixer.Sound('sprites/sounds/player/' + sound + '.mp3')
soundObj.play()
def draw(self, screen):
screen.blit(pygame.transform.flip(self.image, self.flip, False), self.rect)
You need to reverse the list for the flipped side. reverse the list in place:
temp_list.reverse()
self.animation_list.append(temp_list)
or append a reversed list:
self.animation_list.append(reversed(temp_list))
If your images are different sizes, you need to create images of the same size. Find the size of the largest image. Create a pygame.Surface for each image and blit the image onto the Surface:
for animation in animation_types:
max_image_size = [0, 0]
temp_list = []
num_of_frames = len(os.listdir(f'sprites/{self.char_type}/{animation}'))
for i in range(num_of_frames):
img = pygame.image.load(f'sprites/{self.char_type}/{animation}/{i}.png')
temp_list.append(img)
max_image_size[0] = max(max_image_size[0], img.get_width())
max_image_size[0] = max(max_image_size[1], img.get_height())
for i in range(num_of_frames):
img = pygame.Surface(max_image_size, pygame.SRCALPHA)
img.blit(temp_list[i], (0, 0))
temp_list[i] = pygame.transform.scale(img, (int(img.get_width() * scale), int(img.get_height() * scale)))
self.animation_list.append(temp_list)

I am having trouble with my stat function in my player class

When I shoot at my cement block sprites I have it set so that self.score is self.score += 1 in my player collision function, but when I shoot my cement blocks and destroy them, either 1 or 2 points is added at random to my score. Why? How can I fix this? A clear example is, I shoot at and destroy 2 cement blocks in a row and 1 point is added for each one destroyed which means my score is 2, which is what I want cause I want to add 1 point to my score whenever I destroy a cement block, but then when I shoot and destroy the third cement block, 2 points are added instead of 1 bringing my score to 4 instead of being a score of 3 points.
Github: https://github.com/Enoc-Mena99/AutoPilot
My code:
import random
import pygame
import pygame.freetype
pygame.init()
#screen settings
WIDTH = 1000
HEIGHT = 400
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("AutoPilot")
screen.fill((255, 255, 255))
#fps
FPS = 120
clock = pygame.time.Clock()
#load images
bg = pygame.image.load('background/street.png').convert_alpha() # background
bullets = pygame.image.load('car/bullet.png').convert_alpha()
debris_img = pygame.image.load('debris/cement.png')
#define game variables
shoot = False
#player class
class Player(pygame.sprite.Sprite):
def __init__(self, scale, speed):
pygame.sprite.Sprite.__init__(self)
self.bullet = pygame.image.load('car/bullet.png').convert_alpha()
self.bullet_list = []
self.speed = speed
#self.x = x
#self.y = y
self.moving = True
self.frame = 0
self.flip = False
self.direction = 0
self.score = 0
#load car
self.images = []
img = pygame.image.load('car/car.png').convert_alpha()
img = pygame.transform.scale(img, (int(img.get_width()) * scale, (int(img.get_height()) * scale)))
self.images.append(img)
self.image = self.images[0]
self.rect = self.image.get_rect()
self.update_time = pygame.time.get_ticks()
self.movingLeft = False
self.movingRight = False
self.rect.x = 465
self.rect.y = 325
#draw car to screen
def draw(self):
screen.blit(self.image, (self.rect.centerx, self.rect.centery))
#move car
def move(self):
#reset the movement variables
dx = 0
dy = 0
#moving variables
if self.movingLeft and self.rect.x > 33:
dx -= self.speed
self.flip = True
self.direction = -1
if self.movingRight and self.rect.x < 900:
dx += self.speed
self.flip = False
self.direction = 1
#update rectangle position
self.rect.x += dx
self.rect.y += dy
#shoot
def shoot(self):
bullet = Bullet(self.rect.centerx + 18, self.rect.y + 30, self.direction)
bullet_group.add(bullet)
#check collision
def collision(self, debris_group):
for debris in debris_group:
if pygame.sprite.spritecollide(debris, bullet_group, True):
debris.health -= 1
if debris.health <= 0:
self.score += 1
#player stats
def stats(self):
myfont = pygame.font.SysFont('comicsans', 30)
scoretext = myfont.render("Score: " + str(self.score), 1, (0,0,0))
screen.blit(scoretext, (100,10))
#bullet class
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, direction):
pygame.sprite.Sprite.__init__(self)
self.speed = 5
self.image = bullets
self.rect = self.image.get_rect()
self.rect.center = (x,y)
self.direction = direction
def update(self):
self.rect.centery -= self.speed
#check if bullet has gone off screen
if self.rect.centery < 1:
self.kill()
#debris class
class Debris(pygame.sprite.Sprite):
def __init__(self,scale,speed):
pygame.sprite.Sprite.__init__(self)
self.scale = scale
self.x = random.randrange(100,800)
self.speed_y = 10
self.y = 15
self.speed = speed
self.vy = 0
self.on_ground = True
self.move = True
self.health = 4
self.max_health = self.health
self.alive = True
self.velocity = random.randrange(1,2)
self.speed_x = random.randrange(-3,3)
self.moving_down = True
self.is_destroyed = False
#load debris
self.image = debris_img
self.rect = self.image.get_rect()
self.rect.x = random.randrange(100, 800)
self.rect.y = random.randrange(-150, -100)
self.rect.center = (self.x,self.y)
#load explosion
self.img_explosion_00 = pygame.image.load('explosion/0.png').convert_alpha()
self.img_explosion_00 = pygame.transform.scale(self.img_explosion_00, (self.img_explosion_00.get_width() * 2,
self.img_explosion_00.get_height() * 2))
self.img_explosion_01 = pygame.image.load('explosion/1.png').convert_alpha()
self.img_explosion_01 = pygame.transform.scale(self.img_explosion_01, (self.img_explosion_01.get_width() * 2,
self.img_explosion_01.get_height() * 2))
self.img_explosion_02 = pygame.image.load('explosion/2.png').convert_alpha()
self.img_explosion_02 = pygame.transform.scale(self.img_explosion_02, (self.img_explosion_02.get_width() * 2,
self.img_explosion_02.get_height() * 2))
self.img_explosion_03 = pygame.image.load('explosion/3.png').convert_alpha()
self.img_explosion_03 = pygame.transform.scale(self.img_explosion_03, (self.img_explosion_03.get_width() * 2,
self.img_explosion_03.get_height() * 2))
#explosion list
self.anim_explosion = [self.img_explosion_00,
self.img_explosion_01,
self.img_explosion_02,
self.img_explosion_03]
self.anim_index = 0
self.frame_len = 10
#spawn new debris
def spawn_new_debris(self):
self.rect.x = random.randrange(100, 800)
self.rect.y = random.randrange(-150, -100)
self.velocity = random.randrange(1, 2)
self.speed_x = random.randrange(-3, 3)
#respawn debris when they go of the screen
def boundaries(self):
if self.rect.left > WIDTH + 10 or self.rect.right < -10 or self.rect.top > HEIGHT + 10:
self.spawn_new_debris()
#update image
def update(self):
self.rect.y += self.velocity
self.rect.x += self.speed_x
self.boundaries()
if self.health <= 0:
max_index = len(self.anim_explosion) - 1
if self.anim_index > max_index:
self.kill()
else:
if self.frame_len == 0:
self.image = self.anim_explosion[self.anim_index]
self.anim_index += 1
self.frame_len = 10
else:
self.frame_len -= 1
#make debris fall down
def falldown(self):
self.rect.centery += self.velocity
if self.moving_down and self.rect.y > 350:
self.kill()
######################CAR/DEBRIS##########################
player = Player(1,5)
##########################################################
#groups
bullet_group = pygame.sprite.Group()
debris_group = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
for x in range(50):
d = Debris(1, 5)
debris_group.add(d)
all_sprites.add(d)
#game runs here
run = True
while run:
#draw street
screen.blit(bg, [0, 0])
#update groups
bullet_group.update()
bullet_group.draw(screen)
debris_group.update()
debris_group.draw(screen)
#draw car
player.draw()
player.move()
player.collision(debris_group)
player.stats()
#update all sprites
all_sprites.update()
all_sprites.draw(screen)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
#check if key is down
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
run = False
if event.key == pygame.K_a:
player.movingLeft = True
if event.key == pygame.K_d:
player.movingRight = True
if event.key == pygame.K_SPACE:
player.shoot()
shoot = True
#check if key is up
if event.type == pygame.KEYUP:
if event.key == pygame.K_a:
player.movingLeft = False
if event.key == pygame.K_d:
player.movingRight = False
#update the display
pygame.display.update()
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
Do not count debris objects with healt <= 0:
class Player(pygame.sprite.Sprite):
# [...]
def collision(self, debris_group):
for debris in debris_group:
# if health > 0 and collision:
if debris.health > 0 and pygame.sprite.spritecollide(debris, bullet_group, True):
debris.health -= 1
if debris.health <= 0:
self.score += 1

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.

Enemy collision with pygame.sprite.spritecollide()

In my game, there is a sprite player which I can control. It can move right or left, it can jump, shoot fireballs(bullets) and a breathe fire. I have added an enemy which can move on itself from right to left on a limited distance that I set. What I would like to do now is make my player loose health if it collides with the enemy sprite using pygame.sprite.spritecollide(). However it isn't working out well I don't know how to fix my issue which is the following: if I run my code below it says NameError: name 'enemy_list' is not defined. The errored line is in Sprite1.py in the Player class under the update function. How do I fix my code? I created my Enemy class and Level class with the following website: https://opensource.com/article/18/5/pygame-enemy. I'm open to all suggestions. Thanks beforehand! I separated my code into three files: main.py, settings.py and Sprite1.py. Here's main.py:
import pygame
import os
import sys
import time
from pygame import mixer
from Sprite1 import *
from settings import *
'''
Setup
'''
pygame.init()
clock = pygame.time.Clock()
pygame.mixer.music.load('.\\sounds\\Fairy.mp3')
pygame.mixer.music.play(-1, 0.0)
all_sprites = pygame.sprite.Group()
player = Player(all_sprites)
player.rect.x = 500
player.rect.y = 500
eloc = []
eloc = [400,500]
enemy_list = Level.bad( 1, eloc )
showStartScreen(surface)
x = 0
'''
Main loop
'''
main = True
while main == True:
background = pygame.image.load(os.path.join('images', 'Bg.png')).convert()
surface.blit(background, (0, 0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
main = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
player.control(-steps,0)
if event.key == pygame.K_RIGHT:
player.control(steps,0)
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
player.control(steps,0)
if event.key == pygame.K_RIGHT:
player.control(-steps,0)
keys = pygame.key.get_pressed()
if not(isJump):
if keys[pygame.K_UP]:
isJump = True
else:
if jumpCount >= -10:
player.rect.y -= (jumpCount * abs(jumpCount)) * 1
jumpCount -= 2
else:
jumpCount = 10
isJump = False
# dt = time since last tick in milliseconds.
dt = clock.tick(60) / 1000
all_sprites.update(dt)
player.update(dt)
all_sprites.draw(surface) #refresh player position
enemy_list.draw(surface)
for e in enemy_list:
e.move()
pygame.display.flip()
Here's my settings.py:
import pygame
isJump = False
jumpCount = 10
width = 960
height = 720
fps = 40 # frame rate
pygame.display.set_caption('B.S.G.')
surface = pygame.display.set_mode((width, height))
PLAYER_ACC = 0.5
PLAYER_FRICTION = -0.12
PLAYER_GRAV = 0.8
PLAYER_JUMP = 20
PLAYER_LAYER = 2
PLATFORM_LAYER = 1
RED = (255, 0, 0)
steps = 10 # how fast to move
And here's my Sprite1.py:
import pygame
import sys
import os
import time
from pygame import mixer
from pygame.locals import *
from settings import *
vec = pygame.math.Vector2
def showStartScreen(surface):
show = True
while (show == True):
background = pygame.image.load(os.path.join('images', 'Starting_scr.png'))
# rect = surface.get_rect()
surface.blit(background, (0,0))
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
show = False
class Player(pygame.sprite.Sprite):
def __init__(self, all_sprites):
pygame.sprite.Sprite.__init__(self)
self.movex = 0
self.movey = 0
self.frame = 0
self.health = 10
self.jumping = False
self.images = []
self.imagesleft = []
self.imagesright = []
self.direction = "right"
self.alpha = (0,0,0)
self.ani = 4 # animation cycles
self.all_sprites = all_sprites
self.add(self.all_sprites)
self.fire_timer = .1
self.bullet_timer = .1
self.pos = vec(40, height - 100)
self.vel = vec(0, 0)
for i in range(1,5):
img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
img.convert_alpha()
img.set_colorkey(self.alpha)
self.imagesright.append(img)
self.image = self.imagesright[0]
self.rect = self.image.get_rect()
for i in range(1,5):
img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
img = pygame.transform.flip(img, True, False)
img.convert_alpha()
img.set_colorkey(self.alpha)
self.imagesleft.append(img)
self.image = self.imagesleft[0]
self.rect = self.image.get_rect()
def control(self,x,y):
'''
control player movement
'''
self.movex += x
self.movey -= y
def update(self, dt):
'''
Update sprite position
'''
self.rect.x = self.rect.x + self.movex
self.rect.y = self.rect.y + self.movey
enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
for enemy in ennemy_hit_list:
self.health -= 1
print(self.health)
# moving left
if self.movex < 0:
self.frame += 1
if self.frame > 3*self.ani:
self.frame = 0
self.image = self.imagesleft[self.frame//self.ani]
self.direction = "left"
# moving right
if self.movex > 0:
self.frame += 1
if self.frame > 3*self.ani:
self.frame = 0
self.image = self.imagesright[self.frame//self.ani]
self.direction = "right"
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE]:
self.bullet_timer -= dt # Subtract the time since the last tick.
if keys[pygame.K_x]:
self.fire_timer -= dt
if self.bullet_timer <= 0:
self.bullet_timer = 100 # Bullet ready.
if keys: # Left mouse button.
# Create a new bullet instance and add it to the groups.
if self.direction == "right":
Bullet([self.rect.x + self.image.get_width(), self.rect.y + self.image.get_height()/2], self.direction, self.all_sprites)
else:
Bullet([self.rect.x, self.rect.y + self.image.get_height()/2], self.direction, self.all_sprites)
self.bullet_timer = .5 # Reset the timer.
if self.fire_timer <= 0:
self.fire_timer = 100
if keys:
if self.direction == "right":
Fire([self.rect.x + 170, self.rect.y + self.image.get_height()/2], self.direction, self.all_sprites)
else:
Fire([self.rect.x - 90, self.rect.y + self.image.get_height()/2], self.direction, self.all_sprites)
self.fire_timer = .1
if self.health == 0:
self.kill()
class Enemy(pygame.sprite.Sprite):
'''
Spawn an enemy
'''
def __init__(self,x,y,img):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('images',img))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.counter = 0 # counter variable
def move(self):
'''
enemy movement
'''
distance = 20
speed = 15
if self.counter >= 0 and self.counter <= distance:
self.rect.x += speed
elif self.counter >= distance and self.counter <= distance*2:
self.rect.x -= speed
else:
self.counter = 0
self.counter += 1
class Bullet(pygame.sprite.Sprite):
IMAGE = None
FLIPPED_IMAGE = None
def __init__(self, pos, direction, *sprite_groups):
super().__init__(*sprite_groups)
# cache images
if not Bullet.IMAGE:
Bullet.IMAGE = pygame.image.load(os.path.join('images','fireball.png'))
Bullet.FLIPPED_IMAGE = pygame.transform.flip(Bullet.IMAGE, True, False)
if direction == "right":
self.vel = pygame.math.Vector2(750, 0)
self.image = Bullet.IMAGE
else:
self.vel = pygame.math.Vector2(-750, 0)
self.image = Bullet.FLIPPED_IMAGE
self.pos = pygame.math.Vector2(pos)
self.rect = self.image.get_rect(center=pos)
def update(self, dt):
# Add the velocity to the position vector to move the sprite
self.pos += self.vel * dt
self.rect.center = self.pos # Update the rect pos.
if not pygame.display.get_surface().get_rect().colliderect(self.rect):
self.kill()
class Fire(pygame.sprite.Sprite):
IMAGE = None
FLIPPED_IMAGE = None
def __init__(self, pos, direction, *sprite_groups):
super().__init__(*sprite_groups)
# cache images
if not Fire.IMAGE:
Fire.IMAGE = pygame.image.load(os.path.join('images','fire_drag.png'))
Fire.FLIPPED_IMAGE = pygame.transform.flip(Fire.IMAGE, True, False)
if direction == "right":
self.image = Fire.IMAGE
self.vel = pygame.math.Vector2(0, 0)
else:
self.image = Fire.FLIPPED_IMAGE
self.vel = pygame.math.Vector2(0, 0)
self.pos = pygame.math.Vector2(pos)
self.rect = self.image.get_rect(center=pos)
def update(self, dt):
self.too = True
self.pos += self.vel * dt
self.rect.center = self.pos # Update the rect pos.
if self.too == True:
self.kill()
class Level():
def bad(lvl,eloc):
if lvl == 1:
enemy = Enemy(eloc[0],eloc[1],'cookie1.png') # spawn enemy
enemy_list = pygame.sprite.Group() # create enemy group
enemy_list.add(enemy) # add enemy to group
if lvl == 2:
print("Level " + str(lvl) )
return enemy_list
def loot(lvl,lloc):
print(lvl)
enemy_list is defined in global namespace, in main.py, thus it is not accessible in the module Sprite.py.
Add an additional argument to the update method of the class Player:
class Player(pygame.sprite.Sprite):
# [...]
def update(self, dt, enemy_list):
# [...]
enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
# [...]
Since player is a member of all_sprites, you have to add the argument to the update methods of the other sprites (Enemy, Bullet), too.
Pass enemy_list to the update method all_sprites in the main application loop. Note the update method of Player is invoked by all_sprites.update, thus player.update(dt, enemy_list) is superflous:
while main == True:
# [...]
all_sprites.update(dt, enemy_list)
# [...]

Categories