Get the pixels which are colliding with the Surface - python

Right now, I have a collision detection in pygame which checks if two rctangles overlap or not...what I am trying to do is check the transparency of a the surface and if the alpha value is less then 10, stop player from walking into it..
Currently, Im doing this:
for i in range(0,self.rect.w):
for j in range(0,self.rect.h):
if player.rect.collidepoint((i,j)) and self.image.get_at((i,j))[3]<10:
#STOP PLAYER
But it is a real pain on the Processor. Is there another way to get the collision pixel coordinates in pygame??

Use pygame.mask.Mask objects and overlap() or overlap_mask().
overlap() :
Returns the first point of intersection encountered between this mask and othermask.
[...]
Returns point of intersection or None if no intersection.
overlap_mask():
Returns a Mask, the same size as this mask, containing the overlapping set bits between this mask and othermask.
A mask can be created form a pyame.Surface with pygame.mask.from_surface()-
e.g.:
player_mask = pygame.mask.from_surface(player.image)
self.mask = pygame.mask.from_surface(self.image)
offset = (player.rect.x - self.rect.x), (player.rect.y - self.rect.y)
first_intersection_point = self.mask.overlap(player_mask , offset)
if first_intersection_point:
print("hit")

Related

How to make mask collision detection less laggy in python pygame

Im trying to make a collision detection system for a game im making. This section of code works fine when I am detecting collisions when the objects are smaller, but now once I make a mask of a moon which can be up to 1000 pixels in diameter it starts to lag my computer. Ive tried to make a 2nd image of the outline of the moon to use to detect the collisions but upon further testing it would still detect collisions while in the middle of the outline (in the transparent parts of the image) and did not help the lag. I also tried to make the outline image less pixels but keeping the same size. The images move down the screen as a ship moves across the screen to dodge them. I need pixel perfect collisions
If someone could tell me how to reduce lag, or some other way of detecting if my ship is within the circle that would be a big help :)
for i in range(numMoon):
moonRect = pygame.Rect(moonX[i], moonY[i], int(100*moonScale[i]), int(100*moonScale[i]))
if moonRect.colliderect(shipRect):
moonMask = pygame.mask.from_surface(moon)
offset_x = shipRect.x - moonRect.x
offset_y = shipRect.y - moonRect.y
crash = moonMask.overlap(shipMask, (offset_x, offset_y))
if crash:
print('moon')
Creating a Mask from a Surface is an expensive operation. Do not generate the pygame.mask in the loop. Create the mask during initialization directly after loading the image:
moon = pygame.image.load(...)
moonMask = pygame.mask.from_surface(moon)
Use the pre-generated mask
for i in range(numMoon):
moonRect = pygame.Rect(moonX[i], moonY[i], int(100*moonScale[i]), int(100*moonScale[i]))
if moonRect.colliderect(shipRect):
offset_x = shipRect.x - moonRect.x
offset_y = shipRect.y - moonRect.y
crash = moonMask.overlap(shipMask, (offset_x, offset_y))
if crash:
print('moon')

Handling sprite collisions against an angled rectangular obstacle that is static

So I have this obstacle that I want my sprite to collide with and it is at a particular angle.In this case, we are measuring from the positive x-axis to the top of the rectangle and in this instance it is 333.02 degrees with respect to the positive x-axis or 63.02 degrees with respect to the negative y-axis. So my issue is that how do I set up my pygame sprite to properly collide with the angle rectangle obstacle? Pygame sprite rectangles have no rotation attribute (to my knowledge) and I can't just say, "Hey when the right corner of my sprite collides with top, etc" because of this lack of rotation. My collisions work great for horizontal and even vertical surfaces but I am stuck on how to collide with angled obstacles.
Here is my collision code right now. It uses vectors and checks both x and y independently to see if anything is there. And below is a picture of the object I want to collide with created in the Tile Map Editor. It is at an angle of 333.02 degrees like I mentioned before. I also included a rough sketch of the axis in case that is relevant.
def update(self):
self.animate()
self.acc = vec(0, PLAYER_MASS * GRAVITY)
self.move()
# Equations of Motion
self.acc.x += self.vel.x * PLAYER_FRICTION
self.vel += self.acc
# Collision check in all 4 directions
self.pos.x += (
self.vel.x + 0.5 * self.acc.x * self.game.dt
) # Update x component (Frame-independent motion)
if abs(self.vel.x) < PLAYER_VELX_EPSILON:
self.vel.x = 0
self.rect.x = self.pos.x
hits = pg.sprite.spritecollide(self, self.game.platforms, False)
for hit in hits: # Horizontal collision
if self.vel.x > 0: # Rightward motion
self.rect.right = hit.rect.left
elif self.vel.y < 0: # Leftward motion
self.rect.left = hit.rect.right
self.pos.x = self.rect.x # Update true postion
self.pos.y += self.vel.y + 0.5 * self.acc.y * self.game.dt # Update y component
self.rect.y = self.pos.y + 5
# This prevents double jumping
if self.vel.y > 0:
self.onGnd = False
hits = pg.sprite.spritecollide(self, self.game.platforms, False)
for hit in hits: # Vertical Collision
if self.vel.y > 0: # Downward motion
self.rect.bottom = hit.rect.top
self.vel.y = 0
self.onGnd = True
elif self.vel.y < 0: # Upward motion
self.rect.top = hit.rect.bottom
self.vel.y = 0
self.pos.y = self.rect.y # Update true postion
# Limit Player's movement
if self.rect.bottom > HEIGHT:
self.vel.y = 0
self.rect.bottom = HEIGHT
self.pos.y = self.rect.y
Any help on this problem would be greatly appreciated!
The answer is coordinate translation. Imagine that the rotated object had its own coordinate system, where x runs along the bottom of the rectangle, and y up the side on the left. Then, if you could find the position of your sprite in that coordinate system, you could check for collisions the way you normally would with an unrotated rectangle, i.e., if x >=0 and x <= width and y >=0 and y <= height then there's a collision.
But how do you get the translated coordinates? The answer is matrices. You can use 2d transformation matrices to rotate, scale and translate vectors and coordinates. Unfortunately my experience with these types of transformations is in C#, not python, but this page for instance provides examples and explanations in python using numpy.
Note that this is quite simply the way 2d (and 3d) games work - matrix transformations are everywhere, and are the way to do collision detection of rotated, scaled and translated objects. They are also how sprites are moved, rotated etc: the pygame transform module is a matrix transformation module. So if the code and explanations looks scary at first glance, it is worth investing the time to understand it, since it's hard to write games without it beyond a certain point.
I'm aware this is not a full answer to your question, since I haven't given you the code, but it's too long for a comment, and hopefully points you in the right direction. There's also this answer on SO, which provides some simple code.
EDIT: just to add some further information, a full collision detection routine would check each collidable pixel's position against the object. This may be the required approach in your game, depending on how accurate you need the collision detection to be. That's what I do in my game Magnetar (https://www.youtube.com/watch?v=mbgr2XiIR7w), which collides multiple irregularly shaped sprites at arbitrary positions, scales and rotations.
I note however that in your game there's a way you could possibly "cheat" if all you need is collision detection with some angled slopes. That is you could have a data structure which records the 'corners' of the ground (points it change angle), and then use simple geometry to determine if an x,y point is below or above ground. That is, you would take the x value of a point and check which segment of the ground it is over. If it is over the sloped ground, work out how far along the x axis of the sloped ground it is, then use the sin of the angle times this value to work out the y value of the slope at that position, and if that is greater than the y value of the point you are checking, you have a collision.
The answer from seesharper may be the right way to go. I have no experience with that but it looks interesting and I will have to go read the links and learn something about this approach. I will still provide my immediate reaction to the question before I saw his answer though.
You could use pygames mask and mask collision detection routines. Create a mask that was the shape of the angled rectangle/platform and use the methods to detect collisions with that.
If you add a self.mask attribute to your Sprite subclass, the sprites will automatically use that mask in the collision detection.

How can I made a collision mask?

I try to make a collision mask to detect if two sprites collide themselves, but It's not working at all, I got an instant crash, Can you help me ?
My code is :
Player.rect = Player.image.get_rect()
oc1.rect = oc1.image.get_rect()
mask_1 = pg.mask.from_surface(Player)
mask_2 = pg.mask.from_surface(oc1)
Cm = pg.sprite.collide_mask(mask_1, mask_2)
if Cm != None :
print('life - 1')
See the documentation of pygame.sprite.collide_mask():
Collision detection between two sprites, using masks.
collide_mask(SpriteLeft, SpriteRight) -> point
Tests for collision between two sprites, by testing if their bitmasks overlap. If the sprites have a "mask" attribute, that is used as the mask, otherwise a mask is created from the sprite image. Intended to be passed as a collided callback function to the *collide functions. Sprites must have a "rect" and an optional "mask" attribute.
The parameters to .collide_mask() have to be 2 pygame.sprite.Sprite objects, rather than 2 mask pygame.mask.Mask objects:
In the following is assumed, that Player and oc1 are pygame.sprite.Sprite objects:
Player.rect = Player.image.get_rect()
oc1.rect = oc1.image.get_rect()
Cm = pg.sprite.collide_mask(Player, oc1)
if Cm != None :
print('life - 1')
Minimal example: repl.it/#Rabbid76/PyGame-SpriteMask
See also Sprite mask

how to make a sprite a circle for collisions [duplicate]

I am using pygame to make a simple game. I am having issues with circle collisions. I am getting the following error:
"AttributeError: 'pygame.Rect' object has no attribute 'rect'"
Here is the particular code I am having issues with below:
if pygame.sprite.collide_circle(hero_circle, enemy_circle):
gameover()
Use pygame.mask to create a collision mesh for your objects and use the mesh to do collision detections.
In more detail:
Create an image file for both of your circles and set the bg color to something you will not use anywhere else.
Set that color to "transparent" in your image editor.
Import the images.
Create a mesh for them with pygame.mask and set it to make transparent pixels non-collidable.
Use the generated mask as your collision detection mesh.
PROFIT
(Technically this is just doing collision detection of a circle shaped area on a rectangle, but who cares!)
pygame.draw.rect()
draw a rectangle shape
rect(Surface, color, Rect, width=0) -> Rect
Draws a rectangular shape on the Surface. The given Rect is the area of the rectangle. The width argument is the thickness to draw the outer edge. If width is zero then the rectangle will be filled.
Keep in mind the Surface.fill() method works just as well for drawing filled rectangles. In fact the Surface.fill() can be hardware accelerated on some platforms with both software and hardware display modes.
The best way I've found to check circle collision detection is to calculate the distance between the center points of two circles. If the distance is less than the sum of the two circle's radii, then you've collided.
Just like how gmk said it but if your are using circles instead of rectangles, you should use this pygame function :
pygame.draw.circle(surface, color, center_point, radius, width)
This draws a circle on your surface (which would go in the surface area). Clearly the color requires a list of numbers (RGB anyone?). Your center_point decides the location of your circle since it will be the location of the center of your circle. The radius will need a number to set the radius of the circle (using the number like 25 will set your radius at 25 pixels/diameter at 50 pixels). the width section is optional as it sets the thickness of the perimeter of your circle (having 0 will have none at all). If you are not using circles, you should change your title... But anyways, I hope this helps you!

PyGame Platformer with Interactive Platforms "Drawn" In

I'm looking for the easiest way to implement this. I'm trying to implement platforms (with full collision detection) that you can draw in via mouse. Right now I have a line drawing function that actually draws small circles, but they're so close together that they more or less look like a line. Would the best solution be to create little pygame.Rect objects at each circle? That's going to be a lot of rect objects. It's not an image so pixel perfect doesn't seem like an option?
def drawGradientLine(screen, index, start, end, width, color_mode):
#color values change based on index
cvar1 = max(0, min(255, 9 * index-256))
cvar2 = max(0, min(255, 9 * index))
#green(0,255,0), blue(0,0,255), red(255,0,0), yellow(255,255,0)
if color_mode == 'green':
color = (cvar1, cvar2, cvar1)
elif color_mode == 'blue':
color = (cvar1, cvar1, cvar2)
elif color_mode == 'red':
color = (cvar2, cvar1, cvar1)
elif color_mode == 'yellow':
color = (cvar2, cvar2, cvar1)
dx = end[0] - start[0]
dy = end[1] - start[1]
dist = max(abs(dx), abs(dy))
for i in xrange(dist):
x = int(start[0]+float(i)/dist*dx)
y = int(start[1]+float(i)/dist*dy)
pygame.draw.circle(screen, color, (x, y), width)
That's my drawing function. And here's my loop that I have put in my main game event loop.
i = 0
while (i < len(pointList)-1):
drawGradientLine(screen, i, pointList[i], pointList[i + 1], r, mode)
i += 1
Thanks for any help, collision detection is giving me a huge headache right now (still can't get it right for my tiles either..).
Any reason you want to stick with circles?
Rectangles will make the line/rectangle a lot more smooth and will make collision detecting a lot easier unless you want to look into pixel perfect collision.
You also don't seem to save your drawn objects anywhere (like in a list or spritegroup), so how are you going to check for collision?
Here's a leveleditor I did for game awhile back, it's not perfect, but it works:
https://gist.github.com/marcusmoller/bae9ea310999db8d8d95
How it works:
The whole game level is divided up into 10x10px grid for easier drawing
The leveleditor check if the mouse is being clicked and then saves that mouse position
The player now moves the mouse to another position and releases the mouse button, the leveleditor now saves that new position.
You now have two different coordinates and can easily make a rectangle out of them.
Instead of creating a whole bunch of rect objects to test collision against, I'm going to recommend creating something called a mask of the drawn-in collideable object, and test for collision against that. Basically, a mask is a map of which pixels are being used and which are not in an image. You can almost think of it as a shadow or silhouette of a surface.
When you call pygame.draw.circle, you are already passing in a surface. Right now you are drawing directly to the screen, which might not be as useful for what I'm suggesting. I would recommend creating a rect which covers the entire area of the line being drawn, and then creating a surface of that size, and then draw the line to this surface. My code will assume you already know the bounds of the line's points.
line_rect = pygame.Rect(leftmost, topmost, rightmost - leftmost, bottommost - topmost)
line_surf = pygame.Surface((line_rect.width, line_rect.height))
In your drawGradientLine function, you'll have to translate the point coordinates to the object space of the line_surf.
while (i < len(pointList)-1):
drawGradientLine(line_surf, (line_rect.x, line_rect.y), i, pointList[i], pointList[i+1], r, mode)
i += 1
def drawGradientLine(surf, offset, index, start, end, width, color_mode):
# the code leading up to where you draw the circle...
for i in xrange(dist):
x = int(start[0]+float(i)/dist*dx) - offset[0]
y = int(start[1]+float(i)/dist*dy) - offset[1]
pygame.draw.circle(surf, color, (x, y), width)
Now you'll have a surface with the drawn object blitted to it. Note that you might have to add some padding to the surface when you create it if the width of the lines you are drawing is greater than 1.
Now that you have the surface, you will want to create the mask of it.
surf_mask = pygame.mask.from_surface(line_surf)
Hopefully this isn't getting too complicated for you! Now you can either check each "active" point in the mask for collision within a rect from your player (or whatever other objects you want to collide withe drawn-in platforms), or you can create a mask from the surface of such a player object and use the pygame.Mask.overlap_area function to check for pixel-perfect collision.
# player_surf is a surface object I am imagining exists
# player_rect is a rect object I am imagining exists
overlap_count = surf_mask.overlap_area(player_surf, (line_rect.x - player_rect.x, line_rect.y - player_rect.y))
overlap_count should be a count of the number of pixels that are overlapping between the masks. If this is greater than zero, then you know there has been a collision.
Here is the documentation for pygame.Mask.overlap_area: http://www.pygame.org/docs/ref/mask.html#pygame.mask.Mask.overlap_area

Categories