I need to know if 2 ellipses are colliding in pygame/python
I know how to detect collision between two circles using :
if radius1 + radius2 > math.sqrt(((centerx1 - centerx2)**2) + (centery1 - centery2)**2):
But is it possible to do the same between 2 ellipses? Help will be appreciated. Thanks.
Yes, it is possible to detect collisions between two ellipses in pygame. One way to do this is by using masks in pygame (of course this is not the only way). pygame.mask.Mask is a class in pygame that can be used to create mask objects. To use masks to detect collisions between two ellipses, you will need to create a pygame.mask.Mask object for each surface of an ellipse, and then use the pygame.mask.Mask.overlap() method to check for overlap between the two masks. Here is a function that takes in two pygame.Rect objects represented as ellipses and checks for overlaps of each surface of those two pygame.Rect objects:
def ellipse_collision(ellipse1, ellipse2):
# Create a mask for each ellipse
mask1 = pygame.mask.Mask((ellipse1[0]*2, ellipse1[1]*2))
mask2 = pygame.mask.Mask((ellipse2[0]*2, ellipse2[1]*2))
# Draw the ellipses onto the masks
mask1.draw(pygame.draw.ellipse, (0, 0, ellipse1[0]*2, ellipse1[1]*2))
mask2.draw(pygame.draw.ellipse, (0, 0, ellipse2[0]*2, ellipse2[1]*2))
# Check for overlap between the masks
offset = (ellipse2[2]-ellipse1[2], ellipse2[3]-ellipse1[3])
return mask1.overlap(mask2, offset)
NOTE: Masks in pygame are useful for fast pixel-perfect collision detection.
The problem can be reduced to the intersection of an ellipse and a circle (you can scale 1 dimension). However, there doesn't seem to be a short answer. See Ellipse–circle and ellipse–ellipse collision detection and How to detect if an ellipse intersects(collides with) a circle. The major issue is to find the closest point on the ellipse to the circle.
Even though I don't think the question here was about a collision masks, but about finding a formula that is more powerful than using masks where both masks have to be passed through and the mask bits compared, I will provide a complete alternative solution using masks:
Draw the ellipses on the surface and get the bounding rectangles of the surfaces on the screen.
Crate masks from the surfaces with pygame.mask.from_surface
Test if the masks are colliding with pygame.mask.Mask.overlap (also see PyGame collision with masks) or get the overlapping mask with pygame.mask.Mask.overlap_mask
mask1 = pygame.mask.from_surface(ellipse_surf1)
mask2 = pygame.mask.from_surface(ellipse_surf2)
offset_x = ellipse_rect2.x - ellipse_rect1.x
offset_y = ellipse_rect2.y - ellipse_rect2.y
overlap_mask = mask1.overlap_mask(mask2, (offset_x, offset_y))
Complete example:
import pygame
def create_ellipse_angle(color, rect, angle, width=0):
target_rect = pygame.Rect(rect)
shape_surf = pygame.Surface(target_rect.size, pygame.SRCALPHA)
pygame.draw.ellipse(shape_surf, color, (0, 0, *target_rect.size), width)
rotated_surf = pygame.transform.rotate(shape_surf, angle)
bounding_rect = rotated_surf.get_rect(center = target_rect.center)
return rotated_surf, bounding_rect
pygame.init()
window = pygame.display.set_mode((400, 250))
clock = pygame.time.Clock()
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (160, 160, 160), (192, 192, 192)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
for rect, color in tiles:
pygame.draw.rect(background, color, rect)
angle1 = 0
angle2 = 0
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
ellipse_surf1, ellipse_rect1 = create_ellipse_angle((0, 128, 128), (35, 75, 200, 100), angle1)
angle1 += 1
ellipse_surf2, ellipse_rect2 = create_ellipse_angle((128, 0, 128), (165, 75, 200, 100), angle2)
angle2 -= 0.5
mask1 = pygame.mask.from_surface(ellipse_surf1)
mask2 = pygame.mask.from_surface(ellipse_surf2)
offset_x = ellipse_rect2.x - ellipse_rect1.x
offset_y = ellipse_rect2.y - ellipse_rect2.y
overlap_mask = mask1.overlap_mask(mask2, (offset_x, offset_y))
overlap_surf = overlap_mask.to_surface(setcolor = (255, 0, 0))
overlap_surf.set_colorkey((0, 0, 0))
window.blit(background, (0, 0))
window.blit(ellipse_surf1, ellipse_rect1)
window.blit(ellipse_surf2, ellipse_rect2)
window.blit(overlap_surf, ellipse_rect1)
pygame.display.flip()
pygame.quit()
exit()
Related
I'm trying to make a kind of a Brick Breaker and got a problem with the transparency of the rectangle that surrounds the ball. every time it hits something you can see the rectangle.
any suggestions?it also forces me to use a white background there is an image of the problem
import pygame
pygame.init()
bg_color = (255,255,255)
width, height = 600, 400
dx, dy = 2, 2
screen = pygame.display.set_mode((width, height))
screen.fill(bg_color)
ball = pygame.image.load("medicine-ball.png").convert()
ball = pygame.transform.scale(ball, (50, 50))
ball_rect = ball.get_rect()
ball_color = False
def rect(x1,y1,x2,y2):
pygame.draw.rect(screen, (0,0,0), (x1,y1,x2,y2))
game_loop = True
while game_loop:
event = pygame.event.poll()
if event.type == pygame.QUIT:
game_loop = False
ball_rect = ball_rect.move(dx,dy)
if ball_rect.left < 0 or ball_rect.right > width:
dx *= -1
if ball_rect.top < 0 or ball_rect.bottom > height:
dy *= -1
mouse_pos = list(pygame.mouse.get_pos())
rect(mouse_pos[0]-40,300-10,80,20)
if ball_rect.bottom == 300 and ball_rect.x > mouse_pos[0]-89 and ball_rect.x < mouse_pos[0]+129:
dy *= -1
screen.blit(ball, ball_rect)
pygame.time.wait(1)
pygame.display.flip()
screen.fill(bg_color)
another thing that is bothering me is that I can't change the speed of the ball, I am pretty sure it is a problem in my mac because it works on my friend's pc(it's about the pygame.time.wait())
If you want to make the images transparent, you need to make sure that the alpha channel of the images is set. Additionally you must use convert_alpha() instead of convert():
if ball_color:
ball = pygame.image.load("ball.png").convert_alpha()
else:
ball = pygame.image.load("medicine-ball.png").convert_alpha()
See also the answers to the questions:
How to convert the background color of image to match the color of Pygame window?
Pygame image transparency confusion
pygame image background does not match main background
How can I make an Image with a transparent Backround in Pygame?
make a rect transparent in pygame
Unfortunately there is no good way to draw a transparent shape. See the answers to the question Draw a transparent rectangle in pygame and see pygame.draw module:
A color's alpha value will be written directly into the surface [...], but the draw function will not draw transparently.
Hence you need to do a workaround:
Create a pygame.Surface object with a per-pixel alpha format large enough to cover the shape.
Draw the shape on the _Surface.
Blend the Surface with the target Surface. blit() by default blends 2 Surfaces
For example 3 functions, which can draw transparent rectangles, circles and polygons:
def draw_rect_alpha(surface, color, rect):
shape_surf = pygame.Surface(pygame.Rect(rect).size, pygame.SRCALPHA)
pygame.draw.rect(shape_surf, color, shape_surf.get_rect())
surface.blit(shape_surf, rect)
Use the function in your code instead of pygame.draw.rect, alpha is a value in range [0, 255]:
def rect(x1, y1, x2, y2, alpha = 255):
#pygame.draw.rect(screen, (0,0,0), (x1,y1,x2,y2))
draw_rect_alpha(screen, (0, 0, 0, alpha), (x1, y1, x2, y2))
I have a few sprites in my game that need specific parts to be able to change colour.
My process I am trying to to have a pure white sprite image that is transparent everywhere the colour does not need to be. I am blitting a coloured square on top of that, and then that on top of the main sprite, however the main sprite then changes colour everywhere, but while respecting the main sprite transparency. The part that confuses me most is that the masked colour image does look correct when I put it on the main screen.
# Load main sprite and mask sprite
self.image = pygame.image.load("Enemy.png").convert_alpha()
self.mask = pygame.image.load("EnemyMask.png").convert_alpha()
# Create coloured image the size of the entire sprite
self.coloured_image = pygame.Surface([self.width, self.height])
self.coloured_image.fill(self.colour)
# Mask off the coloured image with the transparency of the masked image, this part works
self.masked = self.mask.copy()
self.masked.blit(self.coloured_image, (0, 0), None, pygame.BLEND_RGBA_MULT)
# Put the masked image on top of the main sprite
self.image.blit(self.masked, (0, 0), None, pygame.BLEND_MULT)
Enemy.png
EnemyMask.png (It's white so can't be seen)
Masked colour Masked Colour
Final Failed Sprite Failed Sprite
Can't post images, not enough reputation
I get no error, but only the white part of the shield is supposed to be green
self.image is the loaded image, where you want to change specific regions by a certain color and self.mask is a mask which defines the regions.
And you create an image masked, which contains the regions which are specified in mask tinted in a specific color.
So all you've to do is to .blit the tinted mask (masked) on the image without any special_flags set:
self.image.blit(self.masked, (0, 0))
See the example, where the red rectangle is changed to a blue rectangle:
repl.it/#Rabbid76/PyGame-ChangeColorOfSurfaceArea
Minimal example: repl.it/#Rabbid76/PyGame-ChangeColorOfSurfaceArea-3
Sprite:
Mask:
import pygame
def changColor(image, maskImage, newColor):
colouredImage = pygame.Surface(image.get_size())
colouredImage.fill(newColor)
masked = maskImage.copy()
masked.set_colorkey((0, 0, 0))
masked.blit(colouredImage, (0, 0), None, pygame.BLEND_RGBA_MULT)
finalImage = image.copy()
finalImage.blit(masked, (0, 0), None)
return finalImage
pygame.init()
window = pygame.display.set_mode((404, 84))
image = pygame.image.load('avatar64.png').convert_alpha()
maskImage = pygame.image.load('avatar64mask.png').convert_alpha()
colors = []
for hue in range (0, 360, 60):
colors.append(pygame.Color(0))
colors[-1].hsla = (hue, 100, 50, 100)
images = [changColor(image, maskImage, c) for c in colors]
clock = pygame.time.Clock()
nextColorTime = 0
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill((255, 255, 255))
for i, image in enumerate(images):
window.blit(image, (10 + i * 64, 10))
pygame.display.flip()
pygame.quit()
exit()
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()
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 am trying to code a simple circle timer in Python using Pygame.
At the moment it looks like this:
As you can see, the blue line is very wavy and has white dots in it. I am achieving this blue line by using pygame.draw.arc() function, but it is not anti-aliased and looks bad. I would like it to be anti-aliased, but gfxdraw module which should let me achieve this, doesn't support arc width selection. Here's code snippet:
pygame.draw.arc(screen, blue, [center[0] - 120, center[1] - 120, 240, 240], pi/2, pi/2+pi*i*koef, 15)
pygame.gfxdraw.aacircle(screen, center[0], center[1], 105, black)
pygame.gfxdraw.aacircle(screen, center[0], center[1], 120, black)
I did it creating the arc with a polygon.
def drawArc(surface, x, y, r, th, start, stop, color):
points_outer = []
points_inner = []
n = round(r*abs(stop-start)/20)
if n<2:
n = 2
for i in range(n):
delta = i/(n-1)
phi0 = start + (stop-start)*delta
x0 = round(x+r*math.cos(phi0))
y0 = round(y+r*math.sin(phi0))
points_outer.append([x0,y0])
phi1 = stop + (start-stop)*delta
x1 = round(x+(r-th)*math.cos(phi1))
y1 = round(y+(r-th)*math.sin(phi1))
points_inner.append([x1,y1])
points = points_outer + points_inner
pygame.gfxdraw.aapolygon(surface, points, color)
pygame.gfxdraw.filled_polygon(surface, points, color)
The for loop could certainly be created more elegantly with a generator, but I am not very sophisticated with python.
The arc definitely looks nicer than pygame.draw.arc, but when I compare it to the screen rendering on my mac, there is room for improvement.
I am not aware of any pygame function that would solve this problem, meaning you basically have to program a solution yourself (or use something other than pygame), since draw is broken as you've noted and gfxdraw won't give you the thickness.
One very ugly but simple solution is to draw multiple times over the arc segments, always slightly shifted to "fill in" the missing gaps. This will still leave some aliasing at the very front of the timer arc, but the rest will be filled in.
import pygame
from pygame.locals import *
import pygame.gfxdraw
import math
# Screen size
SCREEN_HEIGHT = 350
SCREEN_WIDTH = 500
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREY = (150, 150, 150)
RED = (255,0,0)
# initialisation
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
done = False
clock = pygame.time.Clock()
# We need this if we want to be able to specify our
# arc in degrees instead of radians
def degreesToRadians(deg):
return deg/180.0 * math.pi
# Draw an arc that is a portion of a circle.
# We pass in screen and color,
# followed by a tuple (x,y) that is the center of the circle, and the radius.
# Next comes the start and ending angle on the "unit circle" (0 to 360)
# of the circle we want to draw, and finally the thickness in pixels
def drawCircleArc(screen,color,center,radius,startDeg,endDeg,thickness):
(x,y) = center
rect = (x-radius,y-radius,radius*2,radius*2)
startRad = degreesToRadians(startDeg)
endRad = degreesToRadians(endDeg)
pygame.draw.arc(screen,color,rect,startRad,endRad,thickness)
# fill screen with background
screen.fill(WHITE)
center = [150, 200]
pygame.gfxdraw.aacircle(screen, center[0], center[1], 105, BLACK)
pygame.gfxdraw.aacircle(screen, center[0], center[1], 120, BLACK)
pygame.display.update()
step = 10
maxdeg = 0
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
maxdeg = maxdeg + step
for i in range(min(0,maxdeg-30),maxdeg):
drawCircleArc(screen,RED,(150,200),119,i+90,max(i+10,maxdeg)+90,14)
#+90 will shift it from starting at the right to starting (roughly) at the top
pygame.display.flip()
clock.tick(2) # ensures a maximum of 60 frames per second
pygame.quit()
Note that I have copied degreesToRadians and drawCircleArc from https://www.cs.ucsb.edu/~pconrad/cs5nm/08F/ex/ex09/drawCircleArcExample.py
I do not generally recommend this solution, but it might do in a pinch.
You are right, some pygame rendering functions do indeed suck, so you can achieve something like this with PIL instead.
pie_size = (40, 40) # defining constants
pil_img = PIL.Image.new("RGBA", pie_size) # PIL template image
pil_draw = PIL.ImageDraw.Draw(pil_img) # drawable image
pil_draw.pieslice((0, 0, *[ps - 1 for ps in pie_size]), -90, 180, fill=(0, 0, 0)) # args: (x0, y0, x1, y1), start, end, fill
This will create a PIL shape. Now we can convert it to pygame.
data = pil_img.tobytes()
size = pil_img.size
mode = pil_img.mode
pygame_img = pygame.image.fromstring(data, size, mode).convert_alpha()
But don't forget to pip install pillow and
import PIL.Image
import PIL.ImageDraw
Ok, this is really old, but why not try to draw pies instead. For example draw a pie, then an unfilled circle as the outside ring and then a filled circle as the inside and another unfilled circle as the inside ring.
So pie -> unfilled circle -> filled circle -> unfilled.
The order is somewhat arbitrary but if u still have this problem give it a try. (Btw I haven't tried it but I think it will work)
For my own uses, I wrote a simple wrapper function, and to deal with the spotty arc drawing, I used an ugly loop to draw the same arc several times.
def DrawArc(surface, color, center, radius, startAngle, stopAngle, width=1):
width -= 2
for i in range(-2, 3):
# (2pi rad) / (360 deg)
deg2Rad = 0.01745329251
rect = pygame.Rect(
center[0] - radius + i,
center[1] - radius,
radius * 2,
radius * 2
)
pygame.draw.arc(
surface,
color,
rect,
startAngle * deg2Rad,
stopAngle * deg2Rad,
width
)
I'm aware this is not a great solution, but it works alright for my uses.
An important note is I added that "width -= 2" to hopefully preserve the intended size of the arc at least a little more accurately, but this results in increasing the minimum width by 2.
In your case, you might want to consider doing something more to fix the issues this results in.
If the start and end aren't all that important, one can create many circles following an arc trajectory and when done ie small circles drawn 360 time, you finally have a big circle with no wavy effect:
MWE:
#!/usr/bin/env python3
import pygame
import math
# Initialize pygame
pygame.init()
# Set the screen size
screen = pygame.display.set_mode((400, 300))
# Set the center point of the arc
center_x = 200
center_y = 150
arc_radius = 100
circle_radius = 6
# Set the start and stop angles of the arc
start_angle = 0
stop_angle = 360
angle_step = 1
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Clear the screen
screen.fill((0, 0, 0))
# Draw the overlapping circles
for i in range(start_angle, stop_angle, angle_step):
angle = math.radians(i)
x = center_x + arc_radius * math.cos(angle)
y = center_y + arc_radius * math.sin(angle)
pygame.draw.circle(screen, "red", (int(x), int(y)), circle_radius)
# Update the display
pygame.display.flip()
pygame.quit()
Having a start_angle and stop_angle of 0 to 360 respectively yields a fill circle with an output:
To change it to a 1/3 of a circle, one would change the stop_angle from 360 to 120 (1/3 x 360 = 120) and this would then yield: