How can I check collisions of 2 sprites groups? [duplicate] - python

This question already has answers here:
pygame.sprite.groupcollide() does not work when trying to implement collision in pygame [duplicate]
(1 answer)
Permanently delete sprite from memory Pygame
(1 answer)
Pygame - getting a sprite to cycle through images in a class, and assign a hitbox
(1 answer)
Closed 8 days ago.
How do I check the collisions between the bullet_grou and the enemy_group and if they collide I want them do disappear. I have looked at few simular questions but none of them helped me. Thenks for all the help.
This game is supposed to be a simular game to the asteroids. #I dont know what more to specify here bt it doesnt let me post this question if I dont write some more over her sorry
import pygame , sys
from random import randint
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((50,50))
self.image.fill((255,255,255))
self.rect = self.image.get_rect(center = (screen_width/2,screen_height/2))
def update(self):
self.rect.center = pygame.mouse.get_pos()
def create_bullet(self):
return Bullet(pygame.mouse.get_pos()[0],pygame.mouse.get_pos()[1])
def create_enemy(self):
return Enemy(randint(50,screen_width-50),-200)
class Bullet(pygame.sprite.Sprite):
def __init__(self,pos_x,pos_y):
super().__init__()
self.image = pygame.Surface((5,25))
self.image.fill((255,0,0))
self.rect = self.image.get_rect(center = (pos_x,pos_y))
def update(self):
self.rect.y -= 5
if self.rect.y <= -200:
self.kill()
class Enemy(pygame.sprite.Sprite):
def __init__(self,pos_x,pos_y):
super().__init__()
self.image = pygame.Surface((50,50))
self.image.fill((255,140,0))
self.rect = self.image.get_rect(center = (pos_x,pos_y))
## def __init__(self):
## super().__init__()
## self.image = pygame.Surface((50,50))
## self.image.fill((255,128,128))
## self.rect = self.image.get_rect(center = (randint(0,screen_width),-200))
##
def update(self):
self.rect.y += 5
if self.rect.y >= screen_height + 200:
self.kill()
pygame.init()
clock = pygame.time.Clock()
screen_width, screen_height = 800, 800
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.mouse.set_visible(False)
obstacle_timer = pygame.USEREVENT + 1
pygame.time.set_timer(obstacle_timer,150)
player = Player()
player_group = pygame.sprite.Group()
player_group.add(player)
bullet_group = pygame.sprite.Group()
enemy_group = pygame.sprite.Group()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
# enemy_group.add(player.create_enemy())
bullet_group.add(player.create_bullet())
if event.type == obstacle_timer:
if randint(0,2):
enemy_group.add(player.create_enemy())
## if Enemy.rect.coliderect(Bullet.rect):
## print('colision')
screen.fill((30,30,30))
bullet_group.draw(screen)
bullet_group.update()
# collide_enemy_bullet = pygame.sprite.spritecollide(bullet_group, Enemy , False) #HERE IS THE PROBLEM
collide_player_enemy = pygame.sprite.spritecollide(player, enemy_group, False)
for s in collide_player_enemy:
pygame.draw.rect(screen, (255, 255, 255), s.rect, 5, 1)
print('collide_player_enemy')
## for x in collide_enemy_bullet:
## pygame.draw.rect(screen, (255, 255, 255), x.rect, 5, 1)#HERE ALSO
## print('collide_enemy_bullet')
player_group.draw(screen)
player_group.update()
enemy_group.draw(screen)
enemy_group.update()
pygame.display.flip()
clock.tick(240)

pygame.sprite.groupcollide() should do the trick. You pass in the first group and the second group, and whether you want each group to disappear on collision.
# True means you want them to disappear on collision
pygame.sprite.groupcollide(bullet_group, enemy_group, True, True)
Pygame docs: https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.groupcollide

Related

How to draw an object to the screen when receiving input from within a class [duplicate]

This question already has answers here:
How can i shoot a bullet with space bar?
(1 answer)
How do I stop more than 1 bullet firing at once?
(1 answer)
Closed 8 months ago.
So I'm working on a simple game in pygame and I have a file named players.py that contains a Player class and a PlayerAttack class because I want to have the projectiles be their own object. Here is some code from my players.py
import pygame
import gametools
class PlayerAttack(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.bullet_img = gametools.smoothscale2x(pygame.image.load('sprites/player/player-bullet.png')).convert_alpha()
self.image = self.bullet_img
self.rect = self.image.get_rect(center = (400, 300))
class Player(pygame.sprite.Sprite):
'''The Main player class'''
def __init__(self):
super().__init__()
self.player_image_center = gametools.smoothscale2x(pygame.image.load('sprites/player/player.png')).convert_alpha()
self.player_image_left = gametools.smoothscale2x(pygame.image.load('sprites/player/player-left.png')).convert_alpha()
self.player_image_right = gametools.smoothscale2x(pygame.image.load('sprites/player/player-right.png')).convert_alpha()
self.player_image = [self.player_image_left, self.player_image_center, self.player_image_right]
self.player_index = 1
self.image = self.player_image[self.player_index]
self.rect = self.image.get_rect(center = (400, 500))
self.vector = pygame.math.Vector2()
self.velocity = 5
self.bullet = pygame.sprite.Group()
self.bullet.add(PlayerAttack())
def player_input(self):
keys = pygame.key.get_pressed()
# player movement
if keys[pygame.K_UP]:
self.vector.y = -1
elif keys[pygame.K_DOWN]:
self.vector.y = 1
else:
self.vector.y = 0
if keys[pygame.K_LEFT]:
self.vector.x = -1
elif keys[pygame.K_RIGHT]:
self.vector.x = 1
else:
self.vector.x = 0
# attacks
if keys[pygame.K_x]:
return self.bullet.draw(screen)
def move(self, velocity):
if self.vector.magnitude() != 0:
self.vector = self.vector.normalize()
self.rect.center += self.vector * velocity
def update(self):
self.player_input()
self.animation_state()
self.move(self.velocity)
If you look in the __init__() method in the Player class you will see that I define self.bullet as an object belonging to the PlayerAttack class. Now if you look inside the player_input() method in the Player class you see that it draws it to the screen surface defined in main.py
import pygame
import gametools
import players
from sys import exit
# initilaization
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption('Bullet hell')
clock = pygame.time.Clock()
# Player
player = pygame.sprite.GroupSingle()
player.add(players.Player())
x = pygame.sprite.GroupSingle()
# Background
back_surf = pygame.image.load('sprites/backgrounds/background.png').convert()
# main game loop
while True:
# event loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
# display background
screen.blit(back_surf, (0, 0))
player.draw(screen)
player.update()
pygame.display.update()
clock.tick(60)
This produces an error because screen is defined in main.py and not in the Player class. So my question is how can I draw an object to the screen when receiving input from within the class. Or is there a better way to go about this?

Pygame Collision Bug

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.

Can't display more than 1 sprites - PyGame

Problem
My problem is that I have a game that generates 8 obstacles at the start of the game. The issue is that when I loop through the obstacles list, and update the sprites group, it only generates 1 sprite.
What I Want To Happen
When the game loads, I want 8 squares to fly down from the top of the window at random speeds, and starting at random positions.
What Is Currently Happening
Currently, when the game loads, only one square is falling from the screen.
PYthon Code
OBSTICLES_AMOUNT = 8
class Obstacle(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((30, 30))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.x = random.randrange(0, WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.velY = 6
def animate(self):
self.rect.y += self.velY
class Game(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
pygame.init()
pygame.mixer.init()
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption(TITLE)
self.running = True
self.clock = pygame.time.Clock()
self.obstaclesList = []
self.allSprites = pygame.sprite.Group()
self.obstacles = pygame.sprite.Group()
def new(self):
# create a new game
# add obstacles to list
for i in range(OBSTICLES_AMOUNT):
self.obstacle = Obstacle()
self.obstaclesList.append(self.obstacle)
# make new sprite using list
for i in self.obstaclesList:
self.allSprites.add(i)
self.obstacles.add(i)
self.gameLoop()
def gameLoop(self):
# main game loop
while self.running:
self.draw()
def draw(self):
self.screen.fill(WHITE)
self.allSprites.draw(self.screen)
for sprites in self.obstaclesList:
sprites.update()
self.allSprites.update()
Your code is fixed by
adding missing imports
adding missing constants
renaming animate to update in the Obstacle class
calling pygame.display.update after drawing
using a Clock to limit the framerate
adding event handling
adding code to create a Game instance
Some more improvements:
no need for obstaclesList if you already have obstacles
you can pass Groups directly to Sprite's __init__ function
remove a Sprite when it's no longer on the screen
Here's the code:
import pygame
import random
OBSTICLES_AMOUNT = 8
WIDTH, HEIGHT = 800,600
TITLE='some game of falling stuff'
BLUE = pygame.color.THECOLORS['blue']
WHITE = pygame.color.THECOLORS['white']
class Obstacle(pygame.sprite.Sprite):
def __init__(self, *args):
pygame.sprite.Sprite.__init__(self, *args)
self.image = pygame.Surface((30, 30))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.x = random.randrange(0, WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.velY = 6
def update(self):
self.rect.y += self.velY
if self.rect.y > HEIGHT:
self.kill()
class Game(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
pygame.init()
pygame.mixer.init()
self.clock = pygame.time.Clock()
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption(TITLE)
self.running = True
self.clock = pygame.time.Clock()
self.allSprites = pygame.sprite.Group()
self.obstacles = pygame.sprite.Group()
def new(self):
# create a new game
# add obstacles to list
for i in range(OBSTICLES_AMOUNT):
Obstacle(self.allSprites, self.obstacles)
while self.running:
self.allSprites.update()
for e in pygame.event.get():
if e.type == pygame.QUIT:
self.running = False
self.draw()
self.clock.tick(60)
def draw(self):
self.screen.fill(WHITE)
self.allSprites.draw(self.screen)
for sprites in self.obstacles:
sprites.update()
pygame.display.update()
if __name__ == '__main__':
Game().new()

Pygame - sprite collision with sprite group

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.

TypeError: Argument must be rect style object in pygame

For a while now I've been trying to teach myself how to use sprites in pygame and right now I am stuck on collision detection.
The specific place I am having trouble with in my code is the commented section labeled "error here" and that's the code that keeps giving me the "TypeError: Argument must be rect style object" error and the goal of that specific code is to detect collision.
The goal of this code is to print a message in the shell whenever the player block enters the non-player block and as I said earlier I have been having trouble getting that to happen.
from pygame.locals import *
import pygame
pygame.init()
SIZE = WIDTH, HEIGHT = 500, 700
screen = pygame.display.set_mode(SIZE)
plr_g = pygame.sprite.Group()
h_box = pygame.sprite.Group()
BLUE = (0, 206, 209)
GREEN = (0, 255, 0)
class Player(pygame.sprite.Sprite):
def __init__(self, width, height):
pygame.sprite.Sprite.__init__(self, plr_g)
self.image = pygame.Surface([width, height])
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
#self.rect = (400, 200)
def x_pos(self):
self.rect.x
def y_pos(self):
self.rect.y
def move_l(self, pixels):
self.rect.x -= pixels
def move_r(self, pixels):
self.rect.x += pixels
def move_u(self, pixels):
self.rect.y -= pixels
def move_d(self, pixels):
self.rect.y += pixels
class Hitbox(pygame.sprite.Sprite):
def __init__(self, bx, by):
pygame.sprite.Sprite.__init__(self, h_box)
self.image = pygame.Surface([100, 100])
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect = (bx, by)
hitbox = Hitbox(300, 300)
hitbox = Hitbox(100, 500)
player = Player(50, 50)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type==pygame.KEYDOWN:
if event.key==pygame.K_LEFT:
player.move_l(10)
if event.key==pygame.K_RIGHT:
player.move_r(10)
if event.key==pygame.K_UP:
player.move_u(10)
if event.key==pygame.K_DOWN:
player.move_d(10)
#error here
if plr_g.colliderect(h_box):
print("collide")
#----------------
plr_g.update()
h_box.update()
screen.fill((50, 50, 50))
h_box.draw(screen)
plr_g.draw(screen)
pygame.display.flip()
h_box is a sprite group, not a sprite and definitely not a rect. The collide_rect function of sprites must be called on individual sprites. A possible solution is something like the following, iterating over all the sprites in h_box:
if any([plr_g.colliderect(sp) for sp in h_box]):
print("collide")
block_hit_list = plr_g.colliderect(h_box)
for block in block_hit_list:
print ("collide")
I basically loop through the list to see if there are any collisions between the sprites in plr_g and the sprites in hitbox.

Categories

Resources