Note: I do not want to use pygame sprites.
I am making a simple game and I need a way to detect if two images overlap using pixel-perfect collision in pygame. All the answers I have found so far require that I use pygame sprites, which I prefer not to use because I have less control over the objects.
(These images have transparent backgrounds)
First of all, don't be afraid of Sprites.
A Sprite is just a simple class with an image (a Surface that is stored in the image attribute) and the size and position of the image (a Rect stored in the rect attribute).
So when you use a class like this:
class Player:
def __init__(self, image, pos):
self.image = image
self.pos = pos
def draw(self, screen):
screen.blit(self.image, self.pos)
you could simple use the Sprite class instead, since not much would change:
class Player(pygame.sprite.Sprite):
def __init__(self, image, pos):
super().__init__()
self.image = image
self.rect = image.get_rect(center=pos)
Instead, it becames simpler, because we can let pygame handle blitting the image to the screen.
So, to use pixel perfect collision, you can use pygame's Mask class. Use pygame.mask.from_surface to create a Mask from your Surface, and use pygame.mask.Mask.overlap to check if two masks overlap.
It's easier to use when you use the Sprite class, since you could just use functions like spritecollide together with collide_mask.
But if you don't want to use the Sprite class, just take a look how collide_mask is implemented to see how you can use masks:
def collide_mask(left, right):
xoffset = right.rect[0] - left.rect[0]
yoffset = right.rect[1] - left.rect[1]
try:
leftmask = left.mask
except AttributeError:
leftmask = from_surface(left.image)
try:
rightmask = right.mask
except AttributeError:
rightmask = from_surface(right.image)
return leftmask.overlap(rightmask, (xoffset, yoffset))
Related
I am making a non-scrolling platformer in pygame, and am wondering if there is an easy way to detect collisions with the edge of the window, without creating four rects offscreen. Does anyone know if there is? Thanks.
If you're testing collisions for Rects, you can use
if (Rect.left < 0 or Rect.right > (window width) or
Rect.top < 0 or Rect.bottom > (window height)):
collision = True # do whatever collision code you need here
If you need a way to get the screen size, you can use
width, height = pygame.display.get_surface().get_size()
and then use the width and height variables.
You can use pygame.Rect.contains to test if a rectangle is entirely inside another rectangle:
window_rect = screen.get_rect()
if not window_rect.contains(object_rec):
# [...]
Use pygame.Rect.colliderect to test if a rectangle is entirely outside another rectangle:
window_rect = screen.get_rect()
if not window_rect.colliderect(object_rec):
# [...]
I am making a simple project in python pyglet that involves drawing a large amount of entities and tiles to the screen, typically ~77K tiles at a time, to do this, I use two batches and have every tile as a sprite, where its x,y position on the screen is its x,y position in the world.
The problem comes when I try to implement some sort of side-scrolling feature into it, to avoid asking an XY question, I figure I should just ask what the best way to do this is.
I have tried many ways to increase performance:
Moving around glViewport(), but batches do not draw entities outside of the original size.
Updating all the sprites co-ordinates every tick, which insanely slow
Drawing all sprites to another Texture, but I haven't found anything in the documentation about this, the blit_into method gives me "Cannot blit to a texture.
The camera class, update() is called every tick
class Camera:
def __init__(self):
self.world_sprites = []
self.world_x = 0
self.world_y = 0
def add_sprite(self, spr: tools.WorldSprite):
self.world_sprites.append(spr)
def update(self):
for spr in self.world_sprites:
spr.update_camera(self)
The update_camera method inside of the WorldSprite class
def update_camera(self, cam):
self._x = self.px - cam.world_x
self._y = self.py - cam.world_y
self._update_position()
It works, it's just very, very slow.
Sorry if this is a big question.
This question already has an answer here:
Why is my PyGame Sprite, in a Group, not drawn - AttributeError: 'Group' object has no attribute 'blitme'
(1 answer)
Closed 2 years ago.
Hello there dear friends,
for my python project, I made a button class -and their images, coords, actions and so on- and things working good. But I think I will add lots of buttons in the game so I decided to add them to a one pygame sprite group with their coordinates and blit automatically with a for loop.
for oge in buttonList:
pygame.blit(oge, (x, y)
is there a way can I add sprites with their coords to groups or lists, to blit them all together?
Short answer:
If each sprite has an attribute .rect and .image, then you can call .draw():
buttonList.draw(surf)
Long answer:
A pygame.sprite.Sprite object should have a .rect property of type pygame.Rect this attribute defines the position (and size) of the sprite.
In the following I assume that buttonList is a pygame.sprite.Group.
Each Sprite in the Group shuold have a .rect property which is used to draw the sprite at its location e.g.:
class MySprite(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = [...]
self.rect = self.image.get_rect()
All the sprite of the group can be drawn by on call. The parameter surf can be any surface, e.g. the display surface:
buttonList.draw(surf)
Note, the draw() method of the pygame.sprite.Group draws the contained Sprites onto the Surface. The .image of each sprite is "blit" at the location .rect.
pygame.Rect has a lot of virtual attributes, to set its position (and size) e.g. .center or .topleft. Use them to set the position of the sprite:
mysprite = MySprite()
mysprite.rect.topleft = (x, y)
Of course, the position (x, y) can be a parameter the constructor of the sprite class, too:
class MySprite(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = [...]
self.rect = self.image.get_rect(topleft = (x, y))
mysprite = MySprite(x, y)
I have a class in my pygame program that extends sprite, and effectively gives collision and whatnot to a rectangle. The __init__ of the class is as follows:
def __init__(self,topleft,size,label):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(size)
self.rect = self.image.get_rect()
self.rect.topleft = topleft
self.image.fill([128,128,128])
pygame.draw.rect(self.image, [200,200,200], self.rect, 5)
And this works great... for one rectangle. Problem is, after that rectangle, any others are solidly colored rectangles set to the fill color (128,128,128) instead of being a filled rectangle with a differently colored (200,200,200) border. I'm expecting this is some sort of issue with class variables as opposed to instance variables, but I'm unsure where the problem lies.
Okay, I figured it out. The Rect argument in the draw function is relative to the position of the image. Since I had set its rect to its location on the screen, it was drawing the rectangle way offset from the corner. It worked on my first one only because it happened to start at [0,0]. Here is the fixed draw code:
pygame.draw.rect(self.image, [200,200,200],pygame.Rect([0,0],size), 5)
I've seen several different posts about changing a sprite image.
For the current assignment, I have to construct a packman sprite, then it should follow the mouse. No problem there.
Here is that bit of code:
class Packman(games.Sprite):
"""Create the packman that is conrolled by the mouse"""
#load the packman image
image_right = games.load_image("snap_right.png") # initial value
image_left = games.load_image("snap_left.png")
show_image = image_right
def __init__(self, x=games.mouse.x, y = games.mouse.y):
"""Initialise packman"""
super(Packman, self).__init__(image = Packman.show_image, x = games.mouse.x, y = games.mouse.y)
def update(self):
"""Move packmans coordinates"""
self.x = games.mouse.x
self.y = games.mouse.y
if self.left < 0:
self.left = 0
if self.right > games.screen.width:
self.right = games.screen.width
#change Packman's direction that he is facing`
As you can see I try and load two images, but only one image at a time will be displayed.
(I reckon there is a way of just flipping one image horizontally, in stead of using two images.) As is, I can move Packman around. Now I need to add the bit that makes Packman face left/right depending on the direction the mouse is traveling in. My handbook gives me an example to rotate the image through 180deg with keypresses, which work, but then packman is just upside down, with his eye at the bottom.
Is there another way of flipping packman depending on mouse direction? (I only need horizontal flip, i.e. left and right)
Well I use Pygame for my sprites but the solution is pretty simple.
I basically use something like this. Two pointers pointing to initialized images. And then a pointer to pointers called image which will be what we use to draw the sprites.
# image is the pointer that points to the pointer that points to the image being currently used
image1 = pygame.image.load('left.png')
image2 = pygame.image.load('right.png')
image = image1
Then in draw I just do
screen.blit(image, position)
Now since you are using the mouse to track the position of pacman. What you have to do is this. At EACH frame, store the location of the mouse x and y in a class variable. Call it something like old_x and old_y. On the next frame, simply compare your mouse x position to your old_x. If the mouse position is greater, your pacman is trying to move to the right. image = image2 If your mouse position is less, then your pacman is moving to the left. image = image1 Apply the necessary state changes and change your image accordingly.