Here is my full code:
import pygame
pygame.init()
i = 0
x = 0
# Define the colors we will use in RGB format
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
BLUE = ( 0, 0, 255)
GREEN = ( 0, 255, 0)
RED = (255, 0, 0)
# Set the height and width of the screen
size = [600, 300]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Test")
#Loop until the user clicks the close button.
done = False
clock = pygame.time.Clock()
while not done:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done=True
screen.fill(WHITE)
for x in range(x, x+100, 10):
pygame.draw.rect(screen, BLACK, [x, 0, 10, 10], 1)
pygame.display.flip()
pygame.quit()
10 squares are drawn but they scroll across the window to the right and i dont know why. Is there any way I could stop this?
Thanks.
I realise now that it is not about the rectangle loop, but i have changed that to what has been suggested anyway.
Now that you have added more code I see where the problem is coming from and it's just as I suspected - you are doing a static translation the incorrect way namely you use the x (which you have defined outside your main loop) in your for x in range(x, x+100, 10):.
If you add a print(x) statement inside your for-loop you will be able to see that the x gets bigger and bigger, and bigger...This is perfect for adding dynamics to your scene but my guess is (based on your question) that you want to add 10 static rectangles to your scene.
In order to do that you need to reset your x every single time a new iteration of the while loop begins:
while not done:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done=True
screen.fill(WHITE)
# Every single iteration of the while loop will first reset the x to its initial value
# You can make x be any value you want your set of rectangles to start from
x = 0
# Now start adding up to x's initial value
for x in range(x, x+100, 10):
pygame.draw.rect(screen, BLACK, [x, 0, 10, 10], 1)
pygame.display.flip()
You can also omit the definition of x as a variable outside the for loop if you won't be using it to change the x coordinate where your first rectangle will start from and replace the range(x, x+100, 10) with range(CONST, CONST+100, 10) where CONST is a given value such as 0, 100, 1000 etc.
Every time your for loop runs the range is incremented, because the x is persistent. If you remove the x=0, reset it inside the while, or use a different variable in the for loop I think it will work.
x=0
while not done:
for x in range(x, x+100, 10):
pygame.draw.rect(screen, BLACK, [x, 0, 10, 10], 1)
Related
I have code to move a rectangle in pygame from left to right, up and down.
But I want to move my rectangle around the screen that I created..
can someone help me please?
import pygame
from pygame.locals import *
pygame.init()
FPS = 70
fpsclock = pygame.time.Clock()
SIZE = (1000, 700)
form1 = pygame.display.set_mode(SIZE)
WHITE=(255, 255, 255)
x = 0
y = 0
w = 50
h = 60
direction = 'right'
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
form1.fill(WHITE)
pygame.draw.rect(form1, (255, 0, 0), (x, y, w, h), 1)
pygame.display.update()
fpsclock.tick(FPS)
if x,y==0,0:
direction='right'
if x,y==1200-50,0:
direction='down'
if x,y==1200-50,700-60:
direction='left'
if x,y==0,1200-50:
direction='right'
So the first thing you have to look at is the spacing. Even though your code works (after proper indentation) the square goes out of bounds.
The same thing applies to y as well if the square should go up and down.
If you want the square to go around you just need to go left, right, up, or down at the correct time. So if you want to start at the left upper corner and go around you just need to check if the square is in a corner and then change the direction accordingly.
Keep in mind that going down actually increases and going up decreases y.
EDIT:
Here you can see the result of my proposed concept
EDIT 2:
I've copied your code and refactored and completed it. I tried to explain why I did what I did.
import pygame
# Only import the things you need it makes debugging and documenting
# alot easier if you know where everything is coming from
from pygame.locals import QUIT
FPS = 70
# Use variables to define width and height in case you want to
# change it later
width = 200
height = 200
# I would group variables by usage, for example, I would group width,
# height of the screen and w, h of the box so I can easily manipulate
# the numbers if want to.
w = 50
h = 60
# Using an offset variable reduces the hardcoded numbers even more
# if its 0 it will just move along the border but if it >0 it will move
# the square to the centre
offset = 20
# You can declare it as a variable if you need the SIZE tuple somewhere
# else, if you don't need it you can just set it as
# pygame.display.set_mode((width, height))
SIZE = (width, height)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
x = offset
y = offset
direction = 'right'
# This is just my preference but I like to have the variables that are
# changeable at the top for easy access. I think this way the code is
# cleaner.
pygame.init()
fpsclock = pygame.time.Clock()
form1 = pygame.display.set_mode(SIZE)
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
form1.fill(WHITE)
# Try to avoid hardcoded numbers as much as possible, hardcoded
# numbers are hard to change later on when the code gets to certain
# size and complexity.
pygame.draw.rect(form1, RED, (x, y, w, h), 1)
pygame.display.update()
fpsclock.tick(FPS)
# Don't harcode conditions, use variables so you can easily change
# them later
if x == offset and y == offset:
direction='right'
if x == width - w - offset and y == offset:
direction='down'
if x == width - w - offset and y == height - h - offset:
direction='left'
if x == offset and y == height - h - offset:
direction='up'
if direction == 'right':
x += 5
elif direction == 'down':
#Keep in mind going down actually means to increment y
y += 5
elif direction == 'left':
x -= 5
elif direction == 'up':
y -= 5
I'm trying to create a game in python where one can drag a textbox around the screen, but whenever it touches the borders of a circle around it, I want the loop to start over, but with a different text (by storing all text-strings in a list, but I'm not that far, yet). This is how far I have come:
import pygame
import ptext
pygame.init()
gameDisplay = pygame.display.set_mode((500, 500))
gameDisplay.fill((255,255,255))
x = 190
y = 230
a = 250
b = 250
text = "ExampleText 1."
def textbox(x,y):
ptext.draw(text, (x,y), color = (0,0,0))
def circle(a,b):
pygame.draw.circle(gameDisplay, (0,0,0), (250, 250), 210, 5)
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.MOUSEMOTION:
if event.buttons[0]:
x += event.rel[0]
y += event.rel[1]
textbox(x,y)
circle(a,b)
pygame.display.flip()
pygame.quit()
quit()
Now I understand I will need to detect collision of the borders of my objects, but here I'm pretty lost. I tried to store the variables of my objects in rectangles and then produce another if statement that recognizes whether or not my objects collide (I used a print command because I haven't gotten to the actual command I want, yet), but that won't print anything and I'm sure I'm on the wrong path, but it is my best effort...
For that I have defined:
text_rect = pygame.Rect(x, y, 10, 30)
circle_rect = pygame.Rect(a,b, 300, 300)
and then in my loop:
if circle_rect.colliderect(text_rect):
print("COLLIDE")
Does anybody have any tip on a better way to define the objects and to create the function I want?
(Edit: Btw.: I'm not too concerned about the fact that when I drag my textbox, it leaves a print of the text, since that doesn't happen in my original script, but would be thankful if anyone knows why it does that in my current example.)
A rectangle has 4 corner points. If the rectangle is "smaller" then the circle (the diameter of the circle is greater than the diagonal of the rectangle), then the rectangle collides with the contour of a circle, if at least one point is out of the circle and at least one point is in the circle.
Define the rectangle and setup a list of the corner points. Further you've to know the radius of the circle:
w, h = 10, 30
rect = pygame.Rect(x, y, 10, 30)
corners = [rect.bottomleft, rect.bottomright, rect.topleft, rect.topright]
radius = 210
Calculate the Euclidean distance of each corner point to the center of the circle (a, b):
import math
dist = [math.sqrt((p[0]-a)**2 + (p[1]-b)**2) for p in corners]
Create to lists, one with the points in the circle (p_in) and one with the points out of the circle (p_out):
p_out = [i for i, d in enumerate(dist) if d > radius]
p_in = [i for i, d in enumerate(dist) if d < radius]
If both list contain any element, then the rectangle intersects the circle contour:
if any(p_in) and any(p_out):
print("COLLIDE")
If len(p_in) is 4, then the rectangle is completely in the circle. If len(p_out) is 4 then the rectangle is completely out of the circle.
if any(p_in) and any(p_out):
print("COLLIDE")
elif len(p_in) == 4:
print("IN")
elif len(p_out) == 4:
print("OUT")
See the simple example, which is based on your code snippet and demonstrates the collision test. The rectangle is attached to the mouse:
import pygame
import math
pygame.init()
gameDisplay = pygame.display.set_mode((500, 500))
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
x, y = pygame.mouse.get_pos()
w, h = 10, 30
rect = pygame.Rect(x, y, 10, 30)
a, b = 250, 250
radius = 210
corners = [rect.bottomleft, rect.bottomright, rect.topleft, rect.topright]
dist = [math.sqrt((p[0]-a)**2 + (p[1]-b)**2) for p in corners]
p_out = [i for i, d in enumerate(dist) if d > radius]
p_in = [i for i, d in enumerate(dist) if d < radius]
if any(p_in) and any(p_out):
print("COLLIDE")
elif len(p_in) == 4:
print("IN")
elif len(p_out) == 4:
print("OUT")
gameDisplay.fill((255,255,255))
pygame.draw.rect(gameDisplay, (255, 0, 0), rect)
pygame.draw.circle(gameDisplay, (0,0,0), (a, b), radius, 5)
pygame.display.flip()
pygame.quit()
quit()
I'm taking some baby steps with pygame, and I have gotten this program to work. However it throws a "List index out of range" message after looping through main() several hundred times. The list doesn't change size once it grows to a certain limit, so I am not sure where the error is happening.
Once you run the program, just move the mouse across the display, and a bunch of circles are drawn and grow as the clock ticks. Eventually, it will crash.
I have omitted all comments except the lines where the error is happening, hopefully that will make it easier to find out the cause.
Stacktrace:
Traceback (most recent call last):
File "C:\Users\Devo\AppData\Local\Programs\Python\Python37-32\test files\Psychedelic Circles.py", line 89, in <module>
main()
File "C:\Users\Devo\AppData\Local\Programs\Python\Python37-32\test files\Psychedelic Circles.py", line 49, in main
drawCircles(snakeLength)
File "C:\Users\Devo\AppData\Local\Programs\Python\Python37-32\test files\Psychedelic Circles.py", line 75, in drawCircles
pygame.draw.circle(SCREEN, colorList[i], coords[i], abs(i-(len(coords))) * 5, 0)#### Why does the list index go out of range?
IndexError: list index out of range
import pygame, sys, random
from pygame.locals import*
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
FUSCHIA = (255, 0, 240)
GRAY = (80, 80, 80)
YELLOW = (255, 255, 0)
ORANGE = (255, 127, 0)
BLUE = (0, 0, 255)
INDIGO = (75, 0, 130)
VIOLET = (148, 0, 211)
FPS = 20
WWIDTH = 1000
WHEIGHT = 700
BIGBUTTON = pygame.Rect(0, 0, 1000, 700)
rainbowTuple = (RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET)
def main():
global SCREEN, FPS, colorList, coords, snakeLength
pygame.init()
clock = pygame.time.Clock()
size = (WWIDTH, WHEIGHT)
SCREEN = pygame.display.set_mode(size)
pygame.display.set_caption('Psychedelic Circles')
colorList = []
coords = []
snakeLength = 50 ### Change this value to make the circles disappear more quickly or slowly
while True:
clickedButton = None
SCREEN.fill(GRAY)
drawButtons()
checkForQuit()
for event in pygame.event.get():
if event.type == MOUSEMOTION:
mousex, mousey = event.pos
clickedButton = getButtonClicked(mousex, mousey)
if clickedButton == FUSCHIA:
sendCoords(mousex, mousey)
drawCircles(snakeLength)
pygame.display.update()
clock.tick(FPS)
def terminate():
pygame.quit()
sys.exit()
def sendCoords(x, y):
coords.append((x, y))
colorList.append(random.choice(rainbowTuple))
def checkForQuit():
for event in pygame.event.get(QUIT):
terminate()
for event in pygame.event.get(KEYUP):
if event.key == K_ESCAPE:
terminate()
pygame.event.post(event)
def drawButtons():
pygame.draw.rect(SCREEN, FUSCHIA, BIGBUTTON)
def drawCircles(snakeLength):
for i in range(len(coords)):
pygame.draw.circle(SCREEN, colorList[i], coords[i], abs(i-(len(coords))) * 5, 0)#### Why does the list index go out of range?
if i > snakeLength :
popList()
def popList():
coords.pop(0)
colorList.pop(0)
def getButtonClicked(x, y):
if BIGBUTTON.collidepoint((x, y)):
return FUSCHIA
return None
if __name__ == '__main__':
main()
I suspect this error only occurs when there are multiple mouse move events in the event queue. Normally, pygame is fast enough to render the screen while accruing no more than one new user input event, so sendCoords will only be called once in between drawCircles calls. In that case, coords never exceeds a size of 52. But if several mouse move events accrue (perhaps due to system lag, or because the user is jiggling his mouse really fast), then sendCoords may be called many more times in a row. So by the time drawCircles executes, coords can have 53 elements, or even more.
This becomes a problem when you reach drawCircles:
def drawCircles(snakeLength):
for i in range(len(coords)):
pygame.draw.circle(SCREEN, colorList[i], coords[i], abs(i-(len(coords))) * 5, 0)#### Why does the list index go out of range?
if i > snakeLength :
popList()
Let's say this function executes when coords contains 53 elements, and snakeLength is 50. The loop will iterate normally until i equals 51. Then the i > snakeLength will evaluate to True, and popList will be called. Now coords is one element smaller, and has a length of 52. That iteration of the loop ends, and the next iteration starts. i will equal 52. The pygame.draw.circle line will attempt to access coords[i], but because coords no longer has 53 elements, coords[i] will raise an IndexError trying to access the 53rd element.
Python is not smart enough to understand that the for i in range(len(coords)) loop should end one iteration earlier than usual if coords reduces in size by one. It happily iterates all the way up to the original length of the list, heedless of whether this might cause a crash.
One possible solution is to move popList outside of the list, so the size of coords doesn't change while you're iterating over it.
def drawCircles(snakeLength):
for i in range(len(coords)):
pygame.draw.circle(SCREEN, colorList[i], coords[i], abs(i-(len(coords))) * 5, 0)#### Why does the list index go out of range?
while len(coords) > snakeLength :
popList()
You might be thinking "But why is it OK to modify coords' length in this while loop, when it wasn't OK to do it in the for loop?" The critical distinction is the evaluation time of the two statements. range(len(coords)) exectues exactly once before the loop starts, so modifications to coords won't be noticed. but len(coords) > snakeLength executes at the beginning of very iteration of the while loop, so changes to coords gets noticed right away.
How can I get rid of white spots when drawing multiple circles close to each other in pygame?
Here is my code:
import pygame
from pygame import gfxdraw
from math import pow, atan2
def getColor(r, col1, col2, fun="lin"):
if fun =="pol2":
r = pow(r,2)
col1_r = tuple([r*x for x in col1])
col2_r = tuple([(1-r)*x for x in col2])
final_col = tuple(sum(i) for i in zip(col1_r, col2_r))
return final_col
def draw(sizeX, sizeY):
# Initialize the game engine
pygame.init()
screen = pygame.display.set_mode([sizeX, sizeY])
#Loop until the user clicks the close button.
done = False
clock = pygame.time.Clock()
while not done:
# This limits the while loop to a max of 10 times per second.
# Leave this out and we will use all CPU we can.
clock.tick(10)
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT: # If user clicked close
done=True # Flag that we are done so we exit this loop
screen.fill(WHITE)
for y in range(200,500):
for x in range(0,10):
gfxdraw.arc(screen, 400, 400, y, x*15, (x+1)*15, getColor(x/10,(0,0,(y-200)/2),(255,255,(y-200)/2), fun="lin"))
pygame.display.flip()
pygame.quit()
This phenomenon is called aliasing and happens when you take a continuous signal and samples it. In your case, gfx.draw() uses continuous functions (the trigonometric functions) to calculate which pixel to draw the color onto. Since theses calculations are in floats and have to be rounded to integers, it may happen that some pixels are missed.
To fix this you need an anti-aliasing filter. There are many different types such as low pass (blurring), oversampling etc.
Since these holes almost always are one pixel I'd create a function that identifies these holes and fills them with the average of it's neighbours colors. The problem is that Pygame is not very good at manually manipulating pixels, so it can be slow depending on the size of the image. Although, Pygame has a module called surfarray that's built on numpy which allows you to access pixels easier and faster, so that will speed it up some. Of course, it'll require you to install numpy.
I couldn't get your program to work, so next time make sure you really have a Minimal, Complete, and Verifiable example. The following code is just based on the image you provided.
import numpy as np
import pygame
pygame.init()
RADIUS = 1080 // 2
FPS = 30
screen = pygame.display.set_mode((RADIUS * 2, RADIUS * 2))
clock = pygame.time.Clock()
circle_size = (RADIUS * 2, RADIUS * 2)
circle = pygame.Surface(circle_size)
background_color = (255, 255, 255)
circle_color = (255, 0, 0)
pygame.draw.circle(circle, circle_color, (RADIUS, RADIUS), RADIUS, RADIUS // 2)
def remove_holes(surface, background=(0, 0, 0)):
"""
Removes holes caused by aliasing.
The function locates pixels of color 'background' that are surrounded by pixels of different colors and set them to
the average of their neighbours. Won't fix pixels with 2 or less adjacent pixels.
Args:
surface (pygame.Surface): the pygame.Surface to anti-aliasing.
background (3 element list or tuple): the color of the holes.
Returns:
anti-aliased pygame.Surface.
"""
width, height = surface.get_size()
array = pygame.surfarray.array3d(surface)
contains_background = (array == background).all(axis=2)
neighbours = (0, 1), (0, -1), (1, 0), (-1, 0)
for row in range(1, height-1):
for col in range(1, width-1):
if contains_background[row, col]:
average = np.zeros(shape=(1, 3), dtype=np.uint16)
elements = 0
for y, x in neighbours:
if not contains_background[row+y, col+x]:
elements += 1
average += array[row+y, col+x]
if elements > 2: # Only apply average if more than 2 neighbours is not of background color.
array[row, col] = average // elements
return pygame.surfarray.make_surface(array)
def main():
running = True
image = pygame.image.load('test.png').convert()
# image = circle
pos = image.get_rect(center=(RADIUS, RADIUS))
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_1:
print('Reset circle.')
image = circle
elif event.key == pygame.K_2:
print('Starting removing holes.')
time = pygame.time.get_ticks()
image = remove_holes(image, background=(255, 255, 255))
time = pygame.time.get_ticks() - time
print('Finished removing holes in {:.4E} s.'.format(time / 1000))
screen.fill(background_color)
screen.blit(image, pos)
pygame.display.update()
if __name__ == '__main__':
main()
Result
Before
After
Time
As I said before, it's not a very fast operation. Here are some benchmarks based on the circle in the example:
Surface size: (100, 100) | Time: 1.1521E-02 s
Surface size: (200, 200) | Time: 4.3365E-02 s
Surface size: (300, 300) | Time: 9.7489E-02 s
Surface size: (400, 400) | Time: 1.7257E-01 s
Surface size: (500, 500) | Time: 2.6911E-01 s
Surface size: (600, 600) | Time: 3.8759E-01 s
Surface size: (700, 700) | Time: 5.2999E-01 s
Surface size: (800, 800) | Time: 6.9134E-01 s
Surface size: (900, 900) | Time: 9.1454E-01 s
And with your image:
Time: 1.6557E-01 s
I recently discovered the different blending modes you can apply to blitted surfaces in pygame and I wanted to see how flexible the system was. Unless I'm doing something wrong, it's apparently pretty limited (just like the rest of pygame OOOOOOOH shots fired). I wrote a simple program that draws a bunch of gradient circles using alpha and blits them all around the screen. This is the code:
import pygame
import pygame.gfxdraw
pygame.init()
import random
SCREEN = pygame.display.set_mode((800, 600))
SCREEN.fill((0, 0, 0))
def draw_square(surface, colour, x, y):
"""
Yeah it's called draw square but it actually draws a circle thing I was just too lazy
to change the name so you gotta deal with it.
"""
square = pygame.Surface((100, 100))
square.fill((0, 0, 0))
colour += (int(15/255*100), )
for i in range(25):
pygame.gfxdraw.filled_circle(square, 50, 50, i*2, colour)
# Comment the previous two lines out and uncomment the next line to see different results.
# pygame.draw.circle(square, colour[:3], (50, 50), 50)
surface.blit(square, (x - 50, y - 50), special_flags=pygame.BLEND_RGB_ADD)
running = True
while running:
for evt in pygame.event.get():
if evt.type == pygame.QUIT:
running = False
draw_square(SCREEN, (25, 255, 25), random.randint(0, 800), random.randint(0, 600))
pygame.display.update()
pygame.quit()
It seems to work when drawing a normal circle, but when drawing the circles with pygame.gfxdraw.filled_circle additive blending doesn't work. Any ideas?
EDIT: I'm using Python 3, so 15/255 evaluates properly to a float.
The issue is still with this line:
colour += (int(15/255*100), )
It should go to white initially, but the alpha is so low it will take a long time (well, it should in theory...).
Doing:
colour += (int(125/255*100), )
Makes the effect more obvious.
Result: