Identify individual sprite from group in Pygame - python

Original Post:
With pygame is it possible to identify a random sprite from a Group?
I'm trying to learn Python and have been trying to enhance the Alien Invasion program. for the aliens themselves an alien with the alien class and a group is created from this with 4 rows of 8 aliens.
I'd like to periodically have a random alien fly down to the bottom of the screen. Is it possible to do this with a group or do I have to come up with some other means of creating my fleet if I want to have this functionality?
I've came across a few cases where other people seem to have been trying something similar but there isn't any information stating if they were successful or not.
Update:
I've looked into this a little further. I tried creating an alien_attack function in game_functions.py. It was as follows:
def alien_attack(aliens):
for alien in aliens:
alien.y += alien.ai_settings.alien_speed_factor
alien.rect.y = alien.y
I called it from the while loop in alien_invasion.py with gf.alien_attack(aliens). Unfortunately this caused 3 rows to vanish and one row to attack in the manner I desire except that the whole row did this instead of an individual sprite.
I also tried changing aliens = Group() to aliens = GroupSingle() in alien_attack.py. This caused the game to start with only one sprite on the screen. It attacked in the manner I desire but I'd like all the other sprites to appear also but not attack. How is this done?

You can pick a random sprite by calling random.choice(sprite_group.sprites()) (sprites() returns a list of the sprites in the group). Assign this sprite to a variable and do whatever you want with it.
Here's a minimal example in which I just draw an orange rect over the selected sprite and call its move_down method (press R to select another random sprite).
import random
import pygame as pg
class Entity(pg.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = pg.Surface((30, 30))
self.image.fill(pg.Color('dodgerblue1'))
self.rect = self.image.get_rect(center=pos)
def move_down(self):
self.rect.y += 2
def main():
pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
for _ in range(20):
pos = random.randrange(630), random.randrange(470)
all_sprites.add(Entity(pos))
# Select a random sprite from the all_sprites group.
selected_sprite = random.choice(all_sprites.sprites())
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_r:
selected_sprite = random.choice(all_sprites.sprites())
all_sprites.update()
# Use the selected sprite in the game loop.
selected_sprite.move_down()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
# Draw a rect over the selected sprite.
pg.draw.rect(screen, (255, 128, 0), selected_sprite.rect, 2)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()

Related

Pygame sprite constantly redrawn to screen and not moving

I can't see what's wrong with the below code. All I want to do is make the frog move across the screen, but it is simply redrawing many, many frogs all one pixel apart. How do I move the frog rather than just draw it again?
import pygame
from pygame.constants import *
pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
class Frog(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.transform.scale(pygame.image.load('frog.png'), (64, 64))
self.rect = self.image.get_rect()
self.dx = 1
def update(self, *args):
self.rect.x += self.dx
running = True
frog = Frog()
entities = pygame.sprite.Group()
entities.add(frog)
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
entities.update()
entities.draw(screen)
That is how you do it in Pygame, you just redraw objects every iteration to give the illusion that they're moving but you must cover up the previous drawn objects by filling your window with a solid color e.g.
screen.fill((255, 255, 255))
This should be at the start of your game loop so you have a fresh canvas for drawing your objects each iteration.
while running:
screen.fill((255,255,255))
for event in pygame.event.get():
if event.type == QUIT:
running = False
entities.update()
entities.draw(screen)
pygame.display.update()
You may have to use the pygame.display.update() function to update the whole screen rather than just your entities.

Sprite not working/not being displayed (Python/Pygame)

Please help me I am starting a game and my sprite is not showing on screen. Take a look, I am using two files, which include pygame and classes. I hope that's enough information.
Adventure.py --
import pygame, random
pygame.init()
BROWN = (205,192,176)
DEEPBROWN = (139,131,120)
CL = (156,102,31)
from LittleMan import LittleMan
playerSprite = LittleMan(CL, 200, 300)
size = (1000, 600)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Adventure")
all_sprites_list = pygame.sprite.Group()
playerSprite.rect.x = 200
playerSprite.rect.y = 300
carryOn = True
clock = pygame.time.Clock()
while carryOn:
for event in pygame.event.get():
screen.fill(BROWN)
pygame.draw.rect(screen, DEEPBROWN, [55, 250, 900, 70],0)
all_sprites_list.draw(screen)
all_sprites_list.add()
all_sprites_list.update()
pygame.display.flip()
clock.tick(60)
if event.type == pygame.QUIT:
carryOn = False
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_x: #Pressing the x Key will quit the game
carryOn=False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
LittleMan.moveLeft(5)
if keys[pygame.K_RIGHT]:
LittleMan.moveRight(5)
LitlleMan.py --
import pygame
CL = (156,102,31)
WHITE = (255,255,255)
class LittleMan (pygame.sprite.Sprite):
def __init__(self, color, width, height):
super().__init__()
self.image = pygame.Surface([50, 75])
self.image.fill(CL)
self.image.set_colorkey(WHITE)
pygame.draw.rect(self.image, CL, [0, 0, width, height])
self.rect = self.image.get_rect()
def moveRight(self, pixels):
self.rect.x += pixels
def moveLeft(self, pixels):
self.rect.x -= pixels
Anyone know why this could be? I've looked everywhere but I've done it in two files and no-one seems to have an answer to that and if there is a decent answer please link it. Thank you.
I think the real crux of the problem is the code is not adding playerSprite to the all_sprites_list. If a sprite is not in this list, the sprite update and paint calls do not include it. At first I thought the initial position of the sprite may be off-screen, so I parameterised the screen dimensions, and positioned the sprite in the middle.
There's a bunch of other indentation issues in the question's code too, but I think these may be from pasting the question into SO.
I cleaned-up and re-organised the code, it seems to run, and pressing Left/right moves the brown box.
I merged both files together to make my debugging easier, my apologies.
import pygame, random
pygame.init()
BROWN = (205,192,176)
DEEPBROWN = (139,131,120)
CL = (156,102,31)
WHITE = (255,255,255)
WINDOW_WIDTH=500
WINDOW_HEIGHT=500
# Setup the pyGame window
size = (WINDOW_WIDTH, WINDOW_HEIGHT)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Adventure")
class LittleMan (pygame.sprite.Sprite):
def __init__(self, color, width, height):
super().__init__()
self.image = pygame.Surface([50, 75])
self.image.fill(CL)
self.image.set_colorkey(WHITE)
self.rect = self.image.get_rect()
self.rect.center = ( WINDOW_WIDTH//2 , WINDOW_HEIGHT//2 )
def moveRight(self, pixels):
self.rect.x += pixels
def moveLeft(self, pixels):
self.rect.x -= pixels
# Create the player sprite
playerSprite = LittleMan(CL, 200, 300)
# Add user sprite into PyGame sprites list
all_sprites_list = pygame.sprite.Group()
all_sprites_list.add(playerSprite);
clock = pygame.time.Clock()
carryOn = True
while carryOn:
# Handle user input
for event in pygame.event.get():
if event.type == pygame.QUIT:
carryOn = False
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_x: #Pressing the x Key will quit the game
carryOn=False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
playerSprite.moveLeft(5)
if keys[pygame.K_RIGHT]:
playerSprite.moveRight(5)
# Update and Reapint the screen
screen.fill(BROWN)
pygame.draw.rect(screen, DEEPBROWN, [55, 250, 900, 70],0)
all_sprites_list.update()
all_sprites_list.draw(screen)
pygame.display.flip()
clock.tick(60)
The LittleMan class does not include an update() function, which all_sprites_list.update() would normally call. I expect you just haven't needed this part yet.
EDIT: More notes on the sprite update() function ~
The sprite's update() function is called by pygame during the all_sprites_list.update() function. So that means any sprite added to this group, has its update run quasi-automatically. Ideally all sprites have an update function, and it handles the look, position and collisions (etc.) of the sprite.
The idea behind this function is to do any updates to the sprite. So of you had a sprite that was moving, this function would calculate the next position, and set the sprite's self.rect. Or perhaps that sprite is animated - the update function would set the sprite's image to the next frame of the animation based on the time.
Obviously all this work can be performed outside of the update function. But it provides a simple and clean programming mechanism for sprite mechanics.

About releasing memory when using pygame and Python

For example, a projectile flies off screen, does the program still compute its location, speed, etc.?
If so, how to release it?
import pygame
from pygame.locals import *
from sys import exit
pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
background = pygame.image.load(background_image_filename).convert()
sprite = pygame.image.load(sprite_image_filename)
x = 0.
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.blit(background, (0,0))
screen.blit(sprite, (x, 100))
x+= 10.
pygame.display.update()
Yes, the location, speed, etc. still have to be computed, otherwise no object that is off-screen could ever enter the screen area again. Pygame is smart enough not to attempt to render these objects.
It's usually advisable to use pygame sprites and sprite groups which allow you to remove sprites simply by calling self.kill(). You could also use lists or sets to store your objects, but then you have to write a bit more code yourself.
So I'd first define a pygame.Rect (the game_area) with the size of your screen or a bit larger (in the example below I use a smaller one). Rects have a contains method that you can use to check if your sprite's rect is inside the game_area rect. If the sprite is outside, just call self.kill() and pygame will remove the sprite from all associated sprite groups.
import random
import pygame as pg
from pygame.math import Vector2
class Projectile(pg.sprite.Sprite):
def __init__(self, pos, game_area):
super().__init__()
self.image = pg.Surface((5, 5))
self.image.fill(pg.Color('aquamarine2'))
self.rect = self.image.get_rect(center=pos)
self.vel = Vector2(2, 0).rotate(random.randrange(360))
self.pos = Vector2(pos)
self.game_area = game_area
def update(self):
self.pos += self.vel
self.rect.center = self.pos
if not self.game_area.contains(self.rect):
self.kill()
def main():
screen = pg.display.set_mode((640, 480))
game_area = pg.Rect(60, 60, 520, 360)
game_area_color = pg.Color('aquamarine2')
clock = pg.time.Clock()
all_sprites = pg.sprite.Group(Projectile(game_area.center, game_area))
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
all_sprites.add(Projectile(game_area.center, game_area))
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.draw.rect(screen, game_area_color, game_area, 2)
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
Yes it does, but since you have only a single projectile (incremented using x), you can easily choose what to do using a few if statements. The process becomes harder when there are multiple projectiles (which you need to store in a container), you should apply this.
Here is an example
for projectile in projectile_list:
# Check if the position is inside the screen
if 0 < projectile.x < WIDTH and 0 < projectile.y < HEIGHT:
# Do position operations
This way, you only process what is required. You can apply similar method to remove unused projectiles from the list or whatever container you are using.

How do I do collision-detection in pygame?

I have been writing this code recently and I wanted collision-detection but I have never done it before and I need help. this code is written with python and pygame so it should be simple but I'm not sure whether I should have the whole world as a transparent image
import pygame, os, itertools
from pygame.locals import *
w = 640
h = 480
pink = (0,179,179)
player_x = 39
player_y = 320
def setup_background():
screen.fill((pink))
screen.blit(playerImg, (player_x,player_y))
screen.blit(brick_tile, (0,0))
pygame.display.flip()
pygame.init()
screen = pygame.display.set_mode((w, h))
clock = pygame.time.Clock()
playerImg = pygame.image.load('img/player.png').convert_alpha()
brick_tile = pygame.image.load('img/map.png').convert_alpha()
class Player(pygame.sprite.Sprite):
allsprites = pygame.sprite.Group()
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('img/player.png')
self.rect = self.image.get_rect()
class World(pygame.sprite.Sprite):
allsprites = pygame.sprite.Group()
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('img/map.png')
self.rect = self.image.get_rect()
player = Player()
world = World()
done = False
while not done:
setup_background()
block_hit_list = pygame.sprite.spritecollide(player, world.allsprites, True)
for world in block_hit_list:
print("WORKING")
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == K_RIGHT:
player_x += 5
Your sprite collision detection is almost there but incorrect. You seem to know the basics of this concept in this line:
block_hit_list = pygame.sprite.spritecollide(player, world.allsprites, True)
You are almost there, but here is how you do it. In a pygame.sprite.spritecollide() function, you need the sprite's name, the group's name of the other sprite, and either True or False. In your case, you just need to make a group for your world (assuming you want the things in that order) :
world_group = pygame.sprite.Group(world)
This line is necessary for your detection code, and will enable that sprite to be removed. If you want to add another world to that group, so you have plenty of worlds, do this and add some code around it if you want to:
world_group.add(world)
The add() function will add a sprite to that group. I recommend you do a loop to make lots of them if necessary. Now to the collision code!
Now we are ready to do this function correctly. I would delete this line first:
block_hit_list = pygame.sprite.spritecollide(player, world.allsprites, True)
That would be unnecessary and useless for now. You will need to add this if statement in the while loop but outside your for loop:
if pygame.sprite.spritecollide(player, world_group, x):
#Do something
Why did I put x instead of True or False? Because that will be your decision. If you want to delete the sprite in the group after contact, replace x with True. If you don't want to do that, replace x with False. These are the basics of collision detection code, and I hope this helps you!

Sprite Group will not blit [duplicate]

I am a beginner for pygame and I am imitating the game code "alien invasion" in the book "Python Crash Course" to program a game named "Alphabet zoo". In this game, different letters fall down from the top of the screen after a time interval and the letter will vanish while you strike the corresponding key on the keyboard. The x position of each letter is random and the falling speed will accelerate as the game progress. Game will end under a certain condition(e.g. screen height is occupied by letters). This seems a great chanllenge for me. During the first stage, my codes are simplified for same letters 'A' rather than varied letters. They are as the following:
alphabet_zoo.py
import sys
import pygame
from pygame.locals import *
from settings import Settings
from letter import Letter
import game_functions as gf
from pygame.sprite import Group
def run_game():
pygame.init()
az_settings =Settings()
screen = pygame.display.set_mode((0,0), RESIZABLE)
pygame.display.set_caption("Alphabet Zoo")
letter = Letter(az_settings, screen)
letters = Group()
while True:
gf.check_events(az_settings, screen, letters)
letters.update()
gf.update_screen(az_settings, screen, letters)
run_game()
settings.py
class Settings():
def __init__(self):
self.bg_color = (0, 0, 0)
self.letter_speed_factor = 10.5
game_functions.py
import sys
import pygame
from letter import Letter
def letter_generator(az_settings, screen, letters, lag_time):
new_letter = Letter(az_settings, screen)
letters.add(new_letter)
def check_events(az_settings, screen, letters):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_a:
print('a')
def update_screen(az_settings, screen, letters):
screen.fill(az_settings.bg_color)
letters.blitme()
letters.update()
pygame.display.flip()
letter.py
import pygame
import random
from pygame.sprite import Sprite
class Letter(Sprite):
def __init__(self, az_settings, screen):
super().__init__()
self.screen = screen
self.az_settings = az_settings
self.image = pygame.image.load('images/A.png')
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
self.rect.centerx = random.randint(0, self.screen_rect.right)
self.rect.top = self.screen_rect.top
self.center = float(self.rect.centerx)
def blitme(self):
self.screen.blit(self.image, self.rect)
def update(self):
if self.rect.bottom < self.screen_rect.bottom:
self.rect.centery += self.az_settings.letter_speed_factor
I think I should use sprite in the codes. Unfortunately, the program hints " AttributeError: 'Group' object has no attribute 'blitme'" after running. I would appreciate a lot if you help me with the problem.
The method blitme doesn't exist in pygame.sprite.Group. You cannot invoke a method on a pygame.sprite.Group object, that doesn't exist. But you don't need blitme at all. All you have to do is to invoke pygame.sprite.Group.draw():
Draws the contained Sprites to the Surface argument. This uses the Sprite.image attribute for the source surface, and Sprite.rect for the position.
For instance:
letters.draw()
pygame.sprite.Group.draw() and pygame.sprite.Group.update() are methods which are provided by pygame.sprite.Group.
The former delegates the to the update mehtod of the contained pygame.sprite.Sprites - you have to implement the method. See pygame.sprite.Group.update():
Calls the update() method on all Sprites in the Group [...]
The later uses the image and rect attributes of the contained pygame.sprite.Sprites to draw the objects - you have to ensure that the pygame.sprite.Sprites have the required attributes. See pygame.sprite.Group.draw():
Draws the contained Sprites to the Surface argument. This uses the Sprite.image attribute for the source surface, and Sprite.rect. [...]

Categories