Pygame: update in the while loop not succesful - python

A helicopter is shot at. If he is hit, he falls. At the same time, a parachute releases from the helicopter. If the parachute is shot at and also hit by a bullet, the image of the parachute should be replaced with another image.
The jump with the parachute is done by the function absprung. The parachute is created with the Fallschirm class. The shelling is queried via collide.
It doesn't work, the picture is not changed.
def absprung(self): #Auslöser Fallschirm
fallschirm = Fallschirm(self.rect.centerx, self.rect.top)
alle_sprites.add(fallschirm)
fallschirme.add(fallschirm)
class Fallschirm(pygame.sprite.Sprite):
def __init__(self,x,y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("Bilder/fallschirm.png").convert_alpha()
self.image = pygame.transform.scale(self.image,(100,150))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.beschuss = False
def update(self):
if self.beschuss == True:
self.image = pygame.image.load("Bilder/fadenkreuz.png").convert_alpha()
self.image = pygame.transform.scale(self.image,(100,150))
self.rect.y +=2
if self.rect.y > hoehe:
self.kill()
hits = pygame.sprite.groupcollide(fallschirme,bullets,False,True)
for hit in hits:
fallschirme.beschuss = True

beschuss is an attribute of a single Falschirm object, but not an attribute of fallschirme. pygame.sprite.groupcollide returns a dictionary of objects that were hit. You must set the attribute to these objects.
hits = pygame.sprite.groupcollide(fallschirme, bullets,False,True)
for fallschirm in hits:
fallschirm.beschuss = True

Related

How to count the number of collisions between group objects in python pygame

If someone wants to write a conditional code statement in a pygame based game that would require detection of the number of collisions between two group objects specifically, which function would they use?? The pygame library does not have a prewritten function apparently for counting group collisions only, and whenever I try writing one, pygame crashes but no errors are found/returned by the IDLE.
Say I want two collisions to occur and count them before I execute the following block of code:
def _update_bullets(self):
self.bullets.update()
for bullet in self.bullets.copy():
if bullet.rect.bottom <= 0:
self.bullets.remove(bullet)
final =pygame.sprite.groupcollide(self.bullets, self.scarfys, False, True)
and whenever I write the following if statements below
final = pygame.sprite.groupcollide(self.bullets, self.scarfys, False, True):
for bullet in self.bullets.copy():
hitCount = 0
while hitCount < 1:
if pygame.sprite.groupcollide(self.bullets, self.scarfys, True, False):
hitCount += 1
final = pygame.sprite.groupcollide(self.bullets, self.scarfys, True, True)
I run the program with no errors appearing in the IDLE, however when I press the key to shoot a bullet (which I've tested before to ensure that it works properly without the collision counting condition ) I get a "program is not responsive error from windows" but still no errors in the IDLE.
For reference here are the classes/ groups in question:
import pygame
from pygame.sprite import Sprite
class Scarfy(Sprite):
def __init__(self,kns):
super().__init__()
self.screen = kns.screen
self.settings = kns.settings
self.image = pygame.image.load('cutescarfy.bmp')
self.shot = pygame.image.load('onehit.bmp')
self.seq = pygame.image.load('transformation.bmp')
self.real = pygame.image.load('realscarfy.bmp')
self.rect = self.image.get_rect()
self.rect.x = self.rect.width
self.rect.y = self.rect.height
self.x = float(self.rect.x)
self.y = float(self.rect.y)
self.scarfys = kns.scarfys
...
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
def __init__(self,kns):
super().__init__()
self.screen = kns.screen
self.color = (255,229,184)
self.bullets = pygame.sprite.Group()
self.rect = pygame.Rect(0,0,kns.settings.bullet_width,kns.settings.bullet_height)
self.rect.midtop= kns.kirby.rect.midtop
self.y = float(self.rect.y)
self.x = float(kns.kirby.rect.x)
self.bullet_speed = kns.settings.bullet_speed
self.scarfys = kns.scarfys
def update(self):
self.y -= self.bullet_speed
self.rect.y = self.y
def drawbullet(self):
pygame.draw.rect(self.screen,self.color,self.rect)
I also created group objects based off each class:
def _create_rows(self):
scarfy = Scarfy(self)
scarfy_width = scarfy.rect.width
avaliableSpaceHor = self.settings.screen_width - (2*scarfy_width)
rowCapacity = (avaliableSpaceHor//(2*scarfy_width))-1
scarfy_height = scarfy.rect.height
avaliableSpaceVer = self.settings.screen_height - (8*scarfy_height)
rowCount = avaliableSpaceVer//(2*scarfy_height)
for rowNum in range(rowCount):
for scarfyNum in range(rowCapacity):
self._create_scarfy(scarfyNum,rowNum)
def _create_scarfy(self,scarfyNum,rowNum):
scarfy = Scarfy(self)
scarfy_width = scarfy.rect.width
scarfy_height = scarfy.rect.height
scarfy.x = scarfy_width + 2 * scarfy_width * scarfyNum
scarfy.rect.x = scarfy.x
scarfy.rect.y = scarfy.rect.height + 2 * scarfy.rect.height * rowNum
self.scarfys.add(scarfy)
def _fire_bullet(self):
bulletCount = len(self.bullets)
if bulletCount <= self.settings.bullet_limit:
new_bullet = Bullet(self)
self.bullets.add(new_bullet)
I want two collisions to occur before I remove the individual collided rectangles/elements off screen
I think, what you want to do is have an extra attribute kills in your Bullet class, initate it with 0 and manage it when iterating over the results of groupcollide. Something along the lines:
for bullet, scarfies in pygame.sprite.groupcollide(self.bullets, self.scarfys, False, True).items():
if scarfies:
# The bullet hit a scarfy
for scarfy in scarfies: # In case the bullet hit more than one at a time
bullet.kills += 1
if bullets.kills >= 2:
# Maybe remove bullet from bullets...
bullet.kill()
break
Or, having seen your comment, if you want your scarfy to require two hits before it's killed, do the same with an attribute hits on Scarfy and reversed logic in the code:
for scarfy, bullets in pygame.sprite.groupcollide(self.scarfys, self.bullets, False, True).items():
if bullets:
# The scarfy was hit by bullets
for bullet in bullets: # In case the scarfy was hit by more than one bullet at a time
scarfy.hits += 1
if scarfy.hits >= 2:
# Maybe remove scarfy from your list of scarfies
scarfy.kill()
break

Animation doubles duration every time it appears in pygame

I'm fairly new to coding and I am working on a little game with pygame. I have a Sprite for an explosion animation, the first times it appears it works great for the whole game, but once you are playing a second or third game the explosions lasts more every time even though I empty the sprite group every time the game restarts, I think the problem might be that the explosion update function doesn't works well for more than one game, but I don't really know how to fix it. Thanks in advance for the help!
class Explosion(pygame.sprite.Sprite):
expl_img = []
def __init__(self, center):
pygame.sprite.Sprite.__init__(self)
self.image = self.cargaImagenes()
self.rect = self.image.get_rect()
self.rect.center = center
self.frame = 0
self.last_update = pygame.time.get_ticks()
self.frame_rate = 50
def update(self):
now = pygame.time.get_ticks()
if now -self.last_update > self.frame_rate:
self.last_update = now
self.frame +=1
if self.frame == len(self.expl_img):
self.kill()
else:
center = self.rect.center
self.image = self.expl_img[self.frame]
self.rect = self.image.get_rect()
self.rect.center = center
def cargaImagenes(self):
for i in range(9):
filename = 'regularExplosion0{}.png'.format(i)
img = pygame.image.load(path.join(img_dir, filename)).convert()
img.set_colorkey(BLACK)
img_def = pygame.transform.scale(img, (75, 75))
self.expl_img.append(img_def)
return self.expl_img[0]
Here is where the explosion appears:
if self.player.estado == self.player.Estado.volando:
anticolision = False
colisiones = pygame.sprite.spritecollide(self.player, self.asteroides, True, pygame.sprite.collide_circle)
for colision in colisiones:
start_ticks = pygame.time.get_ticks()
if colision and not anticolision:
anticolision = True
print("anticolision true")
expl = Explosion(colision.rect.center)
self.explosionSound.play()
self.all_sprites.add(expl)
self.vidas -= 1
segundos = (pygame.time.get_ticks()-start_ticks)/1000
if segundos > 3:
anticolision = False
print("anticolision false")
elif colision and anticolision:
pass
if self.vidas == 0:
self.gameOver()
running= False
self.cargaImagenes() is called every time when you create a new instance of Explosion. Therefore the number of elements in the class variable expl_img grows continuously. Only add new items to the list when the list is empty:
class Explosion(pygame.sprite.Sprite):
expl_img = []
# [...]
def cargaImagenes(self):
if not expl_img: # <---
for i in range(9):
filename = 'regularExplosion0{}.png'.format(i)
img = pygame.image.load(path.join(img_dir, filename)).convert()
img.set_colorkey(BLACK)
img_def = pygame.transform.scale(img, (75, 75))
self.expl_img.append(img_def)
return self.expl_img[0]

Is there a way to change my_sprite or my_group to change the animation displayed?

I'm making animated sprites in pygame, and would like help finding a way to flip from one to the other? The current code looks something like this:
class normal(pygame.sprite.Sprite):
def __init__(self):
#etc, list of images to create the animation
class tall(pygame.sprite.Sprite):
def __init__(self):
#rinse and repeat with a different set of images
I already have an idea for how to trigger the change via keystroke. But I'm not sure which variable to change, and to what. When I try to change with the following code, nothing happens
fps = 25
pygame.init()
my_sprite = normal()
my_group = pygame.sprite.Group(my_sprite)
#etc until we get to the part where it changes
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
if my_sprite == normal():
my_sprite = tall()
fps = 30
else:
my_sprite = normal()
fps = 25
I'm not sure exactly what isn't working in my code as it doesn't come back with an error. Can someone point me in the right direction?
It's not working because when the code calls normal() it's creating a new instance of an object. So the call:
if my_sprite == normal():
Is saying "is my existing sprite object == this new sprite object", which is never true. You can use the python function to type() of the object to do the same thing, or add your own type-function as I have presented in the code below.
I would track the state of the sprite inside the class, and use some functions grow() and shrink() to change the size automatically.
class GrowingSprite( pygame.sprite.Sprite, x, y ):
def __init__( self ):
#etc, list of images to create the animation
self.image_short = ... # load "short" image
self.image_tall = ... # load "tall" image
# Set the initial state
self.image = self.image_short # start short
self.rect = self.image.get_rect()
self.rect.centerx = x
self.rect.centery = y
self.state = 'short'
def getState( self ):
return self.state
def grow( self ):
self.state = 'tall'
self.image = self.image_tall
current_x = self.rect.centerx # preserve existing location
current_y = self.rect.centery
self.rect = self.image.get_rect()
self.rect.center = ( current_x, current_y )
def shrink( self ):
self.state = 'short'
self.image = self.image_short
current_x = self.rect.centerx # preserve existing location
current_y = self.rect.centery
self.rect = self.image.get_rect()
self.rect.center = ( current_x, current_y )
I found a workaround that is a bit redundant, but it makes it possible to expand it in case there are more groups
It requires making two groups that can be pulled back and fourth, and changing the foo.draw(screen) to the new group. This is how it looks
nml_sprite = normal()
tal_sprite = tall()
tg = 1 #this is nothing more than a switch to trigger the change
tal_group = pygame.sprite.Group(tal_sprite)
nml_group = pygame.sprite.Group(nml_sprite)
cursprite = nml_group #this variable determines which sprite set is loaded
...
...
if event.key == pygame.K_RETURN:
if tg == 1:
curspt = tal_group
tg = 2
else:
curspt = nml_group
tg = 2
...
...
nml_group.update()
tal_group.update()
curspt.draw(screen)
...

Problems with Sprites Appearing and Collision with Rotated Objects; Pygame object is not iterable

I'm currently trying to make pixel perfect collisions between my pong ball and my player's paddle using the mask and collide_rect functions. I made my own checkCollision function in the pong class which would check for pixel perfect collision. However, right now, I can't even get the Sprites to work or appear on the screen because my "Pong object is not iterable.
Here is my pong class with the important features: (I will post additional code if needed)
class Pong(pygame.sprite.Sprite):
def __init__(self, screensize):
pygame.sprite.Sprite.__init__(self)
self.screensize = screensize
self.centerx = screensize[0] // 2
self.centery = screensize[1] // 2
self.radius = 25
self.rect = pygame.Rect(self.centerx-self.radius,
self.centery-self.radius,
self.radius*2, self.radius*2)
self.pokeimage = pygame.image.load("pokeball.png")
self.pokeimage = pygame.transform.scale(self.pokeimage, (self.radius, self.radius))
#Create the mask
self.mask = pygame.mask.from_surface(self.pokeimage)
def checkCollision(self, player_paddle, ai_paddle):
col = pygame.sprite.collide_rect(self, player_paddle)
return col
def collisionFormula(self, player_paddle, ai_paddle):
if self.checkCollision(self, player_paddle):
def collision_checks(self, player_paddle, ai_paddle):
#if the ball hits the top or bottom of the screen, change the y direction
if self.rect.top <= 0 or self.rect.bottom >= self.screensize[1] - 1:
self.direction[1] *= -1
#if the pong hits the paddles, change how the pong ball moves
if self.rect.colliderect(player_paddle.rect) or self.rect.colliderect(ai_paddle.rect):
self.collisionFormula(player_paddle, ai_paddle)
def update(self, player_paddle, ai_paddle):
self.update_ball_position()
self.reset_ball()
self.collision_checks(player_paddle, ai_paddle)
In my PlayerPaddle class, I do the same mask initialization.
class PlayerPaddle(pygame.sprite.Sprite):
def __init__(self, screensize):
pygame.sprite.Sprite.__init__(self)
self.screensize = screensize
self.centerx = 50
self.centery = screensize[1]//2
self.height = 100
self.width = 20
self.imageMaster = pygame.image.load("naruto.png").convert_alpha()
self.imageMaster = pygame.transform.scale(self.imageMaster, (self.width, self.height))
self.image = self.imageMaster
#mask
self.mask = pygame.mask.from_surface(self.image)
def turnLeft(self):
self.dir += 45
if self.dir > 360:
self.dir = 45
def turnRight(self):
self.dir -= 45
if self.dir < 0:
self.dir = 315
def update(self):
#Rotate functions
oldCenter = self.rect.center
self.image = pygame.transform.rotate(self.imageMaster, self.dir)
self.rect = self.image.get_rect()
self.rect.center = oldCenter
And here is my main function:
def main():
pygame.init()
screensize = (640,700)
screen = pygame.display.set_mode(screensize)
background = pygame.Surface(screen.get_size())
background.fill((0, 255, 0))
clock = pygame.time.Clock()
pong = Pong(screensize)
player_paddle = PlayerPaddle(screensize)
ai_paddle = AIPaddle(screensize)
paddleSprite = pygame.sprite.Group(player_paddle)
pongSprite = pygame.sprite.Group(pong)
while running:
running = True
#object updating phase
ai_paddle.update(pong, player_paddle)
player_paddle.update()
pong.update(player_paddle, ai_paddle)
if pygame.sprite.spritecollide(player_paddle, pong, False, pygame.sprite.collide_mask):
print("Collided")
#rendering phase
ai_paddle.render(screen)
player_paddle.render(screen)
pong.render(screen)
paddleSprite.clear(screen, background)
paddleSprite.update()
paddleSprite.draw(screen)
pongSprite.clear(screen,background)
pongSprite.update()
pongSprite.draw(screen)
pygame.display.flip()
pygame.quit()
main()
I made two "group" objects (the pong and the player_paddle) but I'm not sure why I'm even failing to run the program. Additionally, I know the collision will not work because the pong ball will hit the rectangle of the original image, but not the rotated image, but I'm not sure why that will happen if I use the built in sprite function. Thanks.
Read documentation for spritecollide
Find sprites in a group that intersect another sprite.
spritecollide(sprite, group, dokill, collided = None) -> Sprite_list
Second argument has to be group (pygame.sprite.Group), not single Sprite.
It can be group event with one sprite. But you use pong which is single sprite, not group.
See documentation for collide_mask
Collision detection between two sprites, using masks.
collide_mask(SpriteLeft, SpriteRight) -> point
It checks collision between two sprites using mask.
EDIT: in your code you have problem with
spritecollide(player_paddle, pong,...)
because pong is single Sprite, not Group.
With pong you should use collide_mask
collide_mask(player_paddle, pong)
You can use spritecollidewith pongSprite which is Group
spritecollide(player_paddle, pongSprite,...)`
BTW: you could use better names ie. pong_group instead of pongSprite.
And eventually pong_sprite instead of pong (but pong is OK, too).

Making sprites inside/outside of a class

I've got all my code working on its own. I need to start linking it all to buttons though.
QUESTION: trying to setup multiple buttons as sprites for collision purposes. Don't know how to do it outside of a class.
I have buttons working in seperate classes, but cannot get them to work in the same class for the obvious reason of, the self.image of the second one is overwriting the first one.
class Icons(pygame.sprite.Sprite):
def __init__(self, *args):
pygame.sprite.Sprite.__init__(self, *args)
self.image = pygame.image.load("images/airbrushIC.gif").convert()
self.rect = self.image.get_rect()
ic1 = self.image
self.rect.x = 50
self.rect.y = 490
self.image = pygame.image.load("images/fillIC.gif").convert()
self.rect = self.image.get_rect()
ic2 = self.image
self.rect.x = 10
self.rect.y = 540
def update(self):
pygame.mouse.get_pos()
pygame.mouse.get_pressed()
This code doesn't have to be a class. But I do not know how to make the images a sprite without being inside of a class. Any help would be appriciated thanks!
Instead of Icons you should have a generic Icon class.
Then you can create an instance of Icon for each button.
class Icon(pygame.sprite.Sprite):
def __init__(self, image_name, pos, cb, cb_data, *args):
pygame.sprite.Sprite.__init__(self, *args)
self.image = pygame.image.load("images/" + image_name).convert()
self.rect = self.image.get_rect()
self.rect.x = pos[0]
self.rect.y = pos[1]
this.cb = cb # function to call when button is pressed
this.cb_data = cb_data # data to pass to the function
def pressed():
this.cb(cb_data)
Then in you main function you create the buttons:
ic1 = Icon("airbrushIC.gif", (50, 490), button_pressed, "airbrushIC")
ic2 = Icon("fillIC.gif", (10, 540), button_pressed, "fillIC")
buttons = [ic1, ic2]
def button_pressed(data):
print "Button pressed:" + str(data)
Last, for every mouse down event you look for a button collition:
for b in buttons:
if b.rect.collidepoint(event.pos):
b.pressed()

Categories