Say you move your object Upwards (0-degrees) that would be move_ip((1, 0))
or to the right 90-degrees (0, 1)
then 45-degrees would be (1, 1). You get my point.
But how would I move it, say 22 degrees? Math wise that would be around (0.5, 0.75) (not exactly accurate, but a float value is my point). Now how do I move_ip my unit to (0.5, 0.75)? You can't, because PyGame only moves in full integers, not floats. So the only solution you have is to make the value bigger, with for example (0.5 * 100, 0.75 * 100) so (50, 75). But this doesn't work because now my block is moving way too far/fast so it ends up jumping "through" walls and other objects. Sure I can just do * 2 or so, but you just end up with a smaller increment of the same "moving too fast" problem.
So how can I move character 10-degrees, but one-ish unit away (not 100 units away). I am using degrees, so a solution like move_degrees(degree=10, distance=1) would also be fine.
how do I move_ip my unit to (0.5, 0.75)
You can not.
Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.
The coordinates for Rect objects are all integers. [...]
The fraction part of the coordinates gets lost when the new position of the object is assigned to the Rect object. If this is done every frame, the position error will accumulate over time.
If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location (e.g. .topleft) of the rectangle:
x += 0.5
y += 0.75
rect.topleft = round(x), round(y)
Minimal example:
replit.com live example PyGame-move-one-unit-in-degrees
import pygame
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
x, y = 200, 200
rect = pygame.Rect(0, 0, 20, 20)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
x = (x + 0.5) % 400
y = (y + 0.75) % 400
rect.center = round(x), round(y)
window.fill(0)
pygame.draw.rect(window, (255, 0, 0), rect)
pygame.display.flip()
clock.tick(100)
pygame.quit()
exit()
Related
This question already has answers here:
Pygame: Draw single pixel
(6 answers)
Closed 4 months ago.
I'm trying to iterate through the numpy array and assigning a 0 - 255 value based on the distance to the mouse.
WIDTH and HEIGHT are in this case set to 400 and GRID[] is a numpy matrix with WIDTH and HEIGHT dimensions.
I'm using the window.set_at() function to draw each pixel on the screen with the color stored in the numpy matrix, I'm getting about 5 FPS.
Is there a more efficient way to handle this type of pixel processing, or should I switch to something like c++ & SFML
#update pixels
for y in range(HEIGHT):
for x in range(WIDTH):
#get color based on distance to mouse; 0 -> 255
mousePosition = pg.mouse.get_pos()
dx = mousePosition[0] - x
dy = mousePosition[1] - y
d = math.sqrt(abs(dx ** 2 + dy ** 2))
#constraining the distance value between 0 - 255
c = min(max(d, 0), 255)
GRID[x,y] = c
#draw pixels
for y in range(HEIGHT):
for x in range(WIDTH):
c = GRID[x,y]
window.set_at((x, y), (c, c, c))
What constitutes fast enough?
Tidying up your code to create a minimal example with your 400x400 resolution:
import math
import time
import pygame
width, height = 400, 400
pygame.init()
screen = pygame.display.set_mode((width, height))
screen.fill(pygame.Color("black"))
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Graphics
# update pixels
start = time.time()
mx, my = pygame.mouse.get_pos()
for y in range(height):
for x in range(width):
distance = math.sqrt( (mx - x)**2 + (my - y)**2 ) # 2.2 FPS 0.455s
#constrain the distance value between 0 - 255
c = min(max(distance, 0), 255)
screen.set_at((x, y), (c, c, c))
# Update Screen
pygame.display.set_caption(f"FPS: {clock.get_fps():.1f} Pixel Processing {time.time() - start:.3f} s")
pygame.display.update()
pygame.quit()
This results in 2.2 FPS on my PC.
The Python math module has a function that calculates the hypotenuse, so it's probably a little more optimised. Change the calculation to:
distance = math.hypot(mx - x, my - y)
This increases my frame rate by 50% to 3, probably still too slow.
We can be smarter about the pixels we modify, instead of clamping the distance to 255, fill the screen with white and then if the distance is greater than 255, don't change the pixel.
screen.fill(pygame.Color("white"))
for y in range(height):
for x in range(width):
distance = math.hypot(mx - x, my - y)
if distance <= 255: # don't set far pixels
c = round(distance)
screen.set_at((x, y), (c, c, c))
This increases my frame rate to 12 when the mouse is in the corner, 8 in the middle. Perhaps this is approaching usable.
If you look at the documentation for surface.set_at(), it says that using get_at() and set_at is too slow and recommends using PixelArray or SurfArray. So we can create a SurfArray:
surfarray = pygame.surfarray.pixels3d(screen)
Then to set the pixel values, we replace screen.set_at():
surfarray[x, y] = (c,c,c)
Surprisingly and unfortunately this doesn't change the frame rate significantly. Maybe this requires hardware acceleration.
I also tried manually locking the surface before iterating through the pixels as suggested in the docs, but this made no significant improvement.
So lets consider what we're doing, drawing the same circle wherever the mouse is every frame. It will be faster if we draw the circle once, and then blit it every frame centered on the mouse position. To create the circle, it's similar to what's already been done:
size = 255 * 2
dist_image = pygame.Surface((size, size), pygame.SRCALPHA)
for y in range(size):
for x in range(size):
distance = math.hypot(255 - x, 255 - y)
if distance <= 255:
c = round(distance)
dist_image.set_at((x, y), (c, c, c))
Then our graphics update logic becomes:
screen.fill(pygame.Color("white"))
dist_rect = dist_image.get_rect(center=pygame.mouse.get_pos())
screen.blit(dist_image, dist_rect)
This runs at 60 FPS (max) and takes almost no processing time:
pygame pixel processing is slow
Yes it is. So don't process pixels.
Given your colour resolution of 256, this means we're really dealing with a bunch of coloured circles around the mouse cursor. By considering only these circles, you're specifically not processing all those other pixels that can never be anything other than colour-zero.
The code below implements the program by drawing circles about the mouse cursor where the circle radius is the known distance. So we're drawing a circle in the same "distance-colour". Assuming the circle is drawn using the midpoint circle algorithm, this means that it only needs to calculate 1/8 of the pixels, and the rest are just quadrant (octant?) reflected about an axis of circle-symmetry.
import pygame
import random
# Window size
WINDOW_WIDTH = 600
WINDOW_HEIGHT = 600
###
### MAIN
###
pygame.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ) )
pygame.display.set_caption("Mouse Distance")
# Main loop
clock = pygame.time.Clock()
running = True
while running:
time_now = pygame.time.get_ticks()
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
running = False
# Paint the screen in a gradient centred about the mouse
window.fill( ( 0, 0, 0 ) ) # max distance
mouse_pos = pygame.mouse.get_pos()
for distance in range( 255 ):
colour = ( 255-distance, 255-distance, 255-distance )
pygame.draw.circle( window, colour, mouse_pos, distance, 2 ) # use a width of 2 so there's no "holes"
pygame.display.flip()
# Clamp FPS
clock.tick(60)
pygame.quit()
I don't have time to make the change right now, but this example should draw to a surface, and then blit() that surface to the window for painting. That way we only need to re-compute the surface when the mouse moves.
I am currently creating a game in python with pygame and my AI is currently "seeing" my character through the walls and shoot at it, but the AI is not supposed to shoot. So my question is : how to prevent that ? I've thought about a line collision where the line goes from my AI to my character, and if this line collide a wall then this AI don't shoot.
Any help would be appreciated, thanks a lot !
This is a great question!
Your rectangle can be thought of as 4 lines:
(x, y) → (x+width, y) # top
(x+width, y) → (x+width, y+height) # right
(x, y+height) → (x+width, y+height) # bottom
(x, y) → (x, y+height) # left
Taking your intersecting line, it's possible to use the two-lines intersecting formula to determine if any of these lines intersect (but be careful of parallel lines!)
However the formula (specified in linked Wikipedia article) determines if the lines intersect anywhere on the 2D plane, so it needs to be further refined. Obviously the code can quickly throw away any intersections that occur outside the window dimensions.
Once the "infinite-plane" collision-point has been determined (which is a reasonably quick determination), then a more fine-grained intersection can be determined. Using Bresenham's algorithm, enumerate all the points in the intersecting line, and compare them with a 1-pixel rectangle based on each side of your square. This will tell you which side of the rectangle intersected.
If you only need to know if the rectangle was hit, just check the whole rectangle with pygame.Rect.collidepoint() for each point in the line.
Of course once you have all those points generated, it's easily to not bother with the 2D line collision, but for long lines the code must make a lot of checks. So testing the 2D intersection first really speeds it up.
Basically, is doesn't exist a method nor any pygame functionality to detect collisions with lines, that's why I had to come up with the solution I'm about to show.
Using the following link, at section formulas / Given two points on each line segment, you can find a formula to know if two lines intersect each other, and if they do, where exactly.
The basic idea is to check if for every ray in the lightsource there is an intersection with any of the four sides of the rectangle, if so, the lightray should end at that same side of the rectangle.
import pygame, math
pygame.init()
screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('Rays')
pygame.mouse.set_visible(False)
DENSITY = 500
RADIUS = 1000
run = True
while run:
screen.fill('black')
rect = pygame.Rect(50, 200, 100, 50)
pygame.draw.rect(screen, 'red', rect)
for i in range(DENSITY):
mouse_pos = pygame.mouse.get_pos()
pos_fin = (RADIUS * math.cos(2*math.pi / DENSITY * i) + mouse_pos[0], RADIUS * math.sin(2*math.pi / DENSITY * i) + mouse_pos[1])
if rect.collidepoint(mouse_pos) == False:
for extrem_1, extrem_2 in [(rect.bottomright, rect.topright), (rect.topright, rect.topleft), (rect.topleft, rect.bottomleft), (rect.bottomleft, rect.bottomright)]:
deno = (mouse_pos[0] - pos_fin[0]) * (extrem_1[1] - extrem_2[1]) - (mouse_pos[1] - pos_fin[1]) * (extrem_1[0] - extrem_2[0])
if deno != 0:
param_1 = ((extrem_2[0] - mouse_pos[0]) * (mouse_pos[1] - pos_fin[1]) - (extrem_2[1] - mouse_pos[1]) * (mouse_pos[0] - pos_fin[0]))/deno
param_2 = ((extrem_2[0] - mouse_pos[0]) * (extrem_2[1] - extrem_1[1]) - (extrem_2[1] - mouse_pos[1]) * (extrem_2[0] - extrem_1[0]))/deno
if 0 <= param_1 <= 1 and 0 <= param_2 <= 1:
p_x = mouse_pos[0] + param_2 * (pos_fin[0] - mouse_pos[0])
p_y = mouse_pos[1] + param_2 * (pos_fin[1] - mouse_pos[1])
pos_fin = (p_x, p_y)
pygame.draw.aaline(screen, 'white', mouse_pos, pos_fin)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.display.update()
pygame.quit()
It is maybe not the best, and most optimised piece of code but at the end you should get something that works.
The easiest way to detect the collision between a rectangle and a line is to use pygame.Rect.clipline:
Returns the coordinates of a line that is cropped to be completely inside the rectangle. If the line does not overlap the rectangle, then an empty tuple is returned.
e.g.:
rect = pygme.Rect(x, y, width, height)
if rect.clipline((x1, y1), (x2, y2)):
print("hit")
Minimal example
import pygame
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
rect = pygame.Rect(180, 180, 40, 40)
speed = 5
lines = [((20, 300), (150, 20)), ((250, 20), (380, 250)), ((50, 350), (350, 300))]
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
rect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * speed
rect.y += (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * speed
rect.centerx %= window.get_width()
rect.centery %= window.get_height()
color = "red" if any(rect.clipline(*line) for line in lines) else "green"
window.fill(0)
pygame.draw.rect(window, color, rect)
for line in lines:
pygame.draw.line(window, "white", *line)
pygame.display.flip()
pygame.quit()
exit()
Im trying to make a rectangle bounce, without going off limits.
I want my rectangle to bounce depending on the wall it touched.
In this code im trying to bounce the rectangle in a 90º angle, but it isn't working.
Im using this to calculate each advance:
rect_x += rectxSpeed
rect_y += rectySpeed
When it reachs the limit
if rect_y>450 or rect_y<0:
rectySpeed=5
rect_y=rectySpeed*-(math.pi/2)
if rect_x>650 or rect_x<0:
rectxSpeed=5
rectx_y=rectxSpeed*-(math.pi/2)
Whole code here:
import pygame
import random
import math
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
rect_x= 50.0
rect_y = 50.0
rectxSpeed=5
rectySpeed=5
pygame.init()
# Set the width and height of the screen [width, height]
size = (700, 500)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("My Game")
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# -------- Main Program Loop -----------
while not done:
# --- Main event loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# --- Game logic should go here
# --- Screen-clearing code goes here
# Here, we clear the screen to white. Don't put other drawing commands
# above this, or they will be erased with this command.
# If you want a background image, replace this clear with blit'ing the
# background image.
screen.fill(BLACK)
string=str(rect_x)
string2=str(rect_y)
string3="["+string+"]"+"["+string2+"]"
font = pygame.font.SysFont('Calibri', 25, True, False)
text = font.render(string3,True,RED)
screen.blit(text, [0, 0])
#Main rectangle
pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])
#Second rectangle inside the rectangle 1
pygame.draw.rect(screen, RED, [rect_x+10, rect_y+10, 30, 30])
rect_x += rectxSpeed
rect_y+=rectySpeed
if rect_y>450 or rect_y<0:
rectySpeed=5
rect_y=rectySpeed*-(math.pi/2)
if rect_x>650 or rect_x<0:
rectxSpeed=5
rect_x=rectxSpeed*-(math.pi/2)
# --- Drawing code should go here
# --- Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# --- Limit to 60 frames per second
clock.tick(20)
# Close the window and quit.
pygame.quit()
¿How can i adjust the advance?
This code produce this:
By Changing the reach limit code with:
if rect_y>450 or rect_y<0:
rectySpeed=rectySpeed*-(math.pi/2)
if rect_x>650 or rect_x<0:
rectxSpeed=rectxSpeed*-(math.pi/2)
Produces this:
I think it's important to recognize here that the speed of the rectangle object here is a scalar value, not it's vector counterpart velocity. You are attempting to multiply the rectySpeed (a scalar value) by -(math/pi)/2, which is a value that will be returned in radians as #0x5453 mentioned. As far as the rectangle bouncing differently depending on the wall that it contacted, you have not specified the differing constraints that you intend to impose. I think you may want to consider why you want the rectangle to always bounce at a 90° angle. Note that a rectangle that always bounces at a 90° angle would yield some very odd functionality to the player/user.
The angle that the rectangle is approaching the wall measured from the horizontal will be equal to the angle that the rectangle will rebound off the wall measured from the vertical line of the wall to it's new path (in the case of an x-directional bounce).
In terms of an accurate physics engine, you may want to consider just simplifying your mechanics to the following in the case that the rectangle contacts a wall:
if rect_y>450 or rect_y<0:
rectySpeed=rectySpeed*-1
if rect_x>650 or rect_x<0:
rectxSpeed=rectxSpeed*-1
The above ensures that the rectangle object simply changes direction, therefore the magnitude of the rectangle's speed does not change, however the velocity indeed does since it is a vector quantity.
I'm looking for method that allow me to draw single pixel on display screen. For example when I click mouse, I want the position of clicked pixel to change color. I know how to read mouse pos, but I could not find simple pixel draw ( there is screen.fill method but it's not working as I want).
You can do this with surface.set_at():
surface.set_at((x, y), color)
You can also use pygame.gfxdraw.pixel():
from pygame import gfxdraw
gfxdraw.pixel(surface, x, y, color)
Do note, however, the warning:
EXPERIMENTAL!: meaning this api may change, or dissapear in later
pygame releases. If you use this, your code will break with the next
pygame release.
You could use surface.fill() to do the job too:
def pixel(surface, color, pos):
surface.fill(color, (pos, (1, 1)))
You can also simply draw a line with the start and end points as the same:
def pixel(surface, color, pos):
pygame.draw.line(surface, color, pos, pos)
The usual method of drawing a point on a Surface or the display is to use [`pygame.Surface.set_at']:
window_surface.set_at((x, y), my_color)
However, this function is very slow and leads to a massive lack of performance if more than 1 point is to be drawn.
Minimal example where each pixel is set separately: repl.it/#Rabbid76/PyGame-DrawPixel-1
import pygame
pygame.init()
window = pygame.display.set_mode((300, 300))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
rect = pygame.Rect(window.get_rect().center, (0, 0)).inflate(*([min(window.get_size())//2]*2))
for x in range(rect.width):
u = x / (rect.width - 1)
color = (round(u*255), 0, round((1-u)*255))
for y in range(rect.height):
window.set_at((rect.left + x, rect.top + y), color)
pygame.display.flip()
pygame.quit()
exit()
Another option is to use a "pygame.PixelArray" object. This object enables direct pixel access to Surface objects. A PixelArray pixel item can be assigned directly. The pixel can be accessed by subscription. The PixelArray locks the Surface, You have to close() it when you have changed the pixel:
pixel_array = pygame.PixelArray(window_surface)
pixel_array[x, y] = my_color
pixel_array[start_x:end_x, start_y:end_y] = my_color
pixel_array.close()
Minimal example that set one line of pixels at once: repl.it/#Rabbid76/PyGame-DrawPixel-2
import pygame
pygame.init()
window = pygame.display.set_mode((300, 300))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
rect = pygame.Rect(window.get_rect().center, (0, 0)).inflate(*([min(window.get_size())//2]*2))
pixel_array = pygame.PixelArray(window)
for x in range(rect.width):
u = x / (rect.width - 1)
color = (round(u*255), 0, round((1-u)*255))
pixel_array[rect.left + x, rect.top:rect.bottom] = color
pixel_array.close()
pygame.display.flip()
pygame.quit()
exit()
For those who are interested in a more modern answer to the question you can use pygame.draw.circle() to draw a single pixel at a given position (or center).
pygame.draw.circle(surface, color, center, 0)
The documentation specifically says:
radius (int or float) -- radius of the circle, measured from the center parameter, a radius of 0 will only draw the center pixel
One way of doing that is to draw a line staring and ending at the same point.
pygame.draw.line(surface, (255,255,255), (x,y), (x,y))
draw a single coloured pixel
def drawPoint(x,y,color):
s = pygame.Surface((1,1)) # the object surface 1 x 1 pixel (a point!)
s.fill(color) # color as (r,g,b); e.g. (100,20,30)
# now get an object 'rectangle' from the object surface and place it at position x,y
r,r.x,r.y = s.get_rect(),x,y
screen.blit(s,r) # link the object rectangle to the object surface
of course you have to call: pygame.display.update() once you have
drawn all the points you need, don't call update at every single point.
# with this function, you can draw points and change the yer size
def point(surface, color, x, y, size):
'''the surface need the information of the pygame window'''
for i in range(0, size):
pygame.draw.line(surface, color, (x, y-1), (x, y+2), abs(size))
I want to make a map. So I have values in an array and I want to use those values to make a map. So the values are from 1 to 250 and there are 120 of them (one for 3 degrees). So how do I calculate where should I put my points based on the values in the right order (from 0 to 360 degrees)?
Here is my code so far:
import pygame
white = (255, 255, 255)
black = (0, 0, 0)
import sys
pygame.init()
screen = pygame.display.set_caption("Drawing is fun!")
screen = pygame.display.set_mode((500, 500))
screen.fill(white)
pygame.draw.circle(screen, black, (250, 250), 250, 1)
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit();
sys.exit();
pygame.display.update()
clock.tick(60)
I don't have the array yet but I will have it in the near future. Let's say that the array is named values.
For those who didn't understand my question:
- I have a 500 x 500 pygame canvas.
- I have 120 values which represent 3 degrees each. We got them by spinning an ultra sonic sensor around and reading the values.
- I want to create an image on the canvas that reads those values and puts points in the image and then it connects them forming lines.
- It will be like a map generated by an ultrasonic sensor.
So full size of your field is 2w=500, and half-size is w=250
Center of field has coordinates (cx, cy) = (w, w)
Distance are already normalized in you case (max value 250 = w)
If your objects are point in the center angle of the sector, then screen coordinates of object at i-th index are
central_angle = (1.5 + i * 3) * Pi / 180 //in radians
x = cx + Round(values[i] * Cos(central_angle)) //round float value to get integer coordinate
y = cy + Round(values[i] * Sin(central_angle))