I am trying to build a simple version of Flappy Bird. To detect collisions between my circle(Flappy Bird) and my rectangles(Pipes) I was using pygame.sprite.collide_rect() but I wanted a better way of handling collisions.
But using mask collision causes no detection of the collision. The circle passes directly through the rectangle as if it isn't there.
Here is my code:
bird_group = pygame.sprite.Group()
pipe_group = pygame.sprite.Group()
class Bird(pygame.sprite.Sprite):
def __init__(self, x_loc, y_loc, velocity):
super(Bird, self).__init__()
self.velocity = velocity
self.x_loc = x_loc
self.y_loc = y_loc
self.image = pygame.image.load(os.path.join(game_folder,"index2.png")).convert()
self.image.set_colorkey(WHITE)
self.image = pygame.transform.scale(self.image,(60,65))
self.rect = self.image.get_rect()
self.rect.center = (x_loc,y_loc)
def update(self):
self.rect.y += self.velocity
self.velocity = self.velocity+1
self.mask = pygame.mask.from_surface(self.image)
def jump(self):
self.velocity = -10
def boundary_collison(self):
if self.rect.bottom+100>=display_height or self.rect.top<=0:
return True
class UpperPipe(pygame.sprite.Sprite):
"""docstring for UpperPipe"""
def __init__(self, pipe_x, pipe_height, pipe_speed):
super(UpperPipe, self).__init__()
self.pipe_speed = pipe_speed
self.image = pygame.Surface((pipe_width, pipe_height))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.x = (pipe_x)
self.rect.y = (0)
def update(self):
self.rect.x -= self.pipe_speed
self.mask = pygame.mask.from_surface(self.image)
def x_cord(self):
return self.rect.x
class LowerPipe(pygame.sprite.Sprite):
"""docstring for UpperPipe"""
def __init__(self, pipe_x, pipe_height, pipe_speed):
super(LowerPipe, self).__init__()
self.pipe_speed = pipe_speed
self.image = pygame.Surface((pipe_width, display_height-(pipe_gap+pipe_height)))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.x = (pipe_x)
self.rect.y = (pipe_height+pipe_gap)
def update(self):
self.rect.x -= self.pipe_speed
self.mask = pygame.mask.from_surface(self.image)
def x_cord(self):
return self.rect.x
The following code I use to make the sprites:
bird = Bird(x_loc,y_loc,velocity)
bird_group.add(bird)
pipe_list = []
init_pipe_x = 500
for make in range(pipe_count):
pipe_x = init_pipe_x+((between_pipe+pipe_width)*make)
pipe_height = (round(random.uniform(0.2,0.8), 2))*(display_height-pipe_gap)
upper = UpperPipe(pipe_x,pipe_height,pipe_speed)
lower = LowerPipe(pipe_x,pipe_height,pipe_speed)
add_pipe = [upper,lower]
pipe_list.append(add_pipe)
pipe_group.add(upper)
pipe_group.add(lower)
For detection inside my game loop I use the following code:
bird_hits = pygame.sprite.spritecollide(bird,pipe_group,False,pygame.sprite.collide_mask)
if bird_hits:
gameExit = True
The pygame.Surfaces that you pass to mask.from_surface must have an alpha channel. That means you either have to call the convert_alpha or the set_colorkey method of the surfaces.
class UpperPipe(pygame.sprite.Sprite):
def __init__(self, pipe_x, pipe_height, pipe_speed):
super(LowerPipe, self).__init__()
self.pipe_speed = pipe_speed
# Either call `convert_alpha` ...
self.image = pygame.Surface((pipe_width, display_height-(pipe_gap+pipe_height))).convert_alpha()
self.image.fill(GREEN)
# ... or call `set_colorkey`.
# I think surfaces converted with convert_alpha are blitted faster.
# self.image.set_colorkey((0, 0, 0))
# You also don't have to create the mask repeatedly in the
# update method. Just call it once in the __init__ method.
self.mask = pygame.mask.from_surface(self.image)
You haven't defined any collide_mask in your class : something like
self.mask = pygame.mask.from_surface(image)
So if the rect of your Upper and lower pipe corresponds to the hitbix of these: simply use
bird_hits = pygame.sprite.spritecollide(bird,pipe_group,False)
or create a self.mask in your class to use
bird_hits = pygame.sprite.spritecollide(bird,pipe_group,False,pygame.sprite.collide_mask)
Related
My collide_rect function isn't working properly. It always returns True, when it's not suppose to. I have tried looking on the internet but nothing is working for me. I think the collide rect somehow did not use the actual coordinates for the two sprites. Can anyone help with this?
import pygame
import pygame.sprite
import sys
gameDisplay = pygame.display.set_mode((800,600))
pygame.display.set_caption("test_collision")
clock = pygame.time.Clock()
crashed = False
class Ball(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("ball.png")
self.rect = self.image.get_rect()
self.x = 280
self.y = 475
self.col = False
def update(self):
gameDisplay.blit(self.image, (self.x,self.y))
self.rect = self.image.get_rect()
def test_collisions(self,sprite):
self.col = pygame.sprite.collide_rect(self,sprite)
class Obstacle(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.x = 1000
self.y = 483
self.image = pygame.image.load("obstacle.png")
self.time = pygame.time.get_ticks()
self.rect = self.image.get_rect()
def change_x(self):
self.time = pygame.time.get_ticks()
self.x = -(self.time/5) + 800
def update(self):
self.rect = self.image.get_rect()
gameDisplay.blit(self.image,(self.x,self.y))
obstacle = Obstacle()
ball = Ball()
while not crashed:
for event in pygame.event.get():
if event.type == pygame.QUIT:
crashed = True
gameDisplay.fill((255,255,255))
ball.update()
obstacle.change_x()
obstacle.update()
ball.test_collisions(obstacle)
if ball.col:
print("colided")
pygame.display.flip()
clock.tick(1000)
pygame.quit()
sys.exit()
P.S This is my first post :)
pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, but it returns a rectangle that always starts at (0, 0) since a Surface object has no position.
The Surface is placed at a position on the display with the blit function.
You've to set the location of the rectangle, either by a keyword argument, e.g:
self.rect = self.image.get_rect(topleft = (self.x, self.y))
or an assignment to a virtual attribute (see pygame.Rect), e.g:
self.rect = self.image.get_rect()
self.rect.topleft = (self.x, self.y)
It is absolutely unnecessary to add some extra attributes self.x and self.y. Use the location of the rectangle instead. e.g:
class Ball(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("ball.png")
self.rect = self.image.get_rect(topleft = (280, 475))
self.col = False
def update(self):
gameDisplay.blit(self.image, self.rect)
def test_collisions(self,sprite):
self.col = pygame.sprite.collide_rect(self,sprite)
class Obstacle(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("obstacle.png")
self.time = pygame.time.get_ticks()
self.rect = self.image.get_rect(topleft = (1000, 483))
def change_x(self):
self.time = pygame.time.get_ticks()
self.rect.x = -(self.time/5) + 800
def update(self):
gameDisplay.blit(self.image, self.rect)
Further note, that you can get rid of the methods Ball.update() respectively Obstacle.update() (you can delete them), if you use a pygame.sprite.Group and call .draw(), which uses the .image and .rect properties of the contained sprites, to draw them. e.g.:
obstacle = Obstacle()
ball = Ball()
all_sprites = pygame.sprite.Group([obstacle, ball])
while not crashed:
# [...]
gameDisplay.fill((255,255,255))
all_sprites.draw(gameDisplay)
pygame.display.flip()
clock.tick(1000)
Player has ability to stand on iterable platforms, but he can't stand on the not iterable platform - Platform4. How to correct this problem?
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
pygame.sprite.Sprite.__init__(self)
# [...]
def update(self):
hits_4 = pygame.sprite.spritecollide(player, platform4, False)
if hits_4:
self.pos.y = hits_4[0].rect.top + 1
self.vel.y = 0
class Platform4(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = platform_images4
self.image.set_colorkey(WHITE1)
self.rect = self.image.get_rect()
self.rect.centerx = 300
self.rect.centery = 500
def update(self):
self.rect.move_ip(-1, 0)
if self.rect.right <= 0:
self.kill()
platform4 = Platform4()
all_sprites = pygame.sprite.Group()
all_sprites.add(player, fire, platform4)
platform4 is a pygame.sprite.Sprite object. For the collision of 2 Sprite objets you have to use pygame.sprite.collide_rect:
hits_4 = pygame.sprite.spritecollide(player, platform4, False)
hits_4 = pygame.sprite.collide_rect(player, platform4)
class NPC(pg.sprite.Sprite):
def __init__(self,x,y,image):
self.copyimg = pg.image.load(image).convert_alpha()
pg.sprite.Sprite.__init__(self)
self.image = self.copyimg.copy()
#self.copyimg.fill(RED)
self.rect = self.image.get_rect()
self.radius = int(self.rect.width / 3)
#pg.draw.circle(self.copyimg,RED,self.rect.center,self.radius)
self.rect.center = (x,y)
self.pos = vec(x,y)
self.vel = vec(0,0)
self.acc = vec(0,0)
self.speed = 0.3
self.friction = -0.02
self.rot = 0
self.chasex = True
self.chasey = True
self.directing = 1
self.times = pg.time.get_ticks()
self.mainmenu = True
def rotate(self):
self.rot = self.rot % 360
newimage = pg.transform.rotate(self.copyimg,int(self.rot%360))
#pg.draw.rect(screen,WHITE,self.rect,5)
#old_center = self.rect.center
self.image = newimage
self.rect = self.image.get_rect()
#self.rect.center = old_center
def shoot(self):
bullet = BulletPlayer(self.pos.x,self.pos.y,self.rot,lasers['laserblue'][random.randint(0,len(lasers['laserblue'])-1)])
bulletgroupenemy.add(bullet)
pass
class Bullet(pg.sprite.Sprite):
def __init__(self,x,y,rotation,image):
pg.sprite.Sprite.__init__(self)
self.cop = pg.image.load(image).convert_alpha()
self.image = self.cop.copy()
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.shootspeed = 10
self.rotation = rotation
def update(self):
self.rotation = self.rotation % 360
newimage = pg.transform.rotate(self.cop,int(self.rotation))
oldcenter = self.rect.center
self.image = newimage
self.rect = self.image.get_rect()
self.rect.center = oldcenter
keys = pg.key.get_pressed()
if self.rect.x < 0 or self.rect.x >WIDTH or self.rect.y > HEIGHT or self.rect.y < 0:
self.kill()
I have a NPC class and bullet class like that and ofcourse there is mainplayer that we can control. and as you can see there is shoot method with in the NPC class. this is calling automatically
by npc it self how ever when i shoot npc with a bullet and call spritecollide function
hitsenemy = pg.sprite.spritecollide(playerlist[i],bulletgroup,True)
if hitsenemy:
playerlist[i].kill()
the npc get kills thats correct but somehow it is keeping to shoot. the shoot function still works how this can be !. i just killed by using this hitsenemy . and also i use this for loop to add spride group. How can i prevent that i dont want it to keep shooting
playerlist = [Player(300,200,'playerShip2_blue.png')]
for players in playerlist:
allsprites.add(players)
playergroup.add(players)
i have also this allsprites group allsprites = pg.sprite.Group()
this method belongs the player class which i didnt share because but this is how i shoot with player class.
def shoot(self):
bullet = BulletPlayer(self.rect.centerx,self.rect.centery,self.rot,lasers['lasergreen'])
bulletgroup.add(bullet)
allsprites.add(bullet)
playerlist is a list but not a pygame.sprite.Group. pygame.sprite.Sprite.kill
remove the Sprite from all Groups
Therefor when you call kill, the Sprite is removed from all Groups but it is still in the playerlist.
You have to remove the Sprite from the list. See How to remove items from a list while iterating?
hitsenemy = pg.sprite.spritecollide(playerlist[i],bulletgroup,True)
if hitsenemy:
playerlist[i].kill()
playerlist.pop(i)
Alternatively, consider using a group instead of a list. Note the Sprites in a Group can be iterated. And the list of Sprites in the Group can be obtained via pygame.sprite.Group.sprites().
My collide_rect function isn't working properly. It always returns True, when it's not suppose to. I have tried looking on the internet but nothing is working for me. I think the collide rect somehow did not use the actual coordinates for the two sprites. Can anyone help with this?
import pygame
import pygame.sprite
import sys
gameDisplay = pygame.display.set_mode((800,600))
pygame.display.set_caption("test_collision")
clock = pygame.time.Clock()
crashed = False
class Ball(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("ball.png")
self.rect = self.image.get_rect()
self.x = 280
self.y = 475
self.col = False
def update(self):
gameDisplay.blit(self.image, (self.x,self.y))
self.rect = self.image.get_rect()
def test_collisions(self,sprite):
self.col = pygame.sprite.collide_rect(self,sprite)
class Obstacle(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.x = 1000
self.y = 483
self.image = pygame.image.load("obstacle.png")
self.time = pygame.time.get_ticks()
self.rect = self.image.get_rect()
def change_x(self):
self.time = pygame.time.get_ticks()
self.x = -(self.time/5) + 800
def update(self):
self.rect = self.image.get_rect()
gameDisplay.blit(self.image,(self.x,self.y))
obstacle = Obstacle()
ball = Ball()
while not crashed:
for event in pygame.event.get():
if event.type == pygame.QUIT:
crashed = True
gameDisplay.fill((255,255,255))
ball.update()
obstacle.change_x()
obstacle.update()
ball.test_collisions(obstacle)
if ball.col:
print("colided")
pygame.display.flip()
clock.tick(1000)
pygame.quit()
sys.exit()
P.S This is my first post :)
pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, but it returns a rectangle that always starts at (0, 0) since a Surface object has no position.
The Surface is placed at a position on the display with the blit function.
You've to set the location of the rectangle, either by a keyword argument, e.g:
self.rect = self.image.get_rect(topleft = (self.x, self.y))
or an assignment to a virtual attribute (see pygame.Rect), e.g:
self.rect = self.image.get_rect()
self.rect.topleft = (self.x, self.y)
It is absolutely unnecessary to add some extra attributes self.x and self.y. Use the location of the rectangle instead. e.g:
class Ball(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("ball.png")
self.rect = self.image.get_rect(topleft = (280, 475))
self.col = False
def update(self):
gameDisplay.blit(self.image, self.rect)
def test_collisions(self,sprite):
self.col = pygame.sprite.collide_rect(self,sprite)
class Obstacle(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("obstacle.png")
self.time = pygame.time.get_ticks()
self.rect = self.image.get_rect(topleft = (1000, 483))
def change_x(self):
self.time = pygame.time.get_ticks()
self.rect.x = -(self.time/5) + 800
def update(self):
gameDisplay.blit(self.image, self.rect)
Further note, that you can get rid of the methods Ball.update() respectively Obstacle.update() (you can delete them), if you use a pygame.sprite.Group and call .draw(), which uses the .image and .rect properties of the contained sprites, to draw them. e.g.:
obstacle = Obstacle()
ball = Ball()
all_sprites = pygame.sprite.Group([obstacle, ball])
while not crashed:
# [...]
gameDisplay.fill((255,255,255))
all_sprites.draw(gameDisplay)
pygame.display.flip()
clock.tick(1000)
I'm learning pygame and i am encountering a problem :
When i'm trying to change the image of the sprite by changing the content of the variable self.image and then self.rect, it doesn't show/acutalize this new image. This is the code, hoping to make myself understood.
all_sprites_list = pygame.sprite.Group()
luffy_sprites_ls = pygame.sprite.Group()
class luffy(pygame.sprite.Sprite):
"""docstring pour le personnage"""
def __init__(self):
self.lsLuffy = []
self.lsLuffySauter = []
super().__init__()
self.imageAll = SpriteSheet("images/attaquesLuffy.png")
#loading some img to put them in a lsLuffySauter
self.image = self.imageAll.get_image(35, 74, 20, 95)
self.lsLuffySauter.append(self.image)
self.image2 = self.imageAll.get_image(200, 300, 300,300)
self.lsLuffySauter.append(self.image2)
self.rect = self.image.get_rect()
self.rect.x = 500
self.rect.y = 500
all_sprites_list.add(self)
self.positionX = 500
self.positionY = 500
def sauter(self):
""" Called when user hits 'jump' button. """
self.current_image = self.lsLuffySauter[0]
self.positionY -= 10
self.rect = self.current_image.get_rect()
self.rect.x = self.positionX
self.rect.y = self.positionY
luffy_sprites_ls.empty()
luffy_sprites_ls.add(self)
#all_sprites_list.update()
#Code to draw in the screen
screen.fill(WHITE)
all_sprites_list.draw((screen))
luffy_sprites_ls.draw((screen))
pygame.display.flip()
clock.tick(100)
When pygame.sprite.Group.draw() is called, as in luffy_sprites_ls.draw((screen)), every sprite in the group has it's sprite.image rendered to the screen at sprite.rect.
Your sauter() function is changing the luffy.rect, but it is not changing the luffy.image (it is changing luffy.current_image).
Probably you want something like:
def sauter(self):
""" Called when user hits 'jump' button. """
self.image = self.lsLuffySauter[0]
self.positionY -= 10
self.rect = self.image.get_rect()
self.rect.x = self.positionX
self.rect.y = self.positionY