Search code examples
pythonpygame

Draw a full arc with pygame


I'm currently coding a Pac-man with Pygame, and I'm blocked by the sprite. I want to make a yellow circle with a black (or back color) arc, like if it was a pie without a piece of a cake. I read documentations about it but all things I found wasn't at my level (I'm in high school). Thanks for reading and answering me back, Regards.

import pygame
from pygame.locals import *
from board import *
from math import *
import time
import sys

screen_width, screen_height = 660, 700
case_h = (screen_height-50)//32
case_w = screen_width//30

def create_window(longueur, largeur, titre, back_color):
    """
        Création de la fenêtre principale
        :param screen_width : la longueur de l'écran (en pixels)
        :type screen_width : int
        :param screen_height : la largeur de l'écran (en pixels)
        :type screen_height : int
        :param titre : nom donné à la fenêtre
        :type titre : str
    """
    # Création de la fenêtre
    windows = pygame.display.set_mode((longueur, largeur))
    windows.fill(back_color)
    # Nommage de la fenêtre
    pygame.display.set_caption('Pac-Man')
    return windows

def key_pressed():
    """
        Lecture du clavier du joueur
    """
    global run, direction
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                run = False
            if event.key == pygame.K_LEFT:
                direction = 'gauche'
            if event.key == pygame.K_RIGHT:
                direction = 'droite'
            if event.key == pygame.K_UP:
                direction = 'haut'
            if event.key == pygame.K_DOWN:
                direction = 'bas'

def draw_board(boards):
    """
        Affichage du plateau
        :param boards : matrice contenant les cases à afficher
        :type boards : list
    """
    for i in range(len(boards)):
        for j in range(len(boards[i])):
            if boards[i][j] == 1:
                pygame.draw.circle(windows, (255, 255, 0), (case_w*(j+0.5),
                                                    case_h*(i+0.5)), 4)
            if boards[i][j] == 2:
                pygame.draw.circle(windows, (255, 255, 0), (case_w*(j+0.5),
                                                    case_h*(i+0.5)), 6)
            if boards[i][j] == 3:
                pygame.draw.line(windows, (0, 0, 255), (case_w*(j+0.5), case_h*i),
                                (case_w*(j+0.5), case_h*(i+1)))
            if boards[i][j] == 4:
                pygame.draw.line(windows, (0, 0, 255), (case_w*j, case_h*(i+0.5)),
                                (case_w*(j+1), case_h*(i+0.5)))
            if boards[i][j] == 5:
                pygame.draw.arc(windows, (0, 0, 255), [case_w*(j-0.5), case_h*(i+0.5)
                                        , case_w, case_h], 0, pi/2, 1)
            if boards[i][j] == 6:
                pygame.draw.arc(windows, (0, 0, 255), [case_w*(j+0.5), case_h*(i+0.5)
                                        , case_w, case_h], pi/2, pi, 1)
            if boards[i][j] == 7:
                pygame.draw.arc(windows, (0, 0, 255), [case_w*(j+0.5), case_h*(i-0.5)
                                        , case_w, case_h], pi, (3*pi)/2, 1)
            if boards[i][j] ==8:
                pygame.draw.arc(windows, (0, 0, 255), [case_w*(j-0.5), case_h*(i-0.5)
                                        , case_w, case_h], (3*pi)/2, 2*pi, 1)
            if boards[i][j] == 9:
                pygame.draw.line(windows, (255, 255, 255), (case_w*j, case_h*(i+0.5)),
                                        (case_w*(j+1), case_h*(i+0.5)))

def pac_man_move(direction):
    """
        Création et affichage du Pac-man
        :param direction : direction du Pac-man
        :param direction : str
    """
    global posx, posy
    pygame.draw.rect(windows, (255, 255, 0), (case_w*posx, case_h*posy, case_w, case_h))
    if direction == 'gauche':
        posx -= 0.1
    elif direction == 'droite':
        posx += 0.1
    elif direction == 'haut':
        posy -= 0.1
    elif direction == 'bas':
        posy += 0.1

def draw_test():
    global posx, posy
    pygame.draw.arc(windows, (255, 255, 0), (case_w*posx, case_h*posy, case_w*(posx+1), case_h*(posy+1)), (11*pi)/6, pi/6, width=1)

run = True
posx = 15
posy = 24
direction = 'droite'
windows = create_window(screen_width, screen_height, 'Pac-Man', (0, 0, 0))
while run:
    windows.fill((0, 0, 0))
    draw_board(boards)
    pac_man_move(direction)
    key_pressed()
    if posx < 0:
        posx = 30
    elif posx > 30:
        posx = 0
    time.sleep(0.0125)
    pygame.display.update()

I tried to make an arc with pygame.draw.arc(...) and width=0, as when you draw a rectangle, if width=0, it fills the rectangle, but not with an arc.


Solution

  • congratulations for the project!

    The problem is the drawing API for Pygame is not as complete as other drawing APIs around there, and the option for drawing a "pie chart" like sprite is simply lacking.

    At this point there are some options: (1) use another drawing API as library, such as Cairo, pyglet, matplotlib or PIL (not sure if the latest has the "pie chart" thing as well) - and copy the generated pixels to a pygame compatible object; (2) pre-draw the image in an imaging program, and load these images from ".png" files at runtime for the pygame project (might be the simpler path); and (3) which can provide you with more learning of "how things work" under the hood: use basic primitives to draw the pacman shape with mathematical functions alone - this can give you better control in the sense you can add gradients and other color-changing effects across your filled area as well.

    The 3rd approach above will require some trignometric thinking, along with same basic rules for rasterising shapes, which are a learning that will give you some foundation on how things work: The basic idea is just have two nested for-loops that will yield all (x, y) values for the region of interest (ROI) where you are drawing your pacman - for each coordinate you then test if that should be filled or not (and when doing fancier stuff, maybe calculate a custom color). In the case of a Pacman shape, (x,y) should be inside a circle of radius "r", and outside an specified angle range, you will have to find out using math.asin and math.acos

    def pacman(surf,  total_r, min_ang):
        width, height = sc.get_size()
        cx, cy = width // 2, height // 2
        for y in range(height):
            for x in range(width):
                nx, ny = (x - cx) / cx, (y - cy) / cy
                r = (nx ** 2 + ny ** 2 ) ** 0.5
                alpha = math.degrees(math.asin(ny / r if r else 0.00001))
                if r < total_r and (abs(alpha) > min_ang or nx < 0) :
                    color = (255, 255, 0)
                else:
                    color = (0, 0, 0)
                surf.set_at((x,y), color)
        # pygame.display.flip()
    

    Pass a surface (like a 64x64 pixel, or whatever you want), or just your screen straight to test it - it will draw a pacman-like shape mirrored at the angle "0" (horizontal line from left to right)

    Then, of course, rendering an image like this is too slow to do every frame: draw your various pac-man sprites at a game setup stage, and keep then in different pygame Surface objects you can stamp with .blit at game time.