pygame rect collision smaller than image - python

How can I define a rect collision detection smaller than image in pygame?
I'd like to have a collision patter like the second image , but I'm having a cut image when a try to set the width and height in the method rect.
When I try to set using image size, I have the collision detection in red
self.rect = pygame.rect.Rect(location, self.image.get_size())
If I set the size using width and height, I just have the third image
self.rect = pygame.rect.Rect(location, (32, 150))
I really wouldn't like to use pixel perfect collision, because is the slowest collision detection, so someone have some idea how can I achieve the second image collision approach using Rect? Thanks.

It seems that you are using pygames built in sprite module. (Please correct me if I am wrong)
You might know that each sprite consist of an image (which is drawn on a surface) and a rect object (sets location and size (!) of the image).
As Luke Taylor suggested, you could create a new rect object in your player class …
self.collideRect = pygame.rect.Rect((0, 0), (32, 150))
… and set its location (according to your graphic) to
self.collideRect.midbottom = self.rect.midbottom
Every time you change the position of your player you must call this line too, so your self.collideRect rect object "moves" with your player on screen.
To test if a point (e.g. the mouse coordinates) is inside the self.collideRect, call
if self.collideRect.collidepoint(x, y) == True:
print("You clicked on me!")

Try drawing a completely new rectangle separate from the image that is behind the image, but who's location is constantly set to that if the image.

Related

Pygame rect.inflate function does not seem to work

How does Pygame's "rect.inflate_ip" function work?
In this code, I am trying to inflate this rectangle by some value zoom:
rect = pygame.draw.rect(screen,block.color,(block.x+scx,block.y+scy,10,10))
rect.inflate_ip(zoom,zoom)
But this does not have any effect. Why?
Print rect before and after inflate_ip and you'll see the difference. Of course, it doesn't affect the rectangle drawn on the screen. pygame.draw.rect does not generate an object. This function fills a rectangular area on the screen and returns that area. You have to create a pagame.Rect object and use that for drawing. e.g.:
rect = pygame.Rect(block.x+scx, block.y+scy, 10, 10)
rect.inflate_ip(zoom, zoom)
pygame.draw.rect(screen, block.color, rect)

How to get rect of a pygame Surface without transparent parts?

I am implementing collision detection and want to check if a rectangular object is touching the player. My wall uses .set_colorkey(background) where background is the specified background colour. The problem is that when I get my wall's rectangle with .get_rect(), it gets the full image's size which includes the transparent parts instead of just the opaque parts.
I thought about making the wall image file smaller in size to remove the background but that would be inconvenient as I would need to do this for each partially transparent image I have. I also thought about using arrays to get the colour and checking if it matches the background colour and getting the rectangle's size from there but that would be slow and cumbersome.
for x, y in ((i, j) for i in land_x for j in land_y):
# land_x, land_y hold the tiles to be checked
try:
tx1, ty1, tx2, ty2 = \
texture[land[y][x]].get_rect()
# tx1, ty1 coordinates of top-left corner
# tx2, ty2 width and height respectively
if tx2 == 0 and ty2 == 0:
continue # skip to other objects
tx1 = x*64 - tx2/2
ty1 = y*64 - ty2/2
px1, py1, px2, py2 = \
PLAYER.get_rect()
px1 = player_x - px2/2
py1 = -player_y - py2/2
if p.Rect(px1, py1, px2, py2).colliderect(
p.Rect(tx1, ty1, tx2, ty2)
):
player_x -= direction_x
break # go outside loop to start checking y
except IndexError: # incase player is outside map
pass # skip to other objects
The .get_rect() outputs a rectangle the size of the whole image whereas I want a rectangle that doesn't include the transparent parts.
Example:
texture is a 64x64 image with a 48x48 block in the centre.
The background colour is removed and a 48x48 solid coloured block is left (even though the image size is still 64x64).
Expected Output:
texture.get_rect() should output a rectangle of size 48x48.
Actual Output:
texture.get_rect() instead outputs a rectangle of size 64x64.
Any help on this would be appreciated :D
If you want to ignore the transparent pixels in your collision detection, you're talking about pixel-perfect collision.
To do this in pygame, pygame offers the Mask class. You usually create your masks with pygame.mask.from_surface and use it together with pygame.sprite.spritecollide and pygame.sprite.collide_mask.
Maybe think about using pygame's Sprite class to make use of all the features it offers.
Even if you don't want to use pygame's build-in collision detection, you can take a look at the source to see how it works.
You are making this too hard. You know the size of your objects. Add a smaller collision rect to each of your objects at creation time and use that for collision. Or use a circle if that is better for the object.
tile.crect = Rect(whatever)
Or just multiply the existing rect dimensions by some scale factor for your collision rect. Don't do all of these calculations. Store a Rect for each collideable object and have a rect for the player.
tx1 = x*64 - tx2/2
ty1 = y*64 - ty2/2
px1, py1, px2, py2 = \
PLAYER.get_rect()
px1 = player_x - px2/2
py1 = -player_y - py2/2
Then just test collision directly:
for t in tiles:
if player.rect.colliderect( t.rect ):
If the player is a sprite its rect moves around. Look at the example code in the doc.
https://www.pygame.org/docs/ref/sprite.html

How to get the surface from a rect/line

I am trying to find the point where a line collides with a brick in the arkanoid that i am making. The most logical way i found is getting the mask from the line and use collidemask as it returns the point. Well as i tried with this:
linemask = pygame.mask.from_surface(pygame.draw.line(screen, (0,0,0), bola.line[0], bola.line[1], 2))
it gave me this error:
TypeError: argument 1 must be pygame.Surface, not pygame.Rect
meaning that the input(in this case the line) can't be a rect but needs to be a surface. Do you know how to get the surface from a rect or any alternative solution ?
pygame.draw.line draws on a Surface and returns the affected area in form of a Rect object.
The Surface you drew on is screen. So it's screen you want to create a mask from. Alternatively, create a new Surface that you use pygame.draw on and create a mask from it. Or create a mask from the subsurface of the screen (so you don't have to create a mask from the whole screen), like this:
rect = pygame.draw.line(screen, (0,0,0), bola.line[0], bola.line[1], 2)
surface = screen.subsurface(rect)
mask = pygame.mask.from_surface(surface)

Weird shifting when using pygame.transform.rotate()

def rotate(self):
#Save the original rect center
self.saved_center=self.rect.center
#Rotates a saved image every time to maintain quality
self.image=pygame.transform.rotate(self.saved_image, self.angle)
#Make new rect center the old one
self.rect.center=self.saved_center
self.angle+=10
When I rotate the image, there is a weird shifting of it despite the fact that I'm saving the old rect center and making the rotated rect center the old one. I want it to rotate right at the center of the square.
Here's what it looks like:
http://i.imgur.com/g6Os9.gif
You are just calculating the new rect wrong. Try this:
def rotate(self):
self.image=pygame.transform.rotate(self.saved_image, self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
self.angle+=10
It tells the new rect to center itself around the original center (the center never changes here. Just keeps getting passed along).
The issue was that the self.rect was never being properly updated. You were only changing the center value. The entire rect changes as the image rotates because it grows and shrinks in size. So what you needed to do was completely set the new rect each time.
self.image.get_rect(center=self.rect.center)
This calculates a brand new rect, while basing it around the given center. The center is set on the rect before it calculate the positions. Thus, you get a rect that is properly centered around your point.
I had this issue. My method has a bit of a different purpose, but I solved it quite nicely.
import pygame, math
def draw_sprite(self, sprite, x, y, rot):
#'sprite' is the loaded image file.
#'x' and 'y' are coordinates.
#'rot' is rotation in radians.
#Creates a new 'rotated_sprite' that is a rotated variant of 'sprite'
#Also performs a radian-to-degrees conversion on 'rot'.
rotated_sprite = pygame.transform.rotate(sprite, math.degrees(rot))
#Creates a new 'rect' based on 'rotated_sprite'
rect = rotated_sprite.get_rect()
#Blits the rotated_sprite onto the screen with an offset from 'rect'
self.screen.blit(rotated_sprite, (x-(rect.width/2), y-(rect.height/2)))

How can I crop an image with Pygame?

I am learning pygame and want a graphic for a button with the three states: normal, hover, and pressed. I have an image like this one ...
... and I want to get a new Surface using a portion of it.
I'm loading the image with this code:
buttonStates = pygame.image.load(os.path.join('image','button.png'))
How can I make a new surface using just a portion of that graphic?
cropped = pygame.Surface((80, 80))
cropped.blit(buttonStates, (0, 0), (30, 30, 80, 80))
The blit method on a surface 'pastes' another surface on to it. The first argument to blit is the source surface. The second is the location to paste to (in this case, the top left corner). The third (optional) argument is the area of the source image to paste from -- in this case an 80x80 square 30px from the top and 30px from the left.
You can also use the pygame.Surface.subsurface method to create subsurfaces that share their pixels with their parent surface. However, you have to make sure that the rect is inside of the image area or a ValueError: subsurface rectangle outside surface area will be raised.
subsurface = a_surface.subsurface((x, y, width, height))
There are 2 possibilities.
The blit method allows to specify a rectangular sub-area of the source _Surface:
[...] An optional area rectangle can be passed as well. This represents a smaller portion of the source Surface to draw. [...]
In this way you can blit an area of the source surface directly onto a target:
cropped_region = (x, y, width, height)
target.blit(source_surf, (posx, posy), cropped_region)
Alternatively, you can define a subsurface that is directly linked to the source surface with the subsurface method:
Returns a new Surface that shares its pixels with its new parent. The new Surface is considered a child of the original. Modifications to either Surface pixels will effect each other.
As soon as a subsurface has been created, it can be used as a normal surface at any time:
cropped_region = (x, y, width, height)
cropped_subsurf = source_surf.subsurface(cropped_region)
target.blit(cropped_subsurf, (posx, posy))
I think the best way to do it is crop the image of these 3 kind of buttons in a external program and load in different surface instead use pygame to crop it

Categories

Resources