Related
This question already has answers here:
Pygame Drawing a Rectangle
(6 answers)
Draw a transparent rectangles and polygons in pygame
(4 answers)
Closed 2 years ago.
I am trying to draw a screen with a filled in box in the center. I am using pygame.Surface.fill() for the box but it gives this error: TypeError: descriptor 'fill' for 'pygame.Surface' objects doesn't apply to a 'tuple' object Here is my code:
my_color = (0, 0, 0)
my_rect = (100, 100, 200, 150)
box = pygame.Surface.fill(my_color, my_rect)
surf.blit(box)
You just need to do it in two steps. First create the surface, then fill it. Creating a surface makes a separate image, so it takes a size not a rectangle.
my_color = (0, 0, 0)
my_rect = (100, 100, 200, 150)
my_size = ( 200, 150 )
box = pygame.Surface( my_size )
box.fill( my_colour )
surf.blit( box, my_rect )
If you just want to draw a rectangle, you can use the drawing functions, which use the base surface directly:
pygame.draw.rect( surf, my_color, my_rect )
I'm using Python 3.7.4 and Pygame 1.9.6. I know about the pygame.draw.arc and pygame.draw.circle. I want to draw a quarter circle like here:
It's the quarter circle of the soccer field.
My attempts:
pygame.draw.arc(screen, (255, 255, 255), (75, 25, 50, 50), 90, 120, 5)
pygame.draw.arc(screen, (255, 255, 255), (75, 25, 50, 50), 80, 100, 5)
Changing the starting angle and ending angle doesn't change a thing it seems.
So it must be the coordinates of drawing the boundaries.
When I change the 75 in this(75, 25, 50, 50) it just makes the circle go to the right more. If the change the 25 in (75, 25, 50, 50) it makes it go up or down more. If I change the first 50 in (75, 25, 50, 50) it makes it wider width bigger or smaller depending on changing the numbers. If I change the second 50 in (75, 25, 50, 50) it makes the height bigger or smaller.
I've just tried decimals in the angles, because the coordinates have to be integers. When I do pygame.draw.arc(screen, (255, 255, 255), (75, 25, 100, 50), 95.5, 100.5, 5) look at the decimals.
I get:
So maybe the key it changing the starting and ending angles I'll keep updating what I find.
I found the right one with help from #bashBedlam in the comments:
pygame.draw.arc(screen, (255, 255, 255), (60, 13, 50, 50), 17, 13, 5)
I changed the coordinates but keep the same starting and ending angle.
Which is pretty much perfect though I do hate the little black dots though.
The screen is 600 x 600
I can't get to right angle or the circle itself. I'm not aloud to use decimals. Only allows integers. So I don't know how to properly make a quarter circle. I appreciate the advice and thanks.
I just wanted to say for people helping me I've created my own soccer field. I drew all the lines out, but then I just turned it into a png so it wouldn't lag me out of loading all 27 lines with the background constantly. Here is the finished result with everyone's help:
The issue with your code is that pygame.draw.arc takes angles in radians as stated in the documentation. Also angles between 90 and 120 would not draw and arc like you need, 90 to -90 will.
import pygame
import math
pygame.init()
d = pygame.display.set_mode((1200, 600))
while True:
pygame.event.get()
d.fill((255, 255, 255))
pygame.draw.arc(d, (0, 0, 0), [900, 300, 100, 100], math.radians(90), math.radians(-90), 5)
pygame.draw.arc(d, (0, 0, 0), [300, 300, 100, 100], math.radians(-90), math.radians(90), 5)
pygame.display.update()
Edit:
So i was looking online for ways for pygame.draw.arc to draw arc without dots without using floating point numbers, but couldn't any, so i wrote this function for you. If you consider this function as not being a part of the code you wrote (like pygame.draw.arc) then technically, you are not using decimal because the decimal is only used inside of the function(finessed the system :)). Here's the function:
def drawArc(display, startAngle, endAngle, distance, pos, color, thickness=1):
if startAngle > endAngle:
theta = endAngle
bigger = startAngle
else:
theta = startAngle
bigger = endAngle
while theta < bigger:
for t in range(thickness):
x = round((cos(radians(theta)) * (distance-t)) + pos[0])
y = round((-sin(radians(theta)) * (distance-t)) + pos[1])
display.set_at((x, y), color)
theta += 0.01
Think of this function like a compass, it draws an arc in-between the angle you specify (in degrees). Argument pos being the centre of the compass and distance being the distance of the arc from the centre. So now just draw the quarter circles in 4 different corners.
import pygame
from math import radians, sin, cos
pygame.init()
d = pygame.display.set_mode((1200, 600))
def drawArc(display, startAngle, endAngle, distance, pos, color, thickness=1):
if startAngle > endAngle:
theta = endAngle
bigger = startAngle
else:
theta = startAngle
bigger = endAngle
while theta < bigger:
for t in range(thickness):
x = round((cos(radians(theta)) * (distance-t)) + pos[0])
y = round((-sin(radians(theta)) * (distance-t)) + pos[1])
display.set_at((x, y), color)
theta += 0.01
while True:
pygame.event.get()
d.fill((255, 255, 255))
drawArc(d, -90, 0, 100, [0, 0], (0, 0, 0), thickness=5)
drawArc(d, 180, 270, 100, [1200, 0], (0, 0, 0), thickness=5)
drawArc(d, 0, 90, 100, [0, 600], (0, 0, 0), thickness=5)
drawArc(d, 180, 90, 100, [1200, 600], (0, 0, 0), thickness=5)
pygame.display.update()
Btw, using this function hinders performance, so i strongly recommend using pygame.draw.arc with floating point numbers if possible.
This question already has an answer here:
Pygame is running slow
(1 answer)
Closed 2 years ago.
I am creating a 3d pong game using pygame. I wanted to add a thick black border layer to the rectangle to make it more stylish. Here's what I tried:
pygame.draw.rect(screen, (0,0,255), (x,y,150,150), 0)
pygame.draw.rect(screen, (0,0,0), (x-1,y-1,155,155), 1)
pygame.draw.rect(screen, (0,0,0), (x-2,y-2,155,155), 1)
pygame.draw.rect(screen, (0,0,0), (x-3,y-3,155,155), 1)
pygame.draw.rect(screen, (0,0,0), (x-4,y-4,155,155), 1)
It worked but as the game I am trying to create is a 3d game this method was time consuming. Please tell me if there is any inbuilt method in pygame to draw borders around a rectangle. Sorry for my English.
You could also put it in a function, and make it more concise with for loops. First, you'll note that the four rectangles you drew were in a nice, easy pattern, so you could compact the drawing of the four rectangles like this:
pygame.draw.rect(surface, (0,0,255), (x,y,150,150), 0)
for i in range(4):
pygame.draw.rect(surface, (0,0,0), (x-i,y-i,155,155), 1)
Then, because pygame draw functions do not need to be run in the global scope, you can put all this into a function:
def drawStyleRect(surface):
pygame.draw.rect(surface, (0,0,255), (x,y,150,150), 0)
for i in range(4):
pygame.draw.rect(surface, (0,0,0), (x-i,y-i,155,155), 1)
Then, in your mainloop, all you have to do is run:
while not done:
...
drawStyleRect(screen) # Or whatever you named the returned surface of 'pygame.display.set_mode()'
...
You could even put the drawing function in a separate module, if you really wanted to.
Create a function that creates a pygame.Surface object with per pixel alpha (SRCALPHA) and draw the rectangle and the border on the surface:
def create_rect(width, height, border, color, border_color):
surf = pygame.Surface((width+border*2, height+border*2), pygame.SRCALPHA)
pygame.draw.rect(surf, color, (border, border, width, height), 0)
for i in range(1, border):
pygame.draw.rect(surf, border_color, (border-i, border-i, width+5, height+5), 1)
return surf
Create all the surfaces before the main application loop and blit them in the loop:
rect_surf1 = create_rect(150, 150, 5, (0, 0, 255), (0, 0, 0))
# [...]
run = True
while run:
# [...]
screen.blit(rect_surf1, (x, y))
# [...]
I've recently tried teaching myself Python and I've made a app using Pygame to generate random stats for D and D enemies. 2 Questions. What would be a cleaner or better solution be for updating the Pygame display after clicking a button? Because currently I have the loop redefining the main window variable after every click in an attempt to get the stats to stay on the display.
if zombie.isOver(pos):
win.fill((255, 255, 255))
menu.draw(win, (0, 0, 0))
Zombie()
pygame.display.update()
x -= 1
def drawWindow():
again.draw(win, (0, 0, 0))
After the button is pressed this gets redefined.
def drawWindow():
win.fill((255, 255, 255))
bandit.draw(win, (0, 0, 0))
zombie.draw(win, (0, 0, 0))
rat.draw(win, (0, 0, 0))
I did this because the win.fill would cover up the values printed and I couldn't get them to stay any other way.
2nd question is when I mash the buttons in the display, 2 out of the 3 current ones work fine, however the 3rd button's values stutter and it is not smooth like the other 2. I think this is being caused by the location of the values in the code, they are the last ones I added putting them further down in the code, but honestly I have no idea.
if rat.isOver(pos):
win.fill((255, 255, 255))
menu.draw(win, (0, 0, 0))
Rat()
pygame.display.update()
x /= 2
def drawWindow():
again.draw(win, (0, 0, 0))
if again.isOver(pos) and x == 5:
win.fill((255, 255, 255))
menu.draw(win, (0, 0, 0))
again.draw(win, (0, 0, 0))
Rat()
pygame.display.update()
def drawWindow():
again.draw(win, (0, 0, 0))
The button works the same as the other 2 but the values stutter. If anyone has an idea that would help it would be greatly appreciated. Thank You.
Ok, not sure if i completely understand how you are doing it but on way to do it is in the main loop, call drawWindow(). Which is this:
def drawWindow():
win.fill((255, 255, 255))
bandit.draw(win, (0, 0, 0))
zombie.draw(win, (0, 0, 0))
rat.draw(win, (0, 0, 0))
I'm assuming that that is drawing images of them and not the stats. have a bool that controls whether to show the stats like show_zombie_stats for each of them. Then in the main loop or drawWindow(), you can do an if statement like
if show_zombie_stats:
zombie.draw_stats()
then call pygame.display.update() at the end of the main loop.
Now you can change the show_zombie stats with the button with show_zombie stats = not show_zombie stats
I am new to dirty rect animation and I am currently trying to store a snapshot of the main display surface window, however I would only like to store the area where my item is going to be blit so the next frame I can call this stored snapshot instead of re blitting the whole background.
I looked at the documentation for Surface.copy() but it doesn't take arguments and I couldn't find anything similar other than pygame.pixelcopy() which from what I understand is not what I am looking for. If Surface.copy() isn't what I am looking for, please let me know of alternatives.
import pygame, time
pygame.init()
screen = pygame.display.set_mode((500, 500))
screen.fill((128, 128, 128))
pygame.display.update()
#immagine a complex pattern being blit to the screen here
pygame.draw.rect(screen, (128, 0, 0), (0, 0, 50, 50))
pygame.draw.rect(screen, (0, 128, 0), (50, 0, 50, 50))
pygame.draw.rect(screen, (0, 0, 128), (200, 0, 50, 50))
#my complex background area that i want to save ()
area_to_save = pygame.Rect(0, 0, 100, 50)
rest_of_background = pygame.Rect(200, 0, 50, 50)
#updating for demo purposes
dirty_rects = [area_to_save, rest_of_background]
for rect in dirty_rects:
pygame.display.update(rect)
temp_screen = screen.copy()
time.sleep(3)
#after some events happen and I draw the item thats being animated onto the background
item_to_animate = pygame.Rect(35, 10, 30, 30)
pygame.draw.rect(screen, (0, 0, 0), item_to_animate)
pygame.display.update(item_to_animate)
time.sleep(3)
item_to_animate = pygame.Rect(50, 60, 30, 30)
pygame.draw.rect(screen, (0, 0, 0), item_to_animate)
#now that the item has moved, draw back old frame, which draws over the whole surface
screen.blit(temp_screen, (0, 0))
pygame.display.update()
#I understand swapping the drawing of the new item location to after temp_surface blit
#will provide me the desired outcome in this scenario but this is a compressed version of my problem
#so for simplicity sake, is there a way of not saving the whole surface, only those rects defined?
I expect the output of this code to be displaying my background for 3 seconds, then the black square overlaying the patter and then after another 3 seconds, the black square appearing below my pattern.
P.S.:I am new to this site, let me know if I did something wrong please!
Edit: For anyone wondering if this solution (of saving the background before blitting an item over it and then redrawing the saved background before the new item location is blit over) is more efficient than redrawing the whole background and then blitting the item, using a simple square animation over a chequered pattern with redrawing the whole background each time reduced my overall fps by around 50% from 1000 (before redrawing the background) to 500 average. While using dirty rects and this method above I get around 900 fps.
What you want to do can be achieved by pygame.Surface.blit().
Create a surface with the decided size and blit an area of the screen to this surface. Note, the 3rd argument to bilt is an optional parameter which selects a rectangular area of the source surface:
# create a surface with size 'area_to_save.size'
temp_screen = pygame.Surface(area_to_save.size)
# blit the rectangular area 'area_to_save' from 'screen' to 'temp_screen' at (0, 0)
temp_screen.blit(screen, (0, 0), area_to_save)
You could use subsurface to specify the area you want to copy, and then call copy on the returned Surface.
But note that it may happend that this will not increase the performance of your game at all, since copying a lot of Surfaces around does not come free. Just try and check yourself if it actually better then drawing a background surface every frame.
Also note that you should never use time.sleep in your game. While sleep is blocking your game's process, your game can't process events, so you e.g. can't quit the game in this time. Also, it may happen that your game's window will just not be redrawn if you don't process events by calling pygame.event.get; and once the event queue fills up because you never call pygame.event.get, your game will freeze.