Pygame collision detect with object and rect - python

Yep, I'm asking another question about this program :D
Anyway, I currently a program that creates two lines on the screen with a gap in between them that can scroll. From here, I obviously need to see if the two objects are colliding. Since I only have one sprite and one rectangle I thought it was slightly pointless and overkill to make two classes for them. However, I can only find tutorials relating to classes which I obviously don't need. So, my question really is:
Is it possible to test for collision between a standard image and a Pygame rect? If it is not, how can I convert either the image, rectangle or both the sprites to do this. (All preferably without using classes.)
Note: the image and rectangle are created in the following ways (if it makes a difference)
bird = pygame.image.load("bird.png").convert_alpha()
pipeTop = pygame.draw.rect(screen, (0,200,30), Rect((scrollx,0),(30,height)))
pipeBottom = pygame.draw.rect(screen, (0,200,30), Rect((scrollx,900),(30,-bheight)))

An image by itself does not have a position. You cannot test collision between a rect and something that is not placed in the world. I would recommend to create a class Bird along with a class Pipe that will both subclass pygame.Sprite.
Pygame already has collision detection built in.
A short example
bird = Bird()
pipes = pygame.Group()
pipes.add(pipeTop)
pipes.add(pipeBottom)
while True:
if pygame.sprite.spritecollide(bird,pipes):
print "Game Over"
EDIT:
Don't be afraid of classes, you will have to use them anyways sooner or later.
If you really don't want to use sprites, you can use the birds rect and the pipe and call collide_rect to check if they overlap.
EDIT2:
an example Bird class modified from pygame docs
class Bird(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("bird.png").convert_alpha()
# 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()
You could then add methods such as move, which will move the bird down with the force of gravity.
The same would apply for the Pipe but instead of loading an image, you can create an empty Surface, and fill it with a color.
image = pygame.Surface(width,height)
image.fill((0,200,30)

You can just get the x and y values and compare them:
if pipe.x < bird.x < pipe.x+pipe.width:
#collision code
pass

Related

How to remove image, then blit it again?

I am creating a game with pygame and I ran into a problem. My game is a space game: there are meteors coming at you, you have to dodge them and you can also shoot missles at them. When the missles hit the meteors, I want to make the missle dissapear and the meteor to be reset back where it started falling. I saw a line of code that showed how to make an image transparent, by doing this:
transparant = (0,0,0,0)
#Code in between
missle.image = missleImg
#I wrote some code to see if the missle was touching a meteor
missle.image.fill(transparent)
However, when I try to shoot the missle again, it is still transparent. Does anybody have a better way to unblit and the reblit an image?
Use lists or groups to organize the objects. Create one list or groups to for the meteors and another list or group for the missiles. When a missile hits a meteor, remove the objects from the container. Create new objects when you need them:
If the meteor immediately appears in a different position, it is sufficient to change the meteor's position attribute rather than destroying an object and creating a new one.
e.g. pygame.sprite.Sprite and pygame.sprite.Group:
meteor_group = pygame.sprite.Group():
missile_group = pygame.sprite.Group():
pygame.sprite.groupcollide(meteor_group, missile_group, True, True)
e.g. lists:
meteor_list = []:
missile_list = []:
for meteor in meteor_list[:]:
for missile in missile_list[:]:
collide = # [...] check for collision
if collide:
meteor_list.remove(meteor)
missile_list.remove(missile)

Most efficient way to detect collisions between rectangle lists in pygame? [duplicate]

This question already has answers here:
rect collision with list of rects
(2 answers)
How do I detect collision in pygame?
(5 answers)
Closed 2 years ago.
I'm writing an asteroids clone in Pygame and I've tried a few different ways to detect collisions between 'asteroid' rectangles and 'projectile' rectangles (both of which inherit from Rect). I'd like to know which of them (or what other method of detecting collisions) will lead to the least amount of lag. Moreover, I'd like to know why they have the least lag (I tried looking at the source for the Rect class to see how collidelist, colliderect, and collidepoint worked, but it was compiled in bytecode or something of that nature, which I unfortunately don't know anything about). The asteroids and projectiles are in lists named 'asteroids' and 'projectiles' respectively.
I first tried iterating through my projectiles list and using pygame.rect.collidelist on the asteroids list to find collisions:
index = 0
for projectile in projectiles[:]: #copy so I can delete from original
index = projectile.collidelist(asteroids)
if index != -1:
del asteroids[index]
projectiles.remove(projectile)
Unfortunately, this is very laggy, probably because I'm checking all of the rectangles on the screen.
To minimize unnecessary checks, I tried defining a 10 by 10 grid using my screen WIDTH and HEIGHT, and both the projectile and asteroid classes have a method which sets their gridRect attribute to a tuple with the coordinates of the grid segment they are in. I then detect collisions with a nested for-each-in loop like this:
for projectile in projectiles[::-1]:
for asteroid in asteroids[::-1]:
if projectile.gridRect == asteroid.gridRect and projectile.colliderect(asteroid):
projectiles.remove(projectile)
asteroids.remove(asteroid)
This worked really well as long as there weren't more than 50 projectiles and asteroids, but if possible I'd like even better performance.
Finally, I tried creating an array, sorting the rects into it, and then detecting collisions:
gridRects = []
for i in range(GriddedRect.GRIDSIZE):
gridRects.append([])
for j in range(GriddedRect.GRIDSIZE):
gridRects[i].append({"Projectiles": [], "Asteroids": []})
for asteroid in asteroids:
gridRects[asteroid.gridRect[0]][asteroid.gridRect[1]]["Asteroids"].append(asteroid)
for projectile in projectiles:
gridRects[projectile.gridRect[0]][projectile.gridRect[1]]["Projectiles"].append(projectile)
for i in range(GriddedRect.GRIDSIZE):
for j in range(GriddedRect.GRIDSIZE):
for projectile in gridRects[i][j]["Projectiles"][::-1]:
index = projectile.collidelist(gridRects[i][j]["Asteroids"])
if not index == -1:
asteroids.remove(gridRects[i][j]["Asteroids"][index])
del gridRects[i][j]["Asteroids"][index]
projectiles.remove(projectile)
gridRects[i][j]["Projectiles"].remove(projectile)
This was probably more laggy than the first one, but I thought that it should be the least laggy because although it's more complex, it minimizes the amount of calls to check if a Rect should colllide.
I know that the second method is the least laggy, but an explanation of why would be really helpful.
Instead of the second method, I considered trying to only check asteroids within a certain distance of each projectile, but I can't see how I could do this in an efficient way, because for each projectile I would still have to check the distance of every asteroid.
I'm sure there must be a better way than these methods, so if you know of one please let me know. Also, if you need to see more of my code, let me know and I can edit it in. I think this is the minimum amount needed to get the idea though.

Is there a way to kill a Pygame sprite without having to kill it in a group?

I have a single ball sprite and I don't really have a need for a group. I want to set that sprite to be deleted (or killed) when it reaches the end of the screen, also so it generates another one. I am trying to do this without a group and wanted to know if this is possible. Thank you for your expertise.
I tried self.kill() and kill.self in an update method for the sprite class. That didn't work. I tried to do a kill in the screen update loop for the application. That didn't work. I have done extensive research and all the answer are only when using groups. I also tried to do "if not pygame.surface.rect.contains(Ball())" and that didn't work. (It is something like that, I tried several variations of that expression.)
def update(self):
"""Move the ball down"""
self.y = self.y + 1
self.rect.y = self.y
#print("Screen is: " + str(pygame.surface.rect()))
print("Object is: " + str(self.rect))
if (self.rect.y >= 800):
self.kill()
def update_screen(screen, ball, lamp):
#Show on the screen
screen.fill((135,206,250))
lamp.blitme()
ball.blitme()
ballgen(screen)
ball.update()
lamp.update()
pygame.display.flip()
I expected the results to stop counting the rect of the ball, but it keeps counting up, thus making me think that it is not being removed.
If you don't use the Group class, there's no point in calling the Sprite.kill method. All it does is to remove the Sprite from all of its groups. Calling kill does not magically remove the Sprite from your game.
If you want to have a Sprite removed, just stop using it: either stop calling ball.blitme() and ball.update() in your update_screen function, or replace ball with a new instance of your Sprite's class.
But consider starting to actually use the Group class. It'll make your life easier.
To check if the ball is inside the screen, instead of
if not pygame.surface.rect.contains(Ball())
use
if not pygame.display.get_surface().get_rect().contains(self.rect):
self.rect.center = (whatever_new_position, ....)
in Ball's update method. pygame.display.get_surface() will get you the screen surface, get_rect() will get you the Rect of that Surface, and contains() will check if the Rect of the Ball is still inside the screen.
Then we just set a new position for the ball.
Also note that there's usually no need for an x and y attribute in a sprite class since the position is already stored in the rect attribute.

How to set the position of an image using the center of the image in pygame?

I am using python 2.7, and im using pygame to make a basic game.
(code can be found here: http://github.com/ProgrammerKid/snake)
(snake.py is my main file)
My game was initially supposed to mock the snake game, but it turned out to be more like pacman... so my problem is that since in pygame, the position of an image is always set to the top left corner of the image.... is there was i can make it the center of the image...
because the position of the killer/alien is at the top left, and same with the pacman/snake... so the two objects would literally have to be overlapping for the alien to kill the pacman.... i want it so that they only overlap a little....:
check out this picture:
http://i.stack.imgur.com/N88GX.png
see: Pygame Rect Reference
you probably already know:
imageObject = pygame.image.load(filename)
rect = imageObject.get_rect()
Now try:
rect.center = (newX, newY)
Or, better:
rect.move(xDistance, yDistance)
Good Luck!

Loading and blitting images per frame?

I have a simple top down vertical shooter a la Galaga that I'm messing around with. However, after looking through the documentation I've become a bit confused on how to efficiently load images, and whether to blit them every frame or not. All of the images are loaded through a few classes which inherit the pygame sprite class. At the moment, I'm loading the image as a class level attribute as such:
class Laser(pygame.sprite.Sprite):
image = None
def __init__(self, start_x, start_y):
pygame.sprite.Sprite.__init__(self)
self.pos_x = start_x
self.pos_y = start_y
if Laser.image is None:
Laser.image = pygame.image.load('img/laser_single.png')
self.image = Laser.image
self.rect = self.image.get_rect()
self.rect.topleft = [self.pos_x, self.pos_y]
I'm hoping this prevents Python from loading a new instance of the image into memory every time I create a new Laser(). But will this work as I anticipate?
The second issue stems from blitting all of the active sprites onto the pygame surface. At the moment I loop through a list of Laser(), Enemy(), and whatnot objects and blit each one individually before calling pygame.display.update(). It seems redundant to have to blit each object individually, so I'm asking whether or not this is the most efficient method pygame implements. Or, is there a way to blit every object at once and see some sort of performance improvement?
I'm hoping this prevents Python from loading a new instance of the image into memory every time I create a new Laser(). But will this work as I anticipate?
Yes. Not only does this save memory, since every instance will just have a reference to the class variable, it will also increase performance because the image is only loaded once.
The second issue ssems from blitting all of the active sprites onto the pygame surface.
This depends. If your framerate is OK, then stick with it. If your framerate starts dropping, have a look at DirtySprite, and maybe this article.
Another python class that comes in handy is the LayeredDirty sprite group, which will automatically handle dirty sprites and updating only the relevant screen parts instead of the entire screen for you.
It seems redundant to have to blit each object individually
With sprite groups, as e.g. the LayeredDirty or the simple Group, you add your sprites to the group once and just call the draw method of the sprite group.
sprites = pygame.sprite.LayeredDirty((sprite1, sprite2, sprite3))
sprites.add(sprite4)
...
sprites.draw(pygame_screen)
Using sprite groups will also enable you to do simple collision detection between groups using pygame.sprite.groupcollide and pygame.sprite.spritecollideany

Categories

Resources