I am trying to implement movement from my enemy class to follow the player class. Currently, the enemy class only moves in a straight line to the left. Here is some code to better understand my question.
Here is the player class
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 40))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.left = 25
self.rect.bottom = HEIGHT / 2
self.speedx = 0
self.speedy = 0
Here is the enemy class
class Enemy(pygame.sprite.Sprite):
def __init__(self2):
pygame.sprite.Sprite.__init__(self2)
self2.image = pygame.Surface((50, 40))
self2.image.fill(RED)
self2.rect = self2.image.get_rect()
self2.rect.x = random.randrange(680, 750)
self2.rect.y = random.randrange(HEIGHT - self2.rect.height)
self2.speedx = random.randrange(-5, -3)
self2.speedy = random.randrange(-5, 5)
self2.min_dist = 200
def update(self2):
self2.speedy = 0
self2.rect.x += self2.speedx
self2.rect.y += self2.speedy
if self2.rect.left < -25 or self2.rect.top < -25 or self2.rect.bottom > HEIGHT + 10:
self2.rect.x = random.randrange(680, 750)
self2.rect.y = random.randrange(HEIGHT - self2.rect.height)
self2.speedx = random.randrange(-5, -3)
def move_towards_Player(self2, Player):
delta_x = Player.rect.x - self2.rect.x
delta_y = Player.rect.y - self2.rect.y
if abs(delta_x) <= self2.min_dist and abs(delta_y) <= self2.min_dist:
enemy_move_x = abs(delta_x) > abs(delta_y)
if abs(delta_x) > self2.speedx and abs(delta_x) > self2.speedx:
enemy_move_x = random.random() < 0.5
if enemy_move_x:
self2.rect.x += min(delta_x, self2.speedx) if delta_x > 0 else max(delta_x, -self2.speedx)
else:
self2.rect.y += min(delta_y, self2.speedy) if delta_y > 0 else max(delta_y, -self2.speedy)
all_sprites = pygame.sprite.Group()
enemies = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
for i in range(3):
e = Enemy()
all_sprites.add(e)
enemies.add(e)
running = True
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
all_sprites.update()
#Draw render
screen.fill(BLACK)
all_sprites.draw(screen)
pygame.display.flip()
pygame.quit()
quit()
Sorry for the large post in code just would like to get the best answer for this.
Question 1) This is probably a very simple question but, how come when the all_sprites.Group get updated in the loop why doesn't the move_towards player get updated?
Question 2) I tried letting the update inherit Player, like this
def update(self2, Player):
why doesn't this work?
Question 3) How can I make the move_towards_Player get updated in the loop?
how come when the all_sprites.Group get updated in the loop why doesn't the move_towards player get updated?
You ask why the move_towards_Player function is not called? Because you never call it, and it isn't magically called by anything. The update function of Group will call the update function of all its sprites. Nothing more.
I tried letting the update inherit Player, like this ... why doesn't this work?
The update function of Group will pass all arguments to the update function of all its sprites. So you could call it like this:
all_sprites.update(player)
and ensure that all sprite classes have their update function take an extra argument beside self.
How can I make the move_towards_Player get updated in the loop?
Just call it from the Enemy's update function.
You could start with something like this:
import pygame
import random
GREEN=(0,255,0)
RED=(255,0,0)
BLACK=(0,0,0)
HEIGHT=600
WIDTH=800
FPS=120
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 40))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.left = 25
self.rect.bottom = HEIGHT / 2
self.speedx = 0
self.speedy = 0
class Enemy(pygame.sprite.Sprite):
def __init__(self, target):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 40))
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.x = random.randrange(680, 750)
self.rect.y = random.randrange(HEIGHT - self.rect.height)
self.target = target
self.speed = random.randint(4, 6)
def update(self, dt):
self.move_towards_Player(dt)
def move_towards_Player(self, dt):
pos = pygame.Vector2(self.rect.center)
v = pygame.Vector2(self.target.rect.center) - pos
if (v.length() < 5):
self.kill()
else:
v.normalize_ip()
v *= self.speed * dt/10
pos += v
self.rect.center = round(pos.x), round(pos.y)
all_sprites = pygame.sprite.Group()
enemies = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
for i in range(3):
e = Enemy(player)
all_sprites.add(e)
enemies.add(e)
clock=pygame.time.Clock()
pygame.init()
screen=pygame.display.set_mode([WIDTH, HEIGHT])
running = True
while running:
dt=clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
all_sprites.update(dt)
#Draw render
screen.fill(BLACK)
all_sprites.draw(screen)
pygame.display.flip()
pygame.quit()
quit()
Note that:
I renamed self2 to self. By convention, the first argument of a method should be called self. Stick to it.
I pass the Player instance to the Enemy-class' __init__ function and store it in a class member. This way, we don't have to "pollute" the update function
I use pygame's Vector2 class to handle the math.
I pass the delta time to the update function so the movement speed is constant even when the framerate is not
Related
I am building a 2d game using pygame,how do i make the ground collide with the player
here is my game code, I am new to pygame and python
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.surf = pygame.image.load('img/PlayerIdle1.png')
self.surf = pygame.transform.smoothscale(self.surf,(44,44))
self.rect = self.surf.get_rect(center = (10, 420))
self.pos = vec((10, 360))
self.vel = vec(0,0)
self.acc = vec(0,0)
def gravity(self):
self.acc = vec(0,0.5)
self.acc.x += self.vel.x * FRIC
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
self.rect.midbottom = self.pos
this update method for collision is not working
def update(self):
hits = pygame.sprite.spritecollide(p1 ,Gro_und, False)
if p1.vel.y > 0:
if hits:
self.vel.y = 0
self.pos.y = hits[0].rect.top + 1
print('got hit')
def render(self):
screen.blit(self.surf, self.rect)
screen.blit(self.surf, self.rect)
class Ground(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.cimage = pygame.image.load('img/gnd.png')
self.cimage = pygame.transform.smoothscale(self.cimage,(800,200))
self.rectCimg = self.cimage.get_rect()
self.surf = self.cimage
self.rect = self.rectCimg
self.cY1 = 400
self.cX1 = 0
self.cY2 = 400
self.cX2 = self.rectCimg.width
self.scroll_speed = 20
def update(self):
self.cX1 -= self.scroll_speed
self.cX2 -= self.scroll_speed
if self.cX1 <= -self.rectCimg.width:
self.cX1 = self.rectCimg.width
if self.cX2 <= -self.rectCimg.width:
self.cX2 = self.rectCimg.width
def render(self):
screen.blit(self.cimage, (self.cX1, self.cY1))
screen.blit(self.cimage, (self.cX2, self.cY2))
def gravity(self):
pass
p1 = Player()
back_ground = Background()
Gro_und = Ground()
all_sprites = pygame.sprite.Group()
all_sprites.add(p1)
all_sprites.add(Gro_und)
run = True
while run:
clock.tick(fps)
#draw and scroll the ground
back_ground.update()
back_ground.render()
Gro_und.update()
Gro_und.render()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for entity in all_sprites:
entity.update()
entity.render()
entity.gravity()
pygame.display.update()
pygame.quit()
I am getting ground object not iterable error while running the update function in player class for collision
what should i do to to solve this
If I'm not mistaken, the reason you're getting a Ground object not iterable error is because the pygame.sprite.spritecollide function is supposed to receive the sprite you want to check for collision with (in your case, p1), but the second parameter should be an instance of pygame.sprite.Group, which is iterable (unlike a normal sprite, which your Ground object is based upon.
I see that you have already made a sprite group, all_sprites, but you should create another one for just the ground (for example, ground_sprites), and then change the function call in your update function to be like so:
def update(self):
hits = pygame.sprite.spritecollide(p1 ,ground_sprites, False)
if p1.vel.y > 0:
if hits:
self.vel.y = 0
self.pos.y = hits[0].rect.top + 1
print('got hit')
I'm trying out pygame, using Sprites & Groups, and getting myself a little confused around passing arguments.
Basically I have a wrapper class MyGame() as my_game(). In that I have pygame.sprite.Group() - all_sprites.
I then have a class Alien(pygame.sprite.Sprite) for my aliens, and I add each alien to my all_sprites Group.
I want my Group of aliens to track across the screen (which they do) and then all drop as a block, a group (which they don't).
I can loop through my all_sprites.Group in my main loop after the self.all_sprites.update() and see if alien.rect.right > WIDTH or alien.rect.left < 0 , then loop through the Group again calling alien.end_row() then break the loop so it doesn't run for each alien on the screen edge, but that seems very clunky.
I've tried setting a flag in MyGame self.alien_drop = False but when I try to set my_game.alien_drop = True in the Alien class, it doesn't recognise my_game - not defined. I'm a little confused as MyGame is creating the instances of Alien, so they should be enclosed by the scope of MyGame?
I can pass MyGame self.alien_drop into my Alien init() but when I update the Alien self.alien_drop it doesn't update MyGame.alien_drop. Which I get because it's created a new variable local to Alien.
There doesn't seem to be a way to pass an argument through Group.update() which I guess is because it's just calling the .update() on all Sprite inside the group. I can't see an easy way to modify the Group.update() function so that I can pass values, and in fairness I probably don't want to go mucking around in there anyway.
I also can't return True back through update().
I'm kinda stuck at this point...
I know the self.aliens.row_end() probably won't work, I'll have to loop through self.aliens and call each alien.row_end(), but at the moment it's not even getting to that point.
import pygame
WIDTH = 360 # width of our game window
HEIGHT = 480 # height of our game window
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
class MyGame():
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
self.clock = pygame.time.Clock()
self.all_sprites = pygame.sprite.Group()
self.alien_drop = False
for row_index in range(4):
for column_index in range(6):
alien = Alien(20 + (column_index * 40), 20 + (row_index *40))
alien.add(self.all_sprites)
while True:
self.screen.fill(BLACK)
self.all_sprites.update()
if self.alien_drop:
self.aliens.row_end()
self.all_sprites.draw(self.screen)
pygame.display.update()
self.clock.tick(60)
class Alien(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((25, 25))
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.image.fill(GREEN)
self.dx = 1
def update(self):
self.rect.x += self.dx
if self.rect.right >= WIDTH or self.rect.left <= 0:
my_game.alien_drop = True
def row_end(self):
self.dx *= -1
self.rect.y += 40
if __name__ == "__main__":
my_game = MyGame()
So to do the traditional Space Invaders move and drop, the entire row drops when any of the invaders hits either side.
In the example below I have modified the Alien.update() to detect when the screen-side is hit, but if-so, only set a Boolean flag Alien.drop to True.
The algorithm becomes: First move all the aliens. Next, check if any alien hit the side-wall by checking this flag. If so, move every alien down 1 step & stop checking.
The Alien.row_end() function moves the aliens down. It also needs to clear the Alien.drop flag, so they don't all move down again.
import pygame
WIDTH = 360 # width of our game window
HEIGHT = 480 # height of our game window
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
class MyGame():
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
self.clock = pygame.time.Clock()
self.all_sprites = pygame.sprite.Group()
self.alien_drop = False
for row_index in range(4):
for column_index in range(6):
alien = Alien(20 + (column_index * 40), 20 + (row_index *40))
alien.add(self.all_sprites)
exiting = False
while not exiting:
# Handle events
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
exiting = True # exit this loop
self.screen.fill(BLACK)
self.all_sprites.update()
for alien1 in self.all_sprites:
if ( alien1.shouldDrop() ):
### If any alien drops, we all drop
for alien2 in self.all_sprites:
alien2.row_end()
break # only drop once
if self.alien_drop:
self.aliens.row_end()
self.all_sprites.draw(self.screen)
pygame.display.update()
self.clock.tick(60)
class Alien(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((25, 25))
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.image.fill(GREEN)
self.dx = 1
self.drop = False # should I drop-down a level
def update(self):
self.rect.x += self.dx
if self.rect.right >= WIDTH or self.rect.left <= 0:
self.drop = True
else:
self.drop = False
def shouldDrop( self ):
""" In this alien in a position where it should drop down
one row in the screen-space (i.e.: it's hit a side) """
return self.drop
def row_end(self):
self.dx *= -1
self.rect.y += 40
self.drop = False # drop drop again just yet
if __name__ == "__main__":
my_game = MyGame()
import pygame
import os
import random
from pygame.locals import * # Constants
import math
import sys
import random
pygame.init()
screen=pygame.display.set_mode((1280,700)) #(length,height)
screen_rect=screen.get_rect()
background = pygame.Surface(screen.get_size())
background.fill((255,255,255)) # fill the background white
background = pygame.image.load('stage.png').convert()
Black=(0,0,0)
class Player(pygame.sprite.Sprite):
x = 20
y = 615
def __init__(self):
super().__init__() # calls the parent class allowing sprite to initalize
self.image = pygame.Surface((50,25)) # this is used to create a blank image with the size inputted
self.image.fill((0,0,128)) # fills the blank image with colour
self.rect = self.image.get_rect(topleft =(20,615)) # This place the player at the given position
self.dist = 10
def update(self): # code to make the character move when the arrow keys are pressed
if self.rect.right > 100: # These are to make the player so move constantly
self.rect.y += 1
self.rect.x += 2
if self.rect.bottom == 700:
pygame.quit()
keys = pygame.key.get_pressed()
if keys[K_LEFT]:
self.rect.move_ip(-1,0)
elif keys[K_RIGHT]:
self.rect.move_ip(0.5,0)
elif keys[K_UP]:
self.rect.move_ip(0,-0.5)
elif keys[K_DOWN]:
self.rect.move_ip(0,1)
self.rect.clamp_ip(screen_rect)
#while self.rect == (20,615):
if keys [K_SPACE]:
self.rect = self.image.get_rect(topleft =(100,100))
class Enemy(pygame.sprite.Sprite): # the enemy class which works fine
def __init__(self):
super().__init__()
#y = random.randint(300,1200)
x = random.randint(50,450)
self.image = pygame.Surface((50,25))
self.image.fill((128,0,0))
self.rect = self.image.get_rect(topleft=(300, 50))
def update(self):
if self.rect.bottom <= 400:
self.rect.y += 1
if self.rect.bottom >= 400:
self.rect.y -= 300
On this part i got so that the enemy class moves downwards and when it reaches 400 it teleport 300 upwards but i wanted it so that it constantly moves upwards and then downwards again.
I thought that i either cancel the movement downwards when it reaches the position but i don't think you can do that.
clock = pygame.time.Clock() # A clock to limit the frame rate.
player = Player()
enemy = Enemy()
enemy_list = pygame.sprite.Group(enemy)
#enemy_list.add(enemy)
sprites = pygame.sprite.Group(player, enemy)
def main(): #my main loop
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
sprites.update()
screen.blit(background, (0, 0))
sprites.draw(screen)
clock.tick(100) # Limit the frame rate to 60 FPS.
pygame.display.flip() #updates the whole screen
#Collison check
player_hit_list = pygame.sprite.spritecollide(player, enemy_list, True)
for enemy in player_hit_list:
pygame.quit()
if __name__ == '__main__':
main()
The Enemy class needs an additional attribute that keeps track of the direction it's moving: up or down.
So a simple solution could look like this:
class Enemy(pygame.sprite.Sprite): # the enemy class which works fine
def __init__(self):
super().__init__()
x = random.randint(50,450)
self.image = pygame.Surface((50,25))
self.image.fill((128,0,0))
self.rect = self.image.get_rect(topleft=(300, 50))
self.direction = 'DOWN'
def update(self):
self.rect.y += 1 if self.direction == 'DOWN' else -1
if self.rect.bottom >= 400:
self.direction = 'UP'
if self.rect.top <= 50:
self.direction = 'DOWN'
I'm quite new to pygame and came across a bug that i just can't fix on my own. I'm trying to program a Flappy Bird game. The Problem is that the collision detection works, but it also messes with my sprites. If i manage to get past the first obstacle while playing, then the gap resets itself randomly. But the gap should always be the same, just on another position. If i remove the collision detection, it works perfectly fine. Any ideas?
import pygame
import random
randomy = random.randint(-150, 150)
class Bird(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((25,25))
self.image.fill((255,255,255))
self.rect = self.image.get_rect()
self.rect.center = (100, 200)
self.velocity = 0.05
self.acceleration =0.4
def update(self):
self.rect.y += self.velocity
self.velocity += self.acceleration
if self.rect.bottom > 590:
self.velocity = 0
self.acceleration = 0
class Pipe1(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((85, 500))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect()
self.rect.center = (500, randomy)
self.randomyupdate = random.randint(-150, 150)
def update(self):
self.rect.x -= 2
if self.rect.x < -90:
self.randomyupdate = random.randint(-150, 150)
self.rect.center = (450, self.randomyupdate)
class Pipe2(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((85, 500))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect()
self.rect.center = (500, (randomy +640))
def update(self):
self.rect.x -= 2
self.randomyupdate = Pipe1.randomyupdate
if self.rect.x < -90:
self.rect.center = (450, (self.randomyupdate + 640))
pygame.init()
pygame.mouse.set_visible(1)
pygame.key.set_repeat(1, 30)
pygame.display.set_caption('Crappy Bird')
clock = pygame.time.Clock()
Bird_sprite = pygame.sprite.Group()
Pipe_sprite = pygame.sprite.Group()
Bird = Bird()
Pipe1 = Pipe1()
Pipe2 = Pipe2 ()
Bird_sprite.add(Bird)
Pipe_sprite.add(Pipe1)
Pipe_sprite.add(Pipe2)
def main():
running = True
while running:
clock.tick(60)
screen = pygame.display.set_mode((400,600))
screen.fill((0,0,0))
Bird_sprite.update()
Pipe_sprite.update()
Bird_sprite.draw(screen)
Pipe_sprite.draw(screen)
The line im talking about:
collide = pygame.sprite.spritecollideany(Bird, Pipe_sprite)
if collide:
running = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.event.post(pygame.event.Event(pygame.QUIT))
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
Bird.rect.y -= 85
Bird.velocity = 0.05
Bird.acceleration = 0.4
Bird.rect.y += Bird.velocity
Bird.velocity += Bird.acceleration
pygame.display.flip()
if __name__ == '__main__':
main()
This has nothing to do with the collision detection, it has to do with the order of the sprites in the sprite group which can vary because sprite groups use dictionaries internally which are unordered (in Python versions < 3.6). So if the Pipe1 sprite comes first in the group, the game will work correctly, but if the Pipe2 sprite comes first, then its update method is also called first and the previous randomyupdate of Pipe1 is used to set the new centery coordinate of the sprite.
To fix this you could either turn the sprite group into an ordered group, e.g.
Pipe_sprite = pygame.sprite.OrderedUpdates()
or update the rect of Pipe2 each frame,
def update(self):
self.rect.x -= 2
self.rect.centery = Pipe1.randomyupdate + 640
if self.rect.x < -90:
self.rect.center = (450, Pipe1.randomyupdate + 640)
Also, remove the global randomy variable and always use the randomyupdate attribute of Pipe1.
I have created two simple sprites in PyGame and one of them is an Umbrella, the other one is a rain drop.
The Raindrops are added into a sprite group called all_sprites. The Umbrella sprite has its own group called Umbrella_sprite
The raindrops are "falling" from top of the screen and if one of them touches the umbrella / collides with it.. the raindrop is supposed to be deleted. BUT instead of that specific raindrops all other are affected by this.
main file (rain.py)
#!/usr/bin/python
VERSION = "0.1"
import os, sys, raindrop
from os import path
try:
import pygame
from pygame.locals import *
except ImportError, err:
print 'Could not load module %s' % (err)
sys.exit(2)
# main variables
WIDTH, HEIGHT, FPS = 300, 300, 30
# initialize game
pygame.init()
screen = pygame.display.set_mode((WIDTH,HEIGHT))
pygame.display.set_caption("Rain and Rain")
# background
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((40,44,52))
# blitting
screen.blit(background,(0,0))
pygame.display.flip()
# clock for FPS settings
clock = pygame.time.Clock()
def main():
all_sprites = pygame.sprite.Group()
umbrella_sprite = pygame.sprite.Group()
# a function to create new drops
def newDrop():
nd = raindrop.Raindrop()
all_sprites.add(nd)
# creating 10 rain drops
for x in range(0,9): newDrop()
# variable for main loop
running = True
# init umbrella
umb = raindrop.Umbrella()
# all_sprites.add(umb)
umbrella_sprite.add(umb)
# event loop
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
for enemy in all_sprites:
gets_hit = pygame.sprite.spritecollideany(umb, all_sprites)
if gets_hit:
all_sprites.remove(enemy)
screen.blit(background,(100,100))
# clear
all_sprites.clear(screen,background)
umbrella_sprite.clear(screen,background)
# update
all_sprites.update()
umbrella_sprite.update()
# draw
all_sprites.draw(screen)
umbrella_sprite.draw(screen)
# flip the table
pygame.display.flip()
pygame.quit()
if __name__ == '__main__':
main()
raindrop.py ( Raindrop() & Umbrella() )
import pygame
from pygame.locals import *
from os import path
from random import randint
from rain import HEIGHT, WIDTH
img_dir = path.join(path.dirname(__file__), 'img')
class Raindrop(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.width = randint(32, 64)
self.height = self.width + 33
self.image = pygame.image.load(path.join(img_dir, "raindrop.png")).convert_alpha()
self.image = pygame.transform.scale(self.image, (self.width, self.height))
self.speedy = randint(1, 15)
self.rect = self.image.get_rect()
self.rect.x = randint(0, 290)
self.rect.y = -self.height
def reset(self):
self.rect.y = -self.height
def update(self):
self.rect.y += self.speedy
if self.rect.y >= HEIGHT:
self.rect.y = -self.height
self.rect.x = randint(0, 290)
class Umbrella(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.width = 50
self.height = 50
self.image = pygame.image.load(path.join(img_dir,"umbrella.png")).convert_alpha()
self.image = pygame.transform.scale(self.image, (self.width, self.height))
self.speedx = 10
self.rect = self.image.get_rect()
self.rect.x = (WIDTH/2) - self.width
self.rect.y = (0.7 * HEIGHT)
def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and self.rect.x > 0:
self.rect.x -= self.speedx
elif keys[pygame.K_RIGHT] and self.rect.x < (WIDTH - self.width):
self.rect.x += self.speedx
This is your problem:
for enemy in all_sprites:
gets_hit = pygame.sprite.spritecollideany(umb, all_sprites)
if gets_hit:
all_sprites.remove(enemy)
You're looping through the group, and if any sprite collides, deleting all of them.
You don't need to loop through the group - the collision functions take care of that. You just need to use the spritecollide function, which compares a sprite versus a group. That function will return a list of collisions, as well as using the DOKILL flag to delete them automatically:
gets_hit = pygame.sprite.spritecollide(umb, all_sprites, True)
spritecollideany checks if the sprite collides with any sprite in the group and returns this sprite, so gets_hit is a trueish value as long as the collided sprite in the group is not removed and the if gets_hit: block gets executed. That means the code in the for loop simply keeps deleting every sprite in the group that appears before the collided sprite is reached and removed. A simple fix would be to check if the hit sprite is the enemy: if enemy == gets_hit:, but the code would still be inefficient, because spritecollideany has to loop over the all_sprites group again and again inside of the for loop.
I recommend to use spritecollide instead of spritecollideany as well, since it's more efficient and just one line of code.