Collision detection in Pygame between Player and sprite group mudballGroup - python

I am following along with a tutorial on how to detect collisions between the class Player and a sprite group MudballGroup. When setting up the collision in the statement pg.sprite.spritecollide(Player, mudballGroup, False) I get the error type object 'Player' has no attribute 'rect'. I have code from my Player sprite here that shows rect to be defined in the following statement: self.rect = self.image.get_rect(). I don't know why I am getting this error. Please if someone could help.
class Player(pg.sprite.Sprite):
def __init__(self, game):
pg.sprite.Sprite.__init__(self)
self.game = game
self.walking = False
self.jumping = False
self.current_frame = 0
self.last_update = 0
self.load_images()
self.image = self.girl_standing[0]
#Isn't this my rect attribute for Player?
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
self.pos = vec(WIDTH / 2, HEIGHT / 2)
self.vel = vec(0, 0)
self.acc = vec(0, 0)
self.screen = pg.display.set_mode((WIDTH, HEIGHT))
self.clock = pg.time.Clock()
def shoot(self):
mudball = MUDBALL(self.rect.centerx, self.rect.centery)
print("SHOOT function")
self.game.all_sprites.add(mudball)
mudballGroup = pg.sprite.Group()
mudballGroup.add(mudball)
# Me attempting collision
hits = pg.sprite.spritecollide(self.player, mudballGroup, False)
if hits:
print("HITS!!!!!!!!", hits)
def hailing(self):
jets = JETS()
print("FLYOVER")
self.game.all_sprites.add(jets)
jetsGroup = pg.sprite.Group()
jetsGroup.add(jets)
class MUDBALL(pg.sprite.Sprite):
def __init__(self, x, y):
pg.sprite.Sprite.__init__(self)
self.image = pg.image.load("SLIMEballGREEN.png")
# self.mudballGroup = pg.sprite.Group()
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.speedx = -30
def update(self):
self.rect.x += self.speedx
#kill if moves off screen
if self.rect.centerx < 0:
self.kill()
class JETS(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.image.load("JETSscaled.png")
#self.jetsGroup = pg.sprite.Group()
self.rect = self.image.get_rect()
self.rect.x = 1366
self.rect.y = 0
self.speedx = -70
def update(self):
self.rect.x += self.speedx
#kill if moves off screen
if self.rect.x + 848 < 0:
self.kill()

You're using the Player class for the collision detection, but you have to use an instance of this class instead.
# Player is the class, but spritecollide needs an instance.
hits = pg.sprite.spritecollide(Player, mudballGroup, False)
To create an instance of the Player class, just write:
# Don't forget to pass the game instance to the `Player`s __init__ method.
player = Player(game)
It also looks odd to me that you define the mudballGroup inside of the shoot method. That means the group will only contain one mudball, but then you could also just check if the rects of the player and mudball collide: player.rect.colliderect(mudball.rect) instead of spritecollide. However, if you want multiple mudballs you need to store the mudballGroup as an attribute of the other class, in the __init__ method write:
self.mudballGroup = pg.sprite.Group()
Edit: Okay, you already have a self.player instance in your game instance. I recommend to define the mudballGroup in the Game class as well and then just pass it and the all_sprites group to the player's shoot method to add a mudball. The collision detection can be done inside the game's update method.
class Game:
def new(self):
# Other code omitted ...
self.mudballGroup = pg.sprite.Group()
def update(self):
# Check if the player is shooting.
if self.player.shooting: # You have to add a `shooting` attribute to player.
# `shoot` just adds a mudball to these groups.
self.player.shoot(self.all_sprites, self.mudballGroup)
# Then detect collisions.
hits = pg.sprite.spritecollide(self.player, self.mudballGroup, False)
if hits:
print("HITS!!!!!!!!", hits)
class Player(pg.sprite.Sprite):
# Pass the sprite groups of the game to the shoot method.
def shoot(self, all_sprites, mudballGroup):
mudball = MUDBALL(self.player.centerx, self.player.centery)
# Add sprite to the passed groups.
all_sprites.add(mudball)
mudballGroup.add(mudball)
Edit 2: Here's the other variant. Pass the two needed sprite groups to the player when you create the instance (you don't have to pass the complete game instance), then set them as attributes of the player.
class Game:
def new(self):
# Other code omitted ...
self.all_sprites = pg.sprite.Group()
self.mudballGroup = pg.sprite.Group()
self.player = Player(self.all_sprites, self.mudballGroup)
def update(self):
# Check if the player is shooting.
if self.player.shooting:
# `shoot` adds a mudball to self.all_sprites & self.mudballGroup.
self.player.shoot()
# Then detect collisions.
hits = pg.sprite.spritecollide(self.player, self.mudballGroup, False)
if hits:
print("HITS!!!!!!!!", hits)
class Player(pg.sprite.Sprite):
def __init__(self, all_sprites, mudballGroup):
# Other code omitted ...
# These two attributes are references to the groups
# that were defined in the Game class.
self.all_sprites = all_sprites
self.mudballGroup = mudballGroup
def shoot(self):
mudball = MUDBALL(self.player.centerx, self.player.centery)
# Add sprite to the passed groups.
self.all_sprites.add(mudball)
self.mudballGroup.add(mudball)
Edit 3: Okay, forget the shooting attribute (it would only be needed for the player instance to check if the user is shooting). You also don't need to call trump's shoot method in the Game class, since you already call it in his update method. So here's the updated code:
class Game(pg.sprite.Sprite):
def new(self):
# Other code omitted ...
self.all_sprites = pg.sprite.Group()
self.mudballGroup = pg.sprite.Group()
# Pass the groups to trump during the instantiation.
# You don't have to pass the complete game instance (self).
self.trump = TRUMP(self.all_sprites, self.mudballGroup)
self.all_sprites.add(self.trump)
def update(self):
hits = pg.sprite.spritecollide(self.player, self.mudballGroup, False)
for collided_sprite in hits:
print("HIT", collided_sprite)
class TRUMP(pg.sprite.Sprite):
def __init__(self, all_sprites, mudballGroup):
# Now set the passed groups as attributes of the trump instance.
# These attributes are references to the same sprite groups as
# in the Game class.
self.all_sprites = all_sprites
self.mudballGroup = mudballGroup
def shoot(self):
print("SHOOT function")
mudball = MUDBALL(self.rect.centerx, self.rect.centery)
# Add the mudball to the groups.
self.all_sprites.add(mudball)
self.mudballGroup.add(mudball)

Related

Play animation upon collision with enemy

so I have loaded the images into pygame:
Explosion = [pygame.image.load('Explosion1.png'), pygame.image.load('Explosion2.png'), pygame.image.load('Explosion3.png'), pygame.image.load('Explosion4.png'), pygame.image.load('Explosion5.png'), pygame.image.load('Explosion6.png'), pygame.image.load('Explosion7.png'), pygame.image.load('Explosion8.png'), pygame.image.load('Explosion9.png'), pygame.image.load('Explosion10.png')]
and I would like, when the bullet, which is a separate class, makes a collision with the missile, it plays this animation at the position where both the bullet and the enemy collide, I'm not sure how I go around doing this?
Collision Script (In the main loop):
hits = pygame.sprite.groupcollide(enemies, bullets, True, True)
Bullet Class:
class Bullet (pygame.sprite.Sprite):
def __init__ (self, x, y):
super (Bullet, self).__init__()
self.surf = pygame.image.load("Bullet.png").convert()
self.surf.set_colorkey((255,255,255), RLEACCEL)
self.rect = self.surf.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.speedx = bullet_speed
def update(self):
self.rect.x += self.speedx
if self.rect.left > SCREEN_WIDTH:
self.kill()
Enemy Class:
class Enemy(pygame.sprite.Sprite):
def __init__(self):
super(Enemy, self).__init__()
self.surf = pygame.image.load("Missiles.png").convert()
self.surf.set_colorkey((255,255,255), RLEACCEL)
self.rect = self.surf.get_rect(
center=(
random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
random.randint(0, SCREEN_HEIGHT),
)
)
self.speed = random.randint(Enemy_SPEED_Min, Enemy_SPEED_Max)
def update(self):
self.rect.move_ip(-self.speed, 0)
if self.rect.right < 0:
self.kill()
all of the code is here https://pastebin.com/CG2C6Bkc if you need it!
thank you in advance!
Do not destroy the enemies, when it collides with an bullet. Iterate through the enemies and which are returned in hits and start the explosion animation instead of killing the enemies:
hits = pygame.sprite.groupcollide(enemies, bullets, True, True)
hits = pygame.sprite.groupcollide(enemies, bullets, False, True)
for enemy in hits:
enemy.start_animation()
Add the attributes animation_count, animation_frames and animation to th class Enemy. When the explosion animation is started then animation is set True. animation_frames controls the speed of the animation. If the animation is started, then the enemy stops to move and the Explosion image is shown instead of the enemy. After the last image of the animation is shown, the enemy is killed:
class Enemy(pygame.sprite.Sprite):
def __init__(self):
super(Enemy, self).__init__()
self.surf = pygame.image.load("Missiles.png").convert()
self.surf.set_colorkey((255,255,255), RLEACCEL)
self.rect = self.surf.get_rect(
center=(
random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
random.randint(0, SCREEN_HEIGHT),
)
)
self.speed = random.randint(Enemy_SPEED_Min, Enemy_SPEED_Max)
self.animation_count = 0
self.animation_frames = 10
self.animation = False
def start_animation(self):
self.animation = True
def update(self):
if self.animation:
image_index = self.animation_count // self.animation_frames
self.animation_count += 1
if image_index < len(Explosion):
self.surf = Explosion[image_index]
self.rect = self.surf.get_rect(center = self.rect.center)
else:
self.kill()
self.rect.move_ip(-self.speed, 0)
if self.rect.right < 0:
self.kill()
Well i dont want to do it for you, But i will say one way you could do it, then you can go and try to do it, and if you run into a problem/error, you can update the question and we can help you.
I would create another class called Explosion and and give it the images, when when you draw it, draw the first image, then change to the second, then draw that one, then change...
Then after it finishes, destroy the object.
So, create the class, create a spritegroup, when the bullet collides, create a new instance of the class, giving it the x and y co ordinates of the collision, update it every frame, and it will draw all the images then destroy itself
also, an easier way to get all of your images instead of typing them all out is
Explosion_imgs = [pygame.image.load("explosion" + str(x) + ".png") for x in range(1,11,1)]

How to know which sprite has collided from the group?

I created a class and each member of this class is a member of my group of sprites. I test the collision between my player and the group with:
pygame.sprite.spritecollide(self,surprise_sprites,False)
and I would like to know which sprite of my group has collided in order to use function from their class.
class Surprise(pygame.sprite.Sprite):
def __init__(self,x,y,win):
pygame.sprite.Sprite.__init__(self, sol_sprites)
pygame.sprite.Sprite.__init__(self, surprise_sprites)
self.width = TILESIZE
self.height = TILESIZE
self.image = Block_surprise
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.y = y
self.x = x
self.win = win
self.exist = True
def update(self):
if self.exist:
self.collision()
win.blit(self.image,(camera.apply_player([self.rect.x]),self.rect.y))
def collision(self):
blocks_hit_list = pygame.sprite.spritecollide(self,player_sprite,False)
if not (blocks_hit_list == []):
self.exist = False
self.image = brick_img
print("TOUCHE")
def i_want_to_execute_a_function_here(self):
pygame.sprite.spritecollide() returns the list of the sprites which collided.
A sprite list can be traversed:
blocks_hit_list = pygame.sprite.spritecollide(self,surprise_sprites,False)
for hit_sprite in blocks_hit_list:
# [...] whatever e.g.
# hit_sprite.myMethod();

Python pygame - Deleting offscreen sprites

I created a simple 2D game with python 2 and pygame where you have to avoid squares that are moving down. I created this class for the 'enemy':
class Enemy(pygame.sprite.Sprite):
def __init__(self, game):
self.groups = game.all_sprites
pygame.sprite.Sprite.__init__(self, self.groups)
self.game = game
self.image = pygame.Surface((TILESIZE + 10, TILESIZE + 10))
self.image.fill(ENEMY_COLOR)
self.rect = self.image.get_rect()
self.x = random.uniform(0, WIDTH - TILESIZE)
self.rect.x = self.x
self.y = 0
def update(self):
if self.rect.colliderect(self.game.player.rect):
self.game.deaths += 1
self.game.score = 0
self.game.run()
self.rect.y += (self.game.score + 500) / 50
Then, I have a thread that creates an instance of the enemy:
class Create_Enemy(Thread):
def __init__(self):
Thread.__init__(self)
def run(self):
while True:
while not game.game_paused:
time.sleep((game.score + 1) / game.speed)
Enemy(game)
Then in the draw method i just write self.all_sprites.draw()
The game is an infinite runner, and every frame the enemies move a few pixels down. The problem is that after a bit the game gets laggy since, when the blocks go offscreen, the sprites don't get deleted.
Is there a way to automatically delete the offscreen instances?
I tried the following but it just deletes all the enemies onscreen.
if self.rect.y >= WIDTH:
self.rect.kill()
I was thinking I could do something like game.all_sprites.pop(1) (the position 0 is the player) but I couldn't find a way to archive something similar ...
The enemies can remove themselfs from the game by calling self.kill() if their rect is no longer inside the screen, e.g.:
def update(self):
if self.rect.colliderect(self.game.player.rect):
self.game.restart() # reset score + count deaths
# we can simply use move_ip here to move the rect
self.rect.move_ip(0, (self.game.score + 500) / 100)
# check if we are outside the screen
if not self.game.screen.get_rect().contains(self.rect):
self.kill()
Also, instead of the thread, you can use pygame's event system to spawn your enemies. Here's a simple, incomplete but runnable example (note the comments):
import random
import pygame
TILESIZE = 32
WIDTH, HEIGHT = 800, 600
# a lot of colors a already defined in pygame
ENEMY_COLOR = pygame.color.THECOLORS['red']
PLAYER_COLOR = pygame.color.THECOLORS['yellow']
# this is the event we'll use for spawning new enemies
SPAWN = pygame.USEREVENT + 1
class Enemy(pygame.sprite.Sprite):
def __init__(self, game):
# we can use multiple groups at once.
# for now, we actually don't need to
# but we could do the collision handling with pygame.sprite.groupcollide()
# to check collisions between the enemies and the playerg group
pygame.sprite.Sprite.__init__(self, game.enemies, game.all)
self.game = game
self.image = pygame.Surface((TILESIZE + 10, TILESIZE + 10))
self.image.fill(ENEMY_COLOR)
# we can use named arguments to directly set some values of the rect
self.rect = self.image.get_rect(x=random.uniform(0, WIDTH - TILESIZE))
# we dont need self.x and self.y, since we have self.rect already
# which is used by pygame to get the position of a sprite
def update(self):
if self.rect.colliderect(self.game.player.rect):
self.game.restart() # reset score + count deaths
# we can simply use move_ip here to move the rect
self.rect.move_ip(0, (self.game.score + 500) / 100)
# check if we are outside the screen
if not self.game.screen.get_rect().contains(self.rect):
self.kill()
class Player(pygame.sprite.Sprite):
def __init__(self, game):
pygame.sprite.Sprite.__init__(self, game.all, game.playerg)
self.game = game
self.image = pygame.Surface((TILESIZE, TILESIZE))
self.image.fill(PLAYER_COLOR)
self.rect = self.image.get_rect(x=WIDTH/2 - TILESIZE/2, y=HEIGHT-TILESIZE*2)
def update(self):
# no nothing for now
pass
class Game(object):
def __init__(self):
# for now, we actually don't need mujtiple groups
# but we could do the collision handling with pygame.sprite.groupcollide()
# to check collisions between the enemies and the playerg group
self.enemies = pygame.sprite.Group()
self.all = pygame.sprite.Group()
self.playerg = pygame.sprite.GroupSingle()
self.running = True
self.score = 0
self.deaths = -1
self.clock = pygame.time.Clock()
def restart(self):
# here we set the timer to create the SPAWN event
# every 1000 - self.score * 2 milliseconds
pygame.time.set_timer(SPAWN, 1000 - self.score * 2)
self.score = 0
self.deaths += 1
def run(self):
self.restart()
self.player = Player(self)
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
while self.running:
for e in pygame.event.get():
if e.type == pygame.QUIT:
self.running = False
# when the SPAWN event is fired, we create a new enemy
if e.type == SPAWN:
Enemy(self)
# draw and update everything
self.screen.fill(pygame.color.THECOLORS['grey'])
self.all.draw(self.screen)
self.all.update()
pygame.display.flip()
self.clock.tick(40)
if __name__ == '__main__':
Game().run()
Check the values of self.rect.y, WIDTH, maybe the method kill. It looks like self.rect.y is always greater or equal WIDTH, that's why it kills them all.

SpriteGroup - adding muliple objects

I am trying to create a sprite class where the user can define and add any number of sprites on to the screen from a random location using this tutorial. However, when I try to run my current program it puts the error
AttributeError: type object 'Sprite' has no attribute 'sprite'
But I dont understand why, all the logic seems correct.
Any suggestions?
Heres my code:
import pygame, sys, random
pygame.init()
black = (0, 0, 0)
image = pygame.image.load("resources/images/img.png")
SCREEN_WIDTH = 640
SCREEN_HEIGHT = 400
sprite_width = 5
sprite_height = 5
sprite_count = 5
# no changes here when using sprite groups
class Sprite(pygame.sprite.Sprite):
def __init__(self, Image, pos):
pygame.sprite.Sprite.__init__(self)
self.image =pygame.image.load("resources/images/img.png")
self.rect = self.image.get_rect()
self.rect.topleft = pos
self.pygame.display.set_caption('Sprite Group Example')
self.clock = pygame.time.Clock()
# this is a how sprite group is created
self.sprite = pygame.sprite.Group()
def update(self):
self.rect.x += 1
self.rect.y += 2
if self.rect.y > SCREEN_HEIGHT:
self.rect.y = -1 * sprtie_height
if self.rect.x > SCREEN_WIDTH:
self.rect.x = -1 * sprite_width
#classmethod
def sprites(self):
for i in range(actor_count):
tmp_x = random.randrange(0, SCREEN_WIDTH)
tmp_y = random.randrange(0, SCREEN_HEIGHT)
# all you have to do is add new sprites to the sprite group
self.sprite.add(Sprite(image, [tmp_x, tmp_y]))
#classmethod
def game_loop(self):
screen = pygame.display.set_mode([640, 400])
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill(black)
# to update or blitting just call the groups update or draw
# notice there is no for loop
# this will automatically call the individual sprites update method
self.sprite.update()
self.sprite.draw(screen)
self.pygame.display.update()
self.clock.tick(20)
Sprite.sprites()
Sprite.game_loop()
You define class method (#classmethod) but self.sprite exists only in object/instance not in class - it is created in __init__ when you crate new object/instance.
Remove #classmethod to have object/instance method and have no problem with self.sprite.
Or create sprite outside __init__ to get class attribute
class Sprite(pygame.sprite.Sprite):
sprite = pygame.sprite.Group()
def __init__(self, Image, pos)
# instance variable - value copied from class variable
print self.sprite
# class variable
print self.__class__.sprite
#classmethod
def sprites(self):
# instance variable not exists
# class variable
print self.sprite

how does collide.rect work in pygame

I'm having some trouble understanding how colliderect works with sprites. I have a good idea about it, but whenever I try to implement it into my game, I just get the error message "attributeError:'pygame.surface' object has no attribute 'rect'"
ufo_lvl_1 = pygame.image.load("ufo1.png")
bullet = pygame.image.load("bullet.png")
class Bullet(pygame.sprite.Sprite):
def __init__(self):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
self.image = bullet
self.damage = 5
self.rect = self.image.get_rect()
def update(self):
if 1 == 1:
self.rect.x += 15
if self.rect.x >1360:
self.kill()
class ufo1(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = ufo_lvl_1
self.health = 10
self.rect = self.image.get_rect()
def update(self):
if 1==1:
self.rect.x -= 10
if self.rect.colliderect(bullet.rect):
self.health -= bullet.damage
if self.health >= 0:
self.kill()
bullet.kill()
Basically all my sprites work (excluding ufo1), but the moment I create a ufo1 sprite it crashes and I don't know how to fix it.
Thanks in advance.
you called your class Bullet and your surface for that class bullet. When you are calling colliderect you are having it check bullet instead of Bullet or whatever you named the object of class Bullet

Categories

Resources