How do you clear sprites in Pygame? - python

Pygame has the method clear for the Group class which is used to group sprites together. When I call clear on my Group object I can successfully clear the images of all my sprites in that group, but the hitboxes of my sprites remain. I was wondering if there is another way to remove both my sprite's image AND the rectangle hitbox without destroying the sprite object.

You should check mouse position with elements in group and kill() element to remove from group. After that you can use clean() to remove all elements from screen and draw() again to redraw only elements which are still in group
x, y = pygame.mouse.get_pos()
for item in my_group:
if item.rect.collidepoint(x, y):
print("Collision detected")
item.kill()
my_group.clear(screen, background)
my_group.draw(screen)
I created two elements in group so you will see difference if you remove draw() after clear() - it will remove all elements from screen
import pygame
# --- constants ---
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
SIZE = (700, 500)
# --- classes ---
class Block(pygame.sprite.Sprite):
# Constructor. Pass in the color of the block,
# and its x and y position
def __init__(self, color, width, height):
# Call the parent class (Sprite) constructor
super().__init__()
# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
self.image = pygame.image.load("Obrazy/images/square-1.png").convert()
# Fetch the rectangle object that has the dimensions of the image
# Update the position of this object by setting the values of rect.x and rect.y
self.rect = self.image.get_rect()
# --- functions ---
# empty
# --- main ---
pygame.init()
# Set the height and width of the screen
screen = pygame.display.set_mode(SIZE)
pygame.display.set_caption("Testing Screen")
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
my_block1 = Block(WHITE, 20, 20)
my_block2 = Block(WHITE, 20, 20)
my_block2.rect.x = 100
my_group = pygame.sprite.Group()
my_group.add(my_block1)
my_group.add(my_block2)
background = pygame.Surface(SIZE)
screen.fill(BLACK)
#screen.blit(background, (0,0))
my_group.draw(screen)
# -------- Main Program Loop -----------
while not done:
# Set the screen background
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.MOUSEBUTTONDOWN:
x, y = pygame.mouse.get_pos()
for item in my_group:
if item.rect.collidepoint(x, y):
print("Collision detected")
item.kill()
my_group.clear(screen, background)
my_group.draw(screen)
# Limit to 60 frames per second
clock.tick(60)
#my_group.clear(screen, background)
#my_group.draw(screen)
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Be IDLE friendly. If you forget this line, the program will 'hang'
# on exit.
pygame.quit()
BTW: in Python 3 you can use
super().__init__()
instead of
pygame.sprite.Sprite.__init__(self)

Related

How do I save a section of a pygame screen and blit it to another location?

I am making a program with a graph that scrolls and I just need to move a section of the screen.
If I do something like this:
import pygame
screen = pygame.display.set_mode((300, 300))
sub = screen.subsurface((0,0,20,20))
screen.blit(sub, (30,40))
pygame.display.update()
It gives the error message: pygame.error: Surfaces must not be locked during blit
I assume it means the child is locked to its parent surface or something but how else could I go about doing this?
screen.subsurface creates a surface, which reference to the original surface. From documentation:
Returns a new Surface that shares its pixels with its new parent.
To avoid undefined behaviour, the surfaces get locked. You've to .copy the surface, before you can .blit it to its source:
sub = screen.subsurface((0,0,20,20)).copy()
screen.blit(sub, (30,40))
Just don't draw to the screen surface directly. Create a Surface for each part of your game/UI, and blit each of those to the screen.
import pygame
def main():
pygame.init()
screen = pygame.display.set_mode((640, 480))
# create two parts: a left part and a right part
left_screen = pygame.Surface((400, 480))
left_screen.fill((100, 0, 0))
right_screen = pygame.Surface((240, 480))
right_screen.fill((200, 200, 0))
x = 100
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
# don't draw to the screen surface directly
# draw stuff either on the left_screen or right_screen
x += 1
left_screen.fill(((x / 10) % 255, 0, 0))
# then just blit both parts to the screen surface
screen.blit(left_screen, (0, 0))
screen.blit(right_screen, (400, 0))
pygame.display.flip()
if __name__ == '__main__':
main()

What is causing lag in this simple game?

I have this classic example of a pygame program:
import pygame
import random
# Define some colors
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
class Block(pygame.sprite.Sprite):
"""
This class represents the ball.
It derives from the "Sprite" class in Pygame.
"""
def __init__(self, color, width, height):
""" Constructor. Pass in the color of the block,
and its x and y position. """
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
self.image = pygame.Surface([width, height])
self.image.fill(color)
# Fetch the rectangle object that has the dimensions of the image
# image.
# Update the position of this object by setting the values
# of rect.x and rect.y
self.rect = self.image.get_rect()
# Initialize Pygame
pygame.init()
# Set the height and width of the screen
screen_width = 700
screen_height = 400
screen = pygame.display.set_mode([screen_width, screen_height])
# This is a list of 'sprites.' Each block in the program is
# added to this list. The list is managed by a class called 'Group.'
block_list = pygame.sprite.Group()
# This is a list of every sprite.
# All blocks and the player block as well.
all_sprites_list = pygame.sprite.Group()
for i in range(100):
# This represents a block
block = Block(BLACK, 20, 15)
# Set a random location for the block
block.rect.x = random.randrange(screen_width)
block.rect.y = random.randrange(screen_height)
# Add the block to the list of objects
block_list.add(block)
all_sprites_list.add(block)
# Create a RED player block
player = Block(RED, 20, 15)
all_sprites_list.add(player)
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
score = 0
# -------- Main Program Loop -----------
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# Clear the screen
screen.fill(WHITE)
# Get the current mouse position. This returns the position
# as a list of two numbers.
pos = pygame.mouse.get_pos()
# Fetch the x and y out of the list,
# just like we'd fetch letters out of a string.
# Set the player object to the mouse location
player.rect.x = pos[0]
player.rect.y = pos[1]
# See if the player block has collided with anything.
blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True)
# Check the list of collisions.
for block in blocks_hit_list:
score += 1
print(score)
# Draw all the spites
for block in all_sprites_list:
if block.rect.y + 1 > screen_height:
block.rect.y = 0
block.rect.y = block.rect.y + 1
all_sprites_list.draw(screen)
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Limit to 60 frames per second
clock.tick(120)
pygame.quit()
Well, I notice that when I move the mouse, the rest of the blocks are moving slower(like freezing for a fraction of the second) but when it is in one place everything is moving smooth. The player sprite is in the list of all sprites and there is no calculation of the position of the player - only getting mouse's coordinates. And in my opinion there shouldn't be difference between getting (100,100) all the time and getting something else for coordinates of the mouse.
Could someone please explain why motion of the mouse (player's sprite) affects the rest of the sprites?
Thanks!
The problem is probably that you've been running Python for too long a while without restarting, and a fair bit of data has accumulated in memory without being deallocated during previous runs. As noted in the comments, rebooting will usually solve it.

background updates over the sprites

the background of my game is being weird. it doesn't work and the sprites show up, then it works but on top of the sprites!
import pygame
import random
# Define some colors
black = ( 0, 0, 0)
white = ( 255, 255, 255)
lives = 10
# Call this function so the Pygame library can initialize itself
pygame.init()
screen = pygame.display.set_mode([700, 600])
clock = pygame.time.Clock()
# Set positions of graphics
background_position = [0,0]
# Make mouse invisible
pygame.mouse.set_visible(False)
pygame.font.init()
font= pygame.font.Font(None, 50)
# This class represents the ball
# It derives from the "Sprite" class in Pygame
class Background(pygame.sprite.Sprite):
# Constructor. Pass in the color of the block,
# and its x and y position
def __init__(self, width, height):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
# Create an image of the block.
# This could also be an image loaded from the disk.
self.image = pygame.image.load("Grass2.fw.png").convert()
# Fetch the rectangle object that has the dimensions of the image
# image.
# Update the position of this object by setting the values
# of rect.x and rect.y
self.rect = self.image.get_rect()
# Reset position to the right of the screen, at an x location.
# Called by update() or the main program loop if there is a collision.
def reset_pos(self):
self.rect.y =(0)
self.rect.x =(0)
# Called each frame
def update(self):
# Move block left some pixels
self.rect.x -= 1
# If block is too far left, reset to right of screen.
if self.rect.x < -700:
self.reset_pos()
class Block(pygame.sprite.Sprite):
# Constructor. Pass in the color of the block,
# and its x and y position
def __init__(self, color, width, height):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
self.image = pygame.image.load("Enemy small.fw.png").convert()
self.image.set_colorkey(white)
# Fetch the rectangle object that has the dimensions of the image
# image.
# Update the position of this object by setting the values
# of rect.x and rect.y
self.rect = self.image.get_rect()
# Reset position to the top of the screen, at a random x location.
# Called by update() or the main program loop if there is a collision.
def reset_pos(self):
self.rect.y = random.randrange(0, 600)
self.rect.x = random.randrange(700, 800)
# Called each frame
def update(self):
# Move block right one pixel
self.rect.x -= 1
# If block is too far left, reset to top of screen.
if self.rect.x < -100:
self.reset_pos()
class Player(Block):
def __init__(self, width, height):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
# Create an image of the block.
# This could also be an image loaded from the disk.
self.image = pygame.image.load("ship.fw.png").convert()
self.image.set_colorkey(white)
# Fetch the rectangle object that has the dimensions of the image
# image.
# Update the position of this object by setting the values
# of rect.x and rect.y
self.rect = self.image.get_rect()
def update(self):
# Get the current mouse position. This returns the position
# as a list of two numbers.
pos = pygame.mouse.get_pos()
# Fetch the x and y out of the list,
# just like we'd fetch letters out of a string.
# Set the player object to the mouse location
self.rect.x=pos[0]
self.rect.y=pos[1]
# Initialize Pygame
pygame.init()
# Set the height and width of the screen
screen_width=700
screen_height=600
screen=pygame.display.set_mode([screen_width,screen_height])
pygame.display.set_caption('Razazone')
clock.tick(70)
#This is a list of 'sprites.' Each block in the program is
# added to this list. The list is managed by a class called 'Group.'
block_list = pygame.sprite.Group()
# This is a list of every sprite. All blocks and the player block as well.
all_sprites_list = pygame.sprite.Group()
background_list = pygame.sprite.Group()
# This represents a background
background = Background(20,15)
# Set a location for the block
background.rect.x = (screen_width)
background.rect.y = (screen_height)
background_list.add(background)
# Add the block to the list of objects
all_sprites_list.add(background)
for i in range(1):
# This represents a block
block = Block(black, 20, 15)
# Set a random location for the block
block.rect.x = random.randrange(screen_width)
block.rect.y = random.randrange(screen_height)
# Add the block to the list of objects
block_list.add(block)
all_sprites_list.add(block)
# Create a red player block
player = Player(350, 300)
all_sprites_list.add(player)
done=False
I know there is something wrong about the order of my updates but I can't figure out exactly what I need to do to fix the problem.
# -------- Main Program Loop -----------
while done==False:
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT: # If user clicked close
done=True # Flag that we are done so we exit this loop#
# Calls update() method on every sprite in the list
all_sprites_list.update()
# See if the player block has collided with anything.
blocks_hit_list = pygame.sprite.spritecollide(player, block_list, False)
# Check the list of collisions.
for block in blocks_hit_list:
lives -=1
print (lives)
# Reset block to the top of the screen to fall again.
block.reset_pos()
# Reset block to the right of the screen to fall again.
#background.reset_pos()
if lives<=1:
lives=1
screen.fill(black)
background.update()
background_list.draw(screen)
# Draw all the spites
all_sprites_list.draw(screen)
lives_text=font.render("%d lives" % lives, True, white)
screen.blit(lives_text, [100,100])
#background.update()
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
pygame.quit()
You add background to background_list and all_sprite_list and then you draw background_list and all_sprite_list so your background is drawn twice.
Remove all_sprites_list.add(background)

Pygame: .set_clip returns first value in list, instead of requested value

I am trying to make walking animations for images returned from a sprite sheet.
I am using .set_clip to return a portion of the sprite sheet. Everything looks like it should work
But the .set_clip is returning the first Rect value in the list that I provide, rather than the item in the list that I call for.
My Code
import pygame, sys
from pygame.locals import *
xList = (6, 27,48,69)
yList = (24,24,24,24)
wList = (21,21,21,21)
hList = (32,32,32,32)
ani_pos = list(zip(xList, yList, wList, hList))
sprite_num = 0
class Player:
def __init__(self):
playerSheet = pygame.image.load('MainChar1.png').convert() # load sprite sheet
playerSheet.set_clip(pygame.Rect(ani_pos[sprite_num]))
self.player = playerSheet.subsurface(playerSheet.get_clip())
self.x = 320
self.y = 240
def draw(self, DISPLAYSURF):
DISPLAYSURF.blit(self.player, (self.x, self.y))
self.player.set_colorkey(255, 0, 255)# set sprite background invisible
class Background:
def __init__(self):
dirtTile = pygame.image.load('DirtBackground.png').convert()
dirtTile.set_clip(pygame.Rect(0, 0, 863, 1103))
self.background = dirtTile.subsurface(dirtTile.get_clip())
self.x = -111
self.y = -311
def draw(self, DISPLAYSURF):
DISPLAYSURF.blit(self.background, (self.x, self.y))
pygame.init()
FPS = 30
FPSCLOCK = pygame.time.Clock()
# set up the window
WINDOWWIDTH = 640 # size of windows' width in pixels
WINDOWHEIGHT = 480 # size of windows' height in pixels
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
pygame.display.set_caption('farm game')
player = Player()
background = Background()
pygame.key.set_repeat(1,0) # (mSec to begin repeat, mSec between repeats)
running = True
while running: # the main game loop
DISPLAYSURF.fill (255,255,255)
background.draw(DISPLAYSURF)
player.draw(DISPLAYSURF)
pygame.display.flip()
FPSCLOCK.tick(FPS)
for event in pygame.event.get(): # event handling loop
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
running = False
elif event.type == KEYDOWN:
if event.key == K_d:
background.x -= 4
sprite_num = 2
I am using .set_clip to return a portion of the sprite sheet.
No, you don't. You use subsurface to return a portion of the sprite sheet.
I don't know what you think what set_clip does, but it's probably not what it actually does:
This is a rectangle that represents the only pixels on the Surface that can be modified.
Better explained at the SDL docs:
Sets the clipping rectangle for a surface. When this surface is the destination of a blit, only the area within the clip rectangle will be drawn into.
But the .set_clip is returning the first Rect value in the list that I provide, rather than the item in the list that I call for.
set_clip does not return any Rect value of any list at all.
When you call pygame.Rect(ani_pos[sprite_num]), you're creating a Rect with the values in the list ani_pos at index sprite_num, and sprite_num is 0 when the __init__ method is called.
You store your Surface that you create in the __init__ method in the player field, but you never actually change it. If you want to create some kind of animation, you should change it every n frames. You change sprite_num to 2, but that does not do anything since you never use that value again.

problem with making background of image transparent: pygame

I'm in the middle of working on a simple typing tutor using pygame. My problem is that I'm using an image that has a white background, waves1.png. Now's I've specified that I want white to be transparent in the image (self.image.set_colorkey((255, 255, 255))) and it is for everything except the text block. When the waves intersect with the text object, the white background of the waves show on top of the text. You can try running this if you have pygame (with the exception of the waves1.png image).
import pygame
from pygame.locals import *
class TextSprite(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.wordList = ['words yes', 'hello', 'this is a sentence', 'this is another sentence'] # read directly from external file
self.pos = 0
self.wordNum = 0
self.update1()
def update1(self):
# Render the given word
self.image = pygame.font.Font(None, 36).render(self.wordList[self.wordNum], 1, (0, 0, 0))
# Render the correctly guessed letters
self.correct = pygame.font.Font(None, 36).render(self.wordList[self.wordNum][:self.pos], 1, (255, 0, 0))
# Copy correct letters onto given word
self.image.blit(self.correct, (0, 0))
self.rect = self.image.get_rect()
# set the center of the center the given word to the center of the screen
self.rect.center = pygame.display.get_surface().get_rect().center
def keyin(self, key):
word = self.wordList[self.wordNum]
letter = word[self.pos]
if letter == key:
self.pos = self.pos + 1
if self.pos == len(word):
self.reset()
self.update1()
def reset(self):
self.pos = 0
self.wordNum = self.wordNum + 1
self.update1()
class Waves(pygame.sprite.Sprite):
# Constructor. Pass in the color of the block,
# and its x and y position
def __init__(self, filename):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
self.image = pygame.image.load(filename).convert()
# makes any white in the image transparent
self.image.set_colorkey((255, 255, 255))
self.rect = self.image.get_rect()
# Decrease the y coordinate so the waves look like they're moving up
def update(self, text):
self.rect.y = self.rect.y - 6
if self.rect.y <= 200:
text.reset()
self.rect.y = 485
def main():
#I - Import and initialize
pygame.init()
#D - Display configuration
# The screen variable is a pygame Surface object
# Note that the set_mode() method creates a Surface object for you automatically
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Typing Game")
#E - Entities (just background for now)
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((255, 255, 255))
screen.blit(background, (0,0))
#A - Action (broken into ALTER steps)
#A - Assign values to key variables
clock = pygame.time.Clock()
keepGoing = True
# Collect the sprite in a list
all = pygame.sprite.RenderPlain()
waveList = pygame.sprite.RenderPlain()
text = TextSprite()
all.add(text)
waves = Waves("waves1.png")
waveList.add(waves)
waves.rect.x = 0
waves.rect.y = 485
#L - Set up main loop
while keepGoing:
#T - Timer to set frame rate
# Tick is a method in the Clock class that determines the maximum frame rate
clock.tick(30)
#E - Event handling
for event in pygame.event.get():
if event.type == QUIT:
keepGoing = False
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
keepGoing = False
else:
text.keyin(event.unicode)
# update position of waves
waves.update(text)
# clears screen
all.clear(screen, background)
# update screen
all.draw(screen)
waveList.clear(screen, background)
waveList.draw(screen)
# display.flip is a method that copies everything from the screen object to the actual visual display
pygame.display.flip()
pygame.quit ()
if __name__ == '__main__': main()
I don't know if it's an option for you, but you should get better results with png's native alpha transparency.
If you can edit/recreate the png yourself, then try using a transparent background.
From there, you can use convert_alpha() arfter loading the image. (instead of using a colorkey)
http://pygame.org/docs/ref/surface.html#Surface.convert_alpha
EDIT: one other aspect, is that the image may have an alpha channel interfering with the colorkey. Best to ensure you're not trying to use both.
I'm told that you can detect an image's alpha channel programmatically. Something like ...
if self.image.get_masks()[3]!=0:
print "image has alpha!"
See here http://pygame.org/docs/ref/surface.html#Surface.get_masks
HTH
Well done! You've actually done everything correctly to take advantage of transparency and colorkey (ie, making sure to call convert on the surface, making sure to pass the color into the set_colorkey method, etc).
The problem is with the order of calls to draw and clear on your respective sprite groups, "all" and "waveList". After you've rendered the text blocks by calling all.draw, you then follow it with the call to waveList.clear.
Here's the problem: once you've drawn the text sprites, you don't want to clear the space underneath the wave sprites, or that will wipe out the area that overlaps the already-drawn text blocks.
If you want to do this properly, try doing it in this order:
waves.update()
all.clear(screen,background)
waveList.clear(screen,background)
all.draw(screen)
waveList.draw(screen)
(more simply, just move waveList.clear(screen, background) to the line just below all.clear(screen, background); that should do it)
When I'm working with sprite groups, I usually try to group it so that each sprite group calls the same method in this order: clears, updates, collision checks (if any), draws.
This usually handles things in the right order. Then you still may have to pay attention to whether there is any layering of sprites, but that's another story for another day.

Categories

Resources