what does draw.lines return? - python

from what I understand draw.lines joins each coordinate that is passed to it, so if we have [A, B, C]
will draw a line from coordinate A to coordinate B and from B to C and if closed is True it draws a line from coordinate A to coordinate C therefore it will always join the first coordinate with the last one its right?
what i don't understand is what the rect variable returns..
I think that every time I add a new coordinate it returns (starting_point, rectangle_size) where starting point is the first coordinate and the rectangle size is calculated by the distance of the first coordinate with the last one then draw the rectangle with draw.rect
but the reasoning I don't think is right because if I add a coordinate of this type to the list the rectangle remains unchanged
CODE:
"""Place a polygone line with the clicks of the mouse."""
import pygame
from pygame.locals import *
RED = (255, 0, 0)
GREEN = (0, 255, 0)
GRAY = (150, 150, 150)
pygame.init()
screen = pygame.display.set_mode((640, 240))
drawing = False
points = []
running = True
while running:
for event in pygame.event.get():
if event.type == MOUSEBUTTONDOWN:
points.append(event.pos)
drawing = True
elif event.type == MOUSEBUTTONUP:
drawing = False
elif event.type == MOUSEMOTION and drawing:
points[-1] = event.pos
screen.fill(GRAY)
if len(points)>1:
rect = pygame.draw.lines(screen, RED, True, points, 3)
pygame.draw.rect(screen, GREEN, rect, 1)
pygame.display.update()
pygame.quit()

pygame.draw.lines() returns a pygame.Rect object that encloses all the points of the line:
a rect bounding the changed pixels, if nothing is drawn the bounding rect's position will be the position of the first point in the points parameter (float values will be truncated) and its width and height will be 0
The rectangle does not start at any particular point on the line, but is just large enough to enclose all the points along the line.
It returns the same as:
list_x, list_y = zip(*points)
min_x, max_x = min(list_x), max(list_x)
min_y, max_y = min(list_y), max(list_y)
rect = pygame.Rect(min_x, min_y, max_x-min_x, max_y-min_y)

According to the docs:
Returns:
a rect bounding the changed pixels, if nothing is drawn the bounding rect's position will be the position of the first point in the points parameter (float values will be truncated) and its width and height will be 0

this is the reasoning I made:
import pygame
def minore(lista_coordinate, asse):
dimensione = lista_coordinate[0][asse]
for coordinata in lista_coordinate:
if coordinata[asse] < dimensione:
dimensione = coordinata[asse]
return dimensione
def maggiore(lista_coordinate, asse):
dimensione = lista_coordinate[0][asse]
for coordinata in lista_coordinate:
if coordinata[asse] > dimensione:
dimensione = coordinata[asse]
return dimensione
pygame.init()
schermo = pygame.display.set_mode((500, 400))
punti = [(100, 100), (200, 100), (200, 50), (150, 80)]
larghezza_minore = minore(punti, 0)
larghezza_maggiore = maggiore(punti, 0)
larghezza_rettangolo = larghezza_maggiore - larghezza_minore
altezza_minore = minore(punti, 1)
altezza_maggiore = maggiore(punti, 1)
altezza_rettangolo = altezza_maggiore - altezza_minore
dimensioni_rettangolo = (larghezza_rettangolo, altezza_rettangolo)
inizio = (larghezza_minore, altezza_minore)
pygame.draw.lines(schermo, (255, 0, 0), True, punti, 3)
pygame.draw.rect(schermo, (0, 255, 0), (inizio, dimensioni_rettangolo), 1)
pygame.display.update()

Related

Python code problem displaying a polygon in PyGame using a polygon modeled in PyMunk

I am trying to model physics in pymunk and display using pygame and it works well so far with circles and a simple line. The problem I have is that I am trying to model a rectangle.
In pymunk that is a ploygon and I can create it using:-
def create_rect(space, pos):
body = pymunk.Body(1,100,body_type= pymunk.Body.DYNAMIC)
body.position = pos
poly_dims = [(100,10),(200,10),(200,15),(100,15)]
shape = pymunk.Poly(body,poly_dims)
space.add(body,shape)
return shape
I can get the position of the body which starts off falling and print using pygame with:-
pos_x = int(board.body.position.x)
pos_y = int(board.body.position.y)
pygame.draw.circle (screen,(0,0,0),(pos_x,pos_y),10)
But I cannot seem to obtain the moving co-ordinates of the Polygon edges to then print using pygame.
v = board.get_vertices()
print(v)
pygame.draw.polygon(screen,(0,0,0),v,1)
Vertices
[Vec2d(100.0, 10.0), Vec2d(200.0, 10.0), Vec2d(200.0, 15.0), Vec2d(100.0, 15.0)]
Which looks good, however the co-ordinates never change.
Then I tried:-
pos = board.body.position
print('Position')
print(pos)
which outputs
Position
Vec2d(530.5760282186911, 545.8604346347887)
And the position does change, but you cannot print the polygon with a 2D vector, you need all the vertices to do it. And presumably when the polygon hits a surface it will rotate etc. I just want to model a rectangle and I am stuck on this one point !
If you need the vertices in world coordinates then you have to transform the vertices. Follow the example in the Pymunk documentation (see get_vertices()) and write a function that draws a polygon from the transformed vertices:
def drawShape(surf, color, shape):
pts = []
for v in shape.get_vertices():
x, y = v.rotated(shape.body.angle) + shape.body.position
pts.append((round(x), round(surf.get_width() - y)))
pygame.draw.polygon(surf, color, pts, True)
Minimal example
import pygame, math
import pymunk
pygame.init()
screen = pygame.display.set_mode((200, 200))
def create_rect(space, pos, angle):
body = pymunk.Body(1, 100, body_type= pymunk.Body.DYNAMIC)
body.position = pos
body.angle = angle
poly_dims = [(-50, 0), (50, 0), (50, 5), (-50, 5)]
shape = pymunk.Poly(body,poly_dims)
space.add(body,shape)
return shape
def drawShape(surf, color, shape):
pts = []
for v in shape.get_vertices():
x, y = v.rotated(shape.body.angle) + shape.body.position
pts.append((round(x), round(surf.get_width() - y)))
pygame.draw.polygon(surf, color, pts, True)
space = pymunk.Space()
space.gravity = (0, 0)
board = create_rect(space, (100, 100), math.pi / 4)
clock = pygame.time.Clock()
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
screen.fill((255, 255, 255))
drawShape(screen, (255, 0, 0), board)
pygame.display.flip()
pygame.quit()
Alternatively you can use the pymunk.pygame_util Module. Minimal example:
import pygame, math
import pymunk
import pymunk.pygame_util
pygame.init()
screen = pygame.display.set_mode((200, 200))
draw_options = pymunk.pygame_util.DrawOptions(screen)
def create_rect(space, pos, angle):
body = pymunk.Body(1, 100, body_type= pymunk.Body.DYNAMIC)
body.position = pos
body.angle = angle
poly_dims = [(-50, 0), (50, 0), (50, 5), (-50, 5)]
shape = pymunk.Poly(body,poly_dims)
space.add(body,shape)
return shape
space = pymunk.Space()
space.gravity = (0, 0)
board = create_rect(space, (100, 100), math.pi / 4)
clock = pygame.time.Clock()
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
screen.fill((255, 255, 255))
space.debug_draw(draw_options)
pygame.display.flip()
pygame.quit()

What is problem in my texture rendering function and how I solve it?

I tried to make a 2D texture rendering function in pygame. But it doesn't work. Here is function:
def drawTexture(screen,texture,rect):
last_texture_x, last_texture_y = rect[2],rect[3]
for texture_count_y in range(0,int(rect[3]/texture.get_height())+1):
for texture_count_x in range(0,int(rect[2]/texture.get_width())+1):
screen.blit(texture.subsurface(0,0,min(last_texture_x,texture.get_width()),min(last_texture_y,texture.get_height())),(rect[0]+texture.get_width()*texture_count_x,rect[1]+texture.get_height()*texture_count_y))
last_texture_x -= texture.get_width()
last_texture_y -= texture.get_height()
This function fills a rect with a texture. It fills width well. But it don't fill height when rect height is bigger than texture height. I think problem is texture_count_y variable. When I replace the variable to a number manually, the function works. But variable returns right value when I print it. My head really confused right now. How can I make the function works well?
EDIT:
Red lines mean rect.
Yellow lines mean texture images function uses to fill rect.
It fills columns well but it fills one row.
The problem is simple. last_texture_x must be initialized before the inner loop:
def drawTexture(screen, texture, rect):
last_texture_y = rect[3]
for texture_count_y in range(0,int(rect[3]/texture.get_height())+1):
last_texture_x = rect[2]
for texture_count_x in range(0,int(rect[2]/texture.get_width())+1):
screen.blit(texture.subsurface(0,0,min(last_texture_x,texture.get_width()),min(last_texture_y,texture.get_height())),(rect[0]+texture.get_width()*texture_count_x,rect[1]+texture.get_height()*texture_count_y))
last_texture_x -= texture.get_width()
last_texture_y -= texture.get_height()
However, I recommend to use the features of the pygame.Rect object for this task:
def drawTexture(screen, texture, rect):
target_rect = pygame.Rect(rect)
for x in range(target_rect.left, target_rect.right, texture.get_width()):
for y in range(target_rect.top, target_rect.bottom, texture.get_height()):
clip_rect = texture.get_rect(topleft = (x, y)).clip(target_rect)
screen.blit(texture.subsurface(0, 0, *clip_rect.size), (x, y))
Minimal example:
import pygame
pygame.init()
window = pygame.display.set_mode((500, 300))
clock = pygame.time.Clock()
def drawTexture(screen, texture, rect):
target_rect = pygame.Rect(rect)
for x in range(target_rect.left, target_rect.right, texture.get_width()):
for y in range(target_rect.top, target_rect.bottom, texture.get_height()):
clip_rect = texture.get_rect(topleft = (x, y)).clip(target_rect)
screen.blit(texture.subsurface(0, 0, *clip_rect.size), (x, y))
target_rect = pygame.Rect(25, 25, 450, 250)
texture = pygame.Surface((200, 200))
texture.fill((255, 255, 0))
pygame.draw.rect(texture, (127, 127, 127), (2, 2, 196, 196))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
pygame.draw.rect(window, (255, 255, 255), target_rect, 5)
drawTexture(window, texture, target_rect)
pygame.display.flip()
clock.tick(60)
pygame.quit()
exit()

How to make ball bounce off triangle in pygame?

Hello I'm a pretty new programmer and I'm trying to make a ball bounce off a 45 degree triangle. Here is my code:
This program makes the ball bounce when it hits the sides of the window, but I don't know how to make it bounce off a triangle.
import pygame # importing the pygame
import sys # importing the system libraries
import time # importing timer
import random
from pygame.locals import * # importing the locals functions from the pygame library set
pygame.init() # the function from pygame that initializes all relevant variable
# setting length and width
width = 500
length = 300
# colour variables
WHITE = (255,255,255)
BLUE = (0,0,255)
# importing ball image
ball = pygame.image.load('ball.png')
ballRect = ball.get_rect()
ballRect.left = 300
ballRect.right = 300
# setting speed
x_speed = 2
y_speed = 2
# setting window size
WINDOW = pygame.display.set_mode((width, length))# setting the size of the window
pygame.display.update()
# loop
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
ballRect = ballRect.move(x_speed,y_speed)
WINDOW.fill(WHITE) # changing screen colour to white
WINDOW.blit(ball,ballRect) # printing the ball to screen
pygame.display.update()
pygame.display.flip()
time.sleep(0.002) # to slow down the speed of bouncing
pygame.display.update()
# if the left side of ballRect is in a position less than 0, or the right side of ballRect is greater than 500
if ballRect.left < 0 or ballRect.right > (width):
x_speed = x_speed * -1
# if the top of ballRect is in a position less than 0, or the bottom of ballRect is greater than the length
elif ballRect.top < 0 or ballRect.bottom > (length):
y_speed = y_speed * -1
pygame.display.update()
I haven't drawn in the triangle because I don't know where to, but I expect the ball to bounce off the triangle like it does when it hits the sides of the window. Any help would be great!
Interesting task. A triangle can be defined by a simple list:
triangle = [(250, 220), (400, 300), (100, 300)]
The triangle can be drawn by pygame.draw.polygon()
pygame.draw.polygon(WINDOW, RED, triangle, 0)
Use pygame.math.Vector2 to define the position and the motion vector of the ball:
ballvec = pygame.math.Vector2(1, 1)
ballpos = pygame.math.Vector2(150, 250)
balldiameter = 64
Create a function, which does the collision detection. The function has to detect if the ball hits a line. If the line is hit, then the motion vector of the ball is reflected on the line.
The line is represented by 2 points (lp0, lp1), which are pygame.math.Vector2 objects.
The position of the ball (pt) and the motion vector (dir) are pygame.math.Vector2 objects, too:
def isect(lp0, lp1, pt, dir, radius):
# direction vector of the line
l_dir = (lp1 - lp0).normalize()
# normal vector to the line
nv = pygame.math.Vector2(-l_dir[1], l_dir[0])
# distance to line
d = (lp0-pt).dot(nv)
# intersection point on endless line
ptX = pt + nv * d
# test if the ball hits the line
if abs(d) > radius or dir.dot(ptX-pt) <= 0:
return dir
if (ptX-lp0).dot(l_dir) < 0 or (ptX-lp1).dot(l_dir) > 0:
return dir
# reflect the direction vector on the line (like a billiard ball)
r_dir = dir.reflect(nv)
return r_dir
Append the window rectangle and the triangle to a list of lines. Ech line is represented by a tuple of 2 pygame.math.Vector2 objects:
# add screen rect
screen_rect = [(0, 0), (0, 300), (500, 300), (500, 0)]
for i in range(len(screen_rect)):
p0, p1 = screen_rect[i], screen_rect[(i+1) % len(screen_rect)]
line_list.append((pygame.math.Vector2(p0[0], p0[1]), pygame.math.Vector2(p1[0], p1[1])))
# add red trianlge
triangle = [(250, 220), (400, 300), (100, 300)]
for i in range(len(triangle)):
p0, p1 = triangle[i], triangle[(i+1) % len(triangle)]
line_list.append((pygame.math.Vector2(p0[0], p0[1]), pygame.math.Vector2(p1[0], p1[1])))
Do the collision detection in a loop, which traverse the lines. If the ball hits a line, then the motion vector is replaced by the reflected motion vector:
for line in line_list:
ballvec = isect(*line, ballpos, ballvec, balldiameter/2)
Finally update the position of the ball an the ball rectangle:
ballpos = ballpos + ballvec
ballRect.x, ballRect.y = ballpos[0]-ballRect.width/2, ballpos[1]-ballRect.height/2
See the example code, where I applied the suggested changes to your original code. My ball image has a size of 64x64. The ball diameter has to be set to this size (balldiameter = 64):
Minimal example
import pygame
pygame.init()
window = pygame.display.set_mode((500, 300))
try:
ball = pygame.image.load("Ball64.png")
except:
ball = pygame.Surface((64, 64), pygame.SRCALPHA)
pygame.draw.circle(ball, (255, 255, 0), (32, 32), 32)
ballvec = pygame.math.Vector2(1.5, 1.5)
ballpos = pygame.math.Vector2(150, 250)
balldiameter = ball.get_width()
def reflect_circle_on_line(lp0, lp1, pt, dir, radius):
l_dir = (lp1 - lp0).normalize() # direction vector of the line
nv = pygame.math.Vector2(-l_dir[1], l_dir[0]) # normal vector to the line
d = (lp0-pt).dot(nv) # distance to line
ptX = pt + nv * d # intersection point on endless line
if (abs(d) > radius or dir.dot(ptX-pt) <= 0 or # test if the ball hits the line
(ptX-lp0).dot(l_dir) < 0 or (ptX-lp1).dot(l_dir) > 0):
return dir
r_dir = dir.reflect(nv) # reflect the direction vector on the line (like a billiard ball)
return r_dir
triangle1 = [(250, 220), (400, 300), (100, 300)]
triangle2 = [(250, 80), (400, 0), (100, 0)]
screen_rect = [(0, 0), (0, window.get_height()), window.get_size(), (window.get_width(), 0)]
line_list = []
for p0, p1 in zip(triangle1, triangle1[1:] + triangle1[:1]):
line_list.append((pygame.math.Vector2(p0), pygame.math.Vector2(p1)))
for p0, p1 in zip(triangle2, triangle2[1:] + triangle2[:1]):
line_list.append((pygame.math.Vector2(p0), pygame.math.Vector2(p1)))
for p0, p1 in zip(screen_rect, screen_rect[1:] + screen_rect[:1]):
line_list.append((pygame.math.Vector2(p0), pygame.math.Vector2(p1)))
clock = pygame.time.Clock()
run = True
while run:
clock.tick(250)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for line in line_list:
ballvec = reflect_circle_on_line(*line, ballpos, ballvec, balldiameter/2)
ballpos = ballpos + ballvec
window.fill((64, 64, 64))
pygame.draw.polygon(window, (255, 0, 0), triangle1, 0)
pygame.draw.polygon(window, (0, 0, 255), triangle2, 0)
window.blit(ball, (round(ballpos[0]-balldiameter/2), round(ballpos[1]-balldiameter/2)))
pygame.display.flip()
pygame.quit()

How do I change the color of a single rectangle in a grid?

I've already programmed a grid, but now I want to change the color of a single rectangle in the grid.
x = 5
y = 5
height = 30
width = 50
size = 20
color = (255,255,255)
new_color = (255,255,0)
screen.fill((0,0,0))
def draw_grid():
for y in range(height):
for x in range(width):
rect = pygame.Rect(x * (size + 1),y * (size + 1),size,size)
pygame.draw.rect(screen,color,rect)
x += 20
rects.append((rect,color))
y += 20
rects = []
colored_rects = []
while 1:
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
draw_grid()
if pygame.mouse.get_pressed()[0]:
mouse_pos = pygame.mouse.get_pos()
for i,(rect,color) in enumerate(rects):
if rect.collidepoint(mouse_pos):
rects[i] = (rect,new_color)
colored_rects.append((rect,new_color))
for rect,color in rects:
pygame.draw.rect(screen,color,rect)
for rect,new_color in colored_rects:
pygame.draw.rect(screen,new_color,rect)
pygame.display.flip()
clock.tick()
Now I only want to change one rectangle when I click on it, but later they must change automatically (for example when there are three rectangles touching in the same color, they all must become white). I've updated a little bit, but there are still some problems. For example: You have to click on the rectangle till it changes color, and it takes to much time te change color.
One solution would be to store the rects together with their color in tuples. If the mouse button is pressed, you iterate over the rectangles list, if a rectangle collides with the mouse, you create a tuple with the rect and the new color and replace the tuple at the current index.
import sys
import pygame as pg
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
height = 30
width = 50
size = 20
color = (255, 255, 255)
new_color = (255, 255, 0)
rectangles = []
for y in range(height):
for x in range(width):
rect = pg.Rect(x * (size+1), y * (size+1), size, size)
# The grid will be a list of (rect, color) tuples.
rectangles.append((rect, color))
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
if pg.mouse.get_pressed()[0]:
mouse_pos = pg.mouse.get_pos()
# Enumerate creates tuples of a number (the index)
# and the rect-color tuple, so it looks like:
# (0, (<rect(0, 0, 20, 20)>, (255, 255, 255)))
# You can unpack them directly in the head of the loop.
for index, (rect, color) in enumerate(rectangles):
if rect.collidepoint(mouse_pos):
# Create a tuple with the new color and assign it.
rectangles[index] = (rect, new_color)
screen.fill((30, 30, 30))
# Now draw the rects. You can unpack the tuples
# again directly in the head of the for loop.
for rect, color in rectangles:
pg.draw.rect(screen, color, rect)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()

PyGame/Python: Placing a circle onto an ellipse

I am trying to place multiple circles onto an eclipse and be able to move that circle around the eclipse. From looking into PyGames examples I have seen that you can rotate a line around an eclipse however cannot figure out how to do with with a circle.
This is the error message I recieve upon trying:
Traceback (most recent call last):
File "C:/Python32/Attempts/simple_graphics_demo.py", line 66, in <module>
pygame.draw.circle(screen, BLUE, [x, y], 15, 3)
TypeError: integer argument expected, got float
.
import pygame
import math
# Initialize the game engine
pygame.init()
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
PI = 3.141592653
# Set the height and width of the screen
size = [400, 400]
screen = pygame.display.set_mode(size)
my_clock = pygame.time.Clock()
# Loop until the user clicks the close button.
done = False
angle = 0
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# Set the screen background
screen.fill(WHITE)
# Dimensions of radar sweep
# Start with the top left at 20,20
# Width/height of 250
box_dimensions = [20, 20, 250, 250]
# Draw the outline of a circle to 'sweep' the line around
pygame.draw.ellipse(screen, GREEN, box_dimensions, 2)
# Draw a black box around the circle
pygame.draw.rect(screen, BLACK, box_dimensions, 2)
# Calculate the x,y for the end point of our 'sweep' based on
# the current angle
x = 125 * math.sin(angle) + 145
y = 125 * math.cos(angle) + 145
# Draw the line from the center at 145, 145 to the calculated
# end spot
pygame.draw.line(screen, GREEN, [145, 145], [x, y], 2)
# Attempt to draw a circle on the radar
pygame.draw.circle(screen, BLUE, [x, y], 15, 3)
# Increase the angle by 0.03 radians
angle = angle + .03
# If we have done a full sweep, reset the angle to 0
if angle > 2 * PI:
angle = angle - 2 * PI
# Flip the display, wait out the clock tick
pygame.display.flip()
my_clock.tick(60)
# on exit.
pygame.quit()
The math.sin and math.cos functions return floats, and the pos keyword argument to pygame.draw.circle expects integer positions, so you'll want to actually cast your coordinates. You have a few options for doing this:
[int(x), int(y)]
[math.floor(x), math.floor(y)]
[math.ceil(x), math.ceil(y)]
Each comes with slightly different behaviours so you might want to figure out which fits your program best. (specifically: int and floor work differently for negative numbers -- int rounds towards 0 and floor rounds down, as expected)
It is not answer for your main question - because you already got answer.
To put more circles use list with angles and for loop to get angle from list (one-by-one) and draw circle.
import pygame
import math
# === CONSTANTS ===
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
SIZE = (400, 400)
TWO_PI = 2 * math.pi # you don't have to calculate it in loop
# === MAIN ===
# --- init ---
pygame.init()
screen = pygame.display.set_mode(SIZE)
# --- objects ---
angles = [0, 1, math.pi] # angles for many circles
box_dimensions = [20, 20, 250, 250] # create only once
# --- mainloop ---
clock = pygame.time.Clock()
done = False
while not done:
# --- events ---
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
done = True
# --- draws (without updates) ---
screen.fill(WHITE)
pygame.draw.ellipse(screen, GREEN, box_dimensions, 2)
pygame.draw.rect(screen, BLACK, box_dimensions, 2)
# draw many circles
for a in angles:
x = int(125 * math.sin(a)) + 145
y = int(125 * math.cos(a)) + 145
pygame.draw.line(screen, GREEN, [145, 145], [x, y], 2)
pygame.draw.circle(screen, BLUE, [x, y], 15, 3)
pygame.display.flip()
clock.tick(60)
# --- updates (without draws) ---
# new values for many angles
for i, a in enumerate(angles):
a += .03
if a > TWO_PI:
a -= TWO_PI
angles[i] = a
# --- the end ---
pygame.quit()

Categories

Resources