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 !
Related
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.
I want to create yet another clone of Pong with Python and Turtle. My goal is to let my pupils (that begin to code in Python) to practise a bit further.
I'd like to create a Turtle whose shape is an horizontal filled rectangle, like a stylized paddle. But when I create a shape I suppose to be convenient, I get a rotated (vertical) paddle instead of the horizontal one I hoped for.
Here is a code that demonstrates this odd behaviour.
from turtle import *
begin_poly()
fd(200)
left(90)
fd(40)
left(90)
fd(200)
left(90)
fd(40)
left(90)
end_poly()
shape = get_poly()
register_shape("drawn", shape)
polyNotOk = ( (0,0), (100, 0), (100, 20), (0, 20) )
register_shape("polyNotOk", polyNotOk)
polyOk = ( (0,0), (0, 100), (20, 100), (20, 0) )
register_shape("polyOk", polyOk)
t1 = Turtle(shape="drawn")
t2 = Turtle(shape="polyNotOk")
t3 = Turtle(shape="polyOk")
t1.color("black")
t2.color("red")
t3.color("blue")
t1.stamp()
t2.stamp()
t3.stamp()
t1.goto(100,200)
t2.goto(100,-50)
t3.goto(100,-150)
t1.forward(100)
t2.forward(100)
t3.forward(100)
mainloop()
So, as you can see if you run the code, the first drawing is OK, with an horizontal shape. But when I stamp the Turtle t1, the shape is vertical.
Same problem with the 2nd shape, defined through polyNotOk (with values for x and y coords which allow to get a horizontal paddle). I need to create a "vertical" poly to get a horizontal paddle.
So I'm able to find a workaround. Yet I'm still not satisfied with this solution, so I'm asking for brilliant explanations ;-) Thanks in advance.
I hope to illuminate this odd behavior, not defend it. First thing to remember about drawn cursors is that where ever (0, 0) falls in your cursor drawing, that's the center point about which your cursor rotates and the pixel in your cursor that lands on any point you goto().
Some insight might be found in the shapesize() method documentation:
shapesize(stretch_wid=None, stretch_len=None, outline=None)
stretch_wid is stretchfactor perpendicular to orientation
stretch_len is stretchfactor in direction of turtles orientation.
That is, if the cursor is in the default (East) orientation, this reverses the sense of X and Y. I believe that's what you're seeing in your drawing. The X plane is perpendicular to orientation (vertical) and the Y plane is in the direction of orientation (horizontal). The opposite of what we normally expect.
This doesn't appear to be the fault of the Shape() class, but buried in the cursor logic. It may be a historical artifact--if we change to mode("logo") and run your code, we get:
More what we might expect, given that "logo" mode's default orientation is North, and more consistent than before.
Regardless, I would make my paddles a different way. Instead of a custom cursor, I'd use turtle's square cursor and reshape it as needed using shapesize():
from turtle import Screen, Turtle
CURSOR_SIZE = 20
screen = Screen()
t0 = Turtle("square")
t0.shapesize(20 / CURSOR_SIZE, 100 / CURSOR_SIZE)
t0.color("green")
screen.exitonclick()
Still rotated logic (not graphic) from what you might expect, but at least the documentation told us that. But, what I really tend to do is make the paddle in the wrong orientation, and use settiltangle(), but not as a workaround as you did, but to make my paddle face in one direction, but move in the other:
from turtle import Screen, Turtle
CURSOR_SIZE = 20
screen = Screen()
t0 = Turtle("triangle")
t0.shapesize(100 / CURSOR_SIZE, 20 / CURSOR_SIZE)
t0.settiltangle(90)
t0.penup()
t0.color("green")
t0.speed("slowest") # for demonstration purposes
t0.forward(300)
t0.backward(600)
t0.forward(300)
screen.exitonclick()
Notice that I can use forward(10) and backward(10) to move my paddle and not have to do awful things like t0.setx(t0.xcor() + 10). Works great for Space Invader type games where the player faces upwards but moves sideways.
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
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.
I am trying to make a game with pygame but I can't figure out how to keep my character from going off screen(set a limit). I have a .png image controlled by user input, but it's possible for the character to go off the visible screen area normally. I can't figure out how to do this. I made a rectangle around the window, (pygame.draw.rect) but I can't assign the rect to a variable so I can create a collision. I also tried this:
if not character.get_rect() in screen.get_rect():
print("error")
But it didn't work, just spammed the python console with "error" messages.
(i checked the other post with this question but nothing worked/didn't get it)
So my question is, how can I keep my character from going offscreen, and which is the best way to do that?
~thanks
EDIT: My game doesn't have a scrolling playfield/camera. (just a fixed view on the whole window)
if not character.get_rect() in screen.get_rect():
print("error")
I see what you are trying here. If you want to check if a Rect is inside another one, use contains():
contains()
test if one rectangle is inside another
contains(Rect) -> bool
Returns true when the argument is completely inside the Rect.
If you simply want to stop the movement on the edges on the screen, an easy solution is to use clamp_ip():
clamp_ip()
moves the rectangle inside another, in place
clamp_ip(Rect) -> None
Same as the Rect.clamp() [Returns a new rectangle that is moved to be completely inside the argument Rect. If the rectangle is too large to fit inside, it is centered inside the argument Rect, but its size is not changed.] method, but operates in place.
Here's a simple example where you can't move the black rect outside the screen:
import pygame
pygame.init()
screen=pygame.display.set_mode((400, 400))
screen_rect=screen.get_rect()
player=pygame.Rect(180, 180, 20, 20)
run=True
while run:
for e in pygame.event.get():
if e.type == pygame.QUIT: run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_w]: player.move_ip(0, -1)
if keys[pygame.K_a]: player.move_ip(-1, 0)
if keys[pygame.K_s]: player.move_ip(0, 1)
if keys[pygame.K_d]: player.move_ip(1, 0)
player.clamp_ip(screen_rect) # ensure player is inside screen
screen.fill((255,255,255))
pygame.draw.rect(screen, (0,0,0), player)
pygame.display.flip()
When you used pygame.draw.rect, you didn't actually create a "physical" boundary- you just set the colour of the pixels on the screen in a rectangular shape.
If you know the size of the screen, and the displacement of all of the objects on the screen (only applicable if your game has a scrolling playfield or camera), then you can do something like this:
# In the lines of code where you have the player move around
# I assume you might be doing something like this
if keys[pygame.K_RIGHT]:
player.move(player.getSpeed(),0) # giving the x and y displacements
if keys[pygame.K_LEFT]:
player.move(-player.getSpeed(),0)
...
class Player:
...
def move(self, dx, dy):
newX = self.x + dx
newY = self.y + dy
self.x = max(0, min(newX, SCREEN_WIDTH)) # you handle where to store screen width
self.y = max(0, min(newY, SCREEN_HEIGHT))
Note that a useful tool for you to get the size of the Pygame window is pygame.display.get_surface().get_size() which will give you a tuple of the width and height. It is still better, however, to avoid calling this every time you need to know the boundaries of the player. That is, you should store the width and height of the window for later retrieval.
Here's a simple control code that I use in my games to keep sprites from going off the screen:
# Control so Player doesn't go off screen
if self.rect.right > WIDTH:
self.rect.right = WIDTH
if self.rect.left < 0:
self.rect.left = 0
if self.rect.bottom > HEIGHT:
self.rect.bottom = HEIGHT
if self.rect.top < 0:
self.rect.top = 0
WIDTH and HEIGHT are constants that you define to set the size of your screen. I hope this helps.