PyGame Platformer with Interactive Platforms "Drawn" In - python

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

Related

Weird things happening when making image get bigger on mouse hover in Pygame

I am making a scene where there is a thumbs-up image that is supposed to get bigger on mouse hover, and shrink back to normal size when the mouse is no longer hovering.
This is how I make the thumbs-up image:
thumbs_up_image = pygame.image.load("./plz_like.png")
thumbs_up_rect = thumbs_up_image.get_rect(topleft=(screen.get_width() // 2 - thumbs_up_image.get_width() + 75,
screen.get_height() // 2 + thumbs_up_image.get_height() - 225))
And this is how I make it get bigger:
if thumbs_up_rect.collidepoint(pygame.mouse.get_pos()):
thumbs_up_image = pygame.transform.scale(thumbs_up_image,
[n + 50 for n in thumbs_up_image.get_size()])
thumbs_up_rect = thumbs_up_image.get_rect()
This is how the image is blited:
screen.blit(thumbs_up_image, thumbs_up_rect)
The problem is that when I hover on the thumbs-up image, it first goes to the top-left corner of the screen. Then, when I hover on it again, it gets super big and pixelated.
What am I doing wrong?
I managed to figure it out by myself.
This is how I do it:
First, I prepared a bigger version of the image and it's rect: (as shown below)
big_thumbs_image = pygame.transform.scale(thumbs_up_image, [i + 50 for i in thumbs_up_image.get_size()])
big_thumbs_image_rect = thumbs_up_image.get_rect(
topleft=(screen.get_width() // 2 - thumbs_up_image.get_width() + 55,
screen.get_height() // 2 + thumbs_up_image.get_height() - 250))
Then, when the small image's rect collides with the mouse, blit the bigger image:
if thumbs_up_rect.collidepoint(pygame.mouse.get_pos()):
screen.blit(big_thumbs_image, big_thumbs_image_rect)
You are not showing the code that actually renders the image to the screen.; But basically: you are not saving the original size - at each hover event it will grow and grow (and it will grow once per frame, if that code is run in the mainloop).
You need a variable to hold the original image, one to tell your code the image has already been resized, and an else clause on this if to restore the original image: pygame won't do that for you.
Also, when you use the get_rect for the image, its top-left position will always be "0, 0" - you have to translate this top-left corner to a suitable coordinate- getting the rectangle center of the original sprite (wherever the data of its location on the screen is kept), and setting the same center on the new rect should work.
And finally, prefer "rotozoom" than "scale" - Pygame documentation is clear that the second method uses better algorithms for scaling.
Try using this pygame function:
pygame.transform.rotozoom(Surface, angle, scale)
I also had some issues with pixilation in a game but it seemed to work with this.

pygame - Snap Mouse to Grid

I'm making a little platformer game using pygame, and decided that making a level editor for each level would be easier than typing each blocks' coordinate and size.
I'm using a set of lines, horizontally and vertically to make a grid to make plotting points easier.
Here's the code for my grid:
def makeGrid(surface, width, height, spacing):
for x in range(0, width, spacing):
pygame.draw.line(surface, BLACK, (x,0), (x, height))
for y in range(0, height, spacing):
pygame.draw.line(surface, BLACK, (0,y), (width, y))
I want the user's mouse to move at 10px intervals, to move to only the points of intersection. Here's what I tried to force the mouse to snap to the grid.
def snapToGrid(mousePos):
if 0 < mousePos[0] < DISPLAYWIDTH and 0 < mousePos[1] < 700:
pygame.mouse.set_pos(roundCoords(mousePos[0],mousePos[1]))
(BTW, roundCoords() returns the coordinates rounded to the nearest ten unit.)
(Also BTW, snapToGrid() is called inside the main game loop (while not done))
...but this happens, the mouse doesn't want to move anywhere else.
Any suggestions on how to fix this? If I need to, I can change the grid code too.
Thanks a bunch.
P.S. This is using the latest version of PyGame on 64 bit Python 2.7
First of all I think you're not far off.
I think the problem is that the code runs quite fast through each game loop, so your mouse doesn't have time to move far before being set to the position return by your function.
What I would have a look into is rather than to pygame.mouse.set_pos() just return the snapped coordinates to a variable and use this to blit a marker to the screen highlighting the intersection of interest (here I use a circle, but you could just blit the image of a mouse ;) ). And hide your actual mouse using pygame.mouse.set_visible(False):
def snapToGrid(mousePos):
if 0 < mousePos[0] < DISPLAYWIDTH and 0 < mousePos[1] < 700:
return roundCoords(mousePos[0],mousePos[1])
snap_coord = snapToGrid(mousePos)# save snapped coordinates to variable
pygame.draw.circle(Surface, color, snap_coord, radius, 0)# define the remaining arguments, Surface, color, radius as you need
pygame.mouse.set_visible(False)# hide the actual mouse pointer
I hope that works for you !

Get the pixels which are colliding with the Surface

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")

How to Use the Pygame Rect

I have been experimenting with Pygame, and have come across a problem that I could not find the answer to. In this paste, my basic game framework is exhibited. How can i complete my ballSnapLeft() definition correctly?
Edit: I am not looking for my code to be completed, but I am looking for someone to explain how the 'Rect' class(?) works, and how it could be applied.
Edit2: I have tried to use the x and y coordinates to do so, but I think there is a simpler way that can actually work, instead of using brute coordinates.
From Making Games With Python and Pygame:
myRect.left The int value of the X-coordinate of the left side of the
rectangle.
myRect.right
The int value of the X-coordinate of the right side of the rectangle.
myRect.top
The int value of the Y-coordinate of the top side of the rectangle.
myRect.bottom
The int value of the Y-coordinate of the bottom side.
Because all of these attributes return integers, that's probably why your code isn't working.
Also, if your goal with ballSnapLeft() is to move the ball to a position away from the player, ballRect.right = playerRect.left - distance would only change the X coordinate of the rect. To make the ball also move in the Y coordinate you could do something like
def ballSnapTop():
ballRect.top = playerRect.bottom - distance
Are you getting an error when you execute ballRect.right = playerRect.left - (0, 1)?
ballRect.right and ballRect.left, along with the related top, bottom, width, height values, are int types and can't have tuples added or subtracted from them.
You might want to take a look at the pygame.Rect documentation, and consider using pygame.Rect.move(x,y) which will shift the coordinates of the rectangle for you.
It's also worth noting that if you change, for example, myRect.topleft, then the corresponding top, left, bottom, etc... values will change as well so that the rect translates and preserves its size.

How to change a regular image to a Rect in pygame [duplicate]

This question already has an answer here:
converting an image to a rect
(1 answer)
Closed 9 years ago.
I need the simplest way of making an image a Rect, is there a function for this or do I need to make it a Rect from the beginning? I have a script were the player moves around but I want the image of the character to be a Rect, so I can do more with it.
I want the image of the character to be a rect doesn't make a lot of sense, but you're probably looking for the get_rect method of the Surface class:
get_rect()
get the rectangular area of the Surface
get_rect(**kwargs) -> Rect
Returns a new rectangle covering the entire surface. This rectangle will always start at 0, 0 with a width. and height the same size as the image...
(I guess with regular image you mean a Surface, since a Surface is how pygame represents images)
Note that the Rect returned by this function always starts at 0, 0. If you already track the position of your object somehow (say a x and y variable), you could either
drop those variables and only use a Rect to keep track of the position of your object. If you move your object, instead of altering the x and y variable (e.g. x += mx/y += my), you would just update the Rect (e.g. pos.move_ip(mx, my) where pos is the Rect).
create a new Rect whenever you need a Rect, and make sure it points to the right location, using named arguments (e.g. your_surface.get_rect(x=x, y=y))
use a Sprite, which is basically a combination of a Surface and a Rect.

Categories