I am experimenting with a Pathfinding project and got stuck while creating a working GUI. I'm using pygame and already created a grid and a feature, which draws cubes when you press (or keep pressing) the mouse-button. However, these cubes just go wherever you click, and do not snap to the grid. I thought about using modulo somehow but I cannot seem to get it to work. Please find the code attached below. The Cube class is what I use for the squares drawn on the screen. Moreover, the drawgrid() function is how I set up my grid. I'd love some help on this, as I've been stuck on this roadblock for three days now.
class Cube:
def update(self):
self.cx, self.cy = pygame.mouse.get_pos()
self.square = pygame.Rect(self.cx, self.cy, 20, 20)
def draw(self):
click = pygame.mouse.get_pressed()
if click[0]: # evaluate left button
pygame.draw.rect(screen, (255, 255, 255), self.square)
Other drawgrid() function:
def drawgrid(w, rows, surface):
sizebtwn = w // rows # Distance between Lines
x = 0
y = 0
for i in range(rows):
x = x + sizebtwn
y = y + sizebtwn
pygame.draw.line(surface, (255, 255, 255), (x, 0), (x, w))
pygame.draw.line(surface, (255, 255, 255), (0, y), (w, y))
You have to align the position to the grids size. Use the floor division operator (//) to divide the coordinates by the size of a cell and compute the integral index in the grid:
x, y = pygame.mouse.get_pos()
ix = x // sizebtwn
iy = y // sizebtwn
Multiply the result by the size of a cell to compute the coordinate:
self.cx, self.cy = ix * sizebtwn, iy * sizebtwn
Minimal example:
import pygame
pygame.init()
screen = pygame.display.set_mode((200, 200))
clock = pygame.time.Clock()
def drawgrid(w, rows, surface):
sizebtwn = w // rows
for i in range(0, w, sizebtwn):
x, y = i, i
pygame.draw.line(surface, (255, 255, 255), (x, 0), (x, w))
pygame.draw.line(surface, (255, 255, 255), (0, y), (w, y))
class Cube:
def update(self, sizebtwn):
x, y = pygame.mouse.get_pos()
ix = x // sizebtwn
iy = y // sizebtwn
self.cx, self.cy = ix * sizebtwn, iy * sizebtwn
self.square = pygame.Rect(self.cx, self.cy, sizebtwn, sizebtwn)
def draw(self, surface):
click = pygame.mouse.get_pressed()
if click[0]:
pygame.draw.rect(surface, (255, 255, 255), self.square)
cube = Cube()
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
cube.update(screen.get_width() // 10)
screen.fill(0)
drawgrid(screen.get_width(), 10, screen)
cube.draw(screen)
pygame.display.flip()
Related
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()
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()
If a sprite is in the centre of a circle at the point 250,250 in pygame what is the equation to find the edge of the circle in any direction relative to the original point. Could the equation have the angle (as X) in the equation?
The general formula is (x, y) = (cx + r * cos(a), cy + r * sin(a)).
However, in your case ° is at the top and the angle increases clockwise. Therefore the formula is:
angle_rad = math.radians(angle)
pt_x = cpt[0] + radius * math.sin(angle_rad)
pt_y = cpt[1] - radius * math.cos(angle_rad)
Alternatively, you can use the pygame.math module and pygame.math.Vector2.rotate:
vec = pygame.math.Vector2(0, -radius).rotate(angle)
pt_x, pt_y = cpt[0] + vec.x, cpt[1] + vec.y
Minimal example:
import pygame
import math
pygame.init()
window = pygame.display.set_mode((500, 500))
font = pygame.font.SysFont(None, 40)
clock = pygame.time.Clock()
cpt = window.get_rect().center
angle = 0
radius = 100
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# solution 1
#angle_rad = math.radians(angle)
#pt_x = cpt[0] + radius * math.sin(angle_rad)
#pt_y = cpt[1] - radius * math.cos(angle_rad)
# solution 2
vec = pygame.math.Vector2(0, -radius).rotate(angle)
pt_x, pt_y = cpt[0] + vec.x, cpt[1] + vec.y
angle += 1
if angle >= 360:
angle = 0
window.fill((255, 255, 255))
pygame.draw.circle(window, (0, 0, 0), cpt, radius, 2)
pygame.draw.line(window, (0, 0, 255), cpt, (pt_x, pt_y), 2)
pygame.draw.line(window, (0, 0, 255), cpt, (cpt[0], cpt[1]-radius), 2)
text = font.render(str(angle), True, (255, 0, 0))
window.blit(text, text.get_rect(center = cpt))
pygame.display.flip()
pygame.quit()
exit()
In this program when the user types more text I want the rectangle to automatically get longer when the user types to keep the letters inside of the rectangle. However, it doesn't update the rectangle when the text gets longer. How do I fix this?
from pygame import *
init()
screen = display.set_mode((800, 600))
name_font = font.Font(None, 32)
name_text = ''
class Rectangle:
def __init__(self, x, y):
self.x = x
self.y = y
self.input_rect = Rect(x, y, 140, 32)
self.text_surface = name_font.render(name_text, True, (255, 255, 255))
color = Color('lightskyblue3')
draw.rect(screen, color, self.input_rect, 2)
self.input_rect.w = self.text_surface.get_width() + 10
screen.blit(self.text_surface, (self.input_rect.x + 5, self.input_rect.y + 5))
def naming():
global name_text
if events.type == KEYDOWN:
if keys[K_BACKSPACE]:
name_text = name_text[:-1]
screen.fill((0, 0, 0))
rect_1 = Rectangle(200, 200)
else:
name_text += events.unicode
while True:
rect_1 = Rectangle(200, 200)
for events in event.get():
keys = key.get_pressed()
naming()
if events.type == QUIT:
quit()
display.update()
time.delay(1)
The Rectangle.text_surface is a PyGame Surface. So you can easily get the precise width of the bounding box by simply calling self.text_surface.get_width().
But you start the size of the border-rect at 140, so this size has to be the maximum of 140 or whatever the new (longer) width is. Another problem is that when the rectangle re-sizes, the old rectangle is left behind. So whenever we now re-draw the rectangle, it erases the background to black.
This is all pretty easily encapsulated into the exiting __init__():
def __init__(self, x, y):
self.x = x
self.y = y
self.text_surface = name_font.render(name_text, True, (255, 255, 255))
rect_width = max( 140, 10 + self.text_surface.get_width() ) # Adjust the width
color = Color('lightskyblue3')
self.input_rect = Rect(x, y, rect_width, 32) # Use new width (if any)
draw.rect(screen, (0,0,0) , self.input_rect, 0) # Erase any existing rect
draw.rect(screen, color, self.input_rect, 2)
self.input_rect.w = self.text_surface.get_width() + 10
screen.blit(self.text_surface, (self.input_rect.x + 5, self.input_rect.y + 5))
I am making an agario clone for a project and I was wondering what the quickest way to draw many dots in pygame.
from pygame import *
import random as rd
x = rd.randint(100, 700)
y = rd.randint(100, 500)
# I would like to draw about 50 dots of this type.
dot = draw.circle(screen, (0, 0, 0), (x, y), 5)
Here's some basic code to draw circles using pygame:
import pygame as pg
import random as rd
pg.init() # initialize pygame
screen = pg.display.set_mode((500,500)) # create main screen
for ctr in range(25): # 25 circles
x = rd.randint(50, 450)
y = rd.randint(50, 450)
# I would like to draw about 50 dots of this type.
dot = pg.draw.circle(screen, (100, 200, 100), (x, y), 15)
pg.display.update() # update screen
while True: # main pygame loop, always include this
for event in pg.event.get(): # required for OS events
if event.type == pg.QUIT: # user closed window
pg.quit()
Output
Any easy way to do this to use a simple python "for" loop with a "range":
As the loop iterates, it executes the content of the loop body, with the variable i incrementing from 0 to N-1 (i.e. 49). The variable i could be any valid variable name, but for simple numbered loops, i, j & k are commonly used.
from pygame import *
import random as rd
# Draw 50 dots
for i in range( 0, 50 ):
x = rd.randint(100, 700)
y = rd.randint(100, 500)
dot = draw.circle(screen, (0, 0, 0), (x, y), 5)
I made a class for dots:
class Dot():
SIZE = 5
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
draw.circle(screen, self.color, (self.x, self.y), Dot.SIZE)
Then I made an array and generate NUMBER_OF_DOTS like this:
dots = []
for i in range(NUMBER_OF_DOTS):
x = rd.randint(100, 700)
y = rd.randint(100, 500)
dots.append(Dot(x,y))
and in the while loop and redraw after filling the whole scene by white color:
while True:
screen.fill((255, 255, 255))
...
for dot in dots:
dot.draw()
The whole source:
from pygame import *
import random as rd
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
NUMBER_OF_DOTS = 300
class Dot():
SIZE = 5
def __init__(self, x, y):
self.x = x
self.y = y
self.color = random_color()
def draw(self):
draw.circle(screen, self.color, (self.x, self.y), Dot.SIZE)
def random_color():
r = rd.randint(0, 255)
g = rd.randint(0, 255)
b = rd.randint(0, 255)
return (r, g, b)
init()
screen = display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
dots = []
# generate random dots all over the screen
for i in range(NUMBER_OF_DOTS):
x = rd.randint(100, 700)
y = rd.randint(100, 500)
dots.append(Dot(x,y))
# main while loop
while True:
screen.fill((255, 255, 255))
for dot in dots:
dot.draw()
display.update()
time.delay(1) # Speed down
If you want to draw the same circles continuously in the main application loop, then you've to generate a list of random positions:
import pygame as pg
import random as rd
pg.init()
screen = pg.display.set_mode((800,600))
cpts = []
for i in range(25):
x = rd.randint(100, 700)
y = rd.randint(100, 500)
cpts.append((x, y))
run = True
while run:
for event in pg.event.get():
if event.type == pg.QUIT:
run = False
for cpt in cpts:
pg.draw.circle(screen, (255, 255, 255), cpt, 15)
pg.display.update()
See the answer to Pygame unresponsive display for a minimum pygame application.