How do I focus light or how do I only draw certain circular parts of the window in pygame?

Solution 1:

I suggest a solution, which combines a clipping region pygame.Surface.set_clip and drawing a black rectangle with a circular transparent area in the center.

Define a radius and create a square pygame.Surface with twice the radius.

radius = 50
cover_surf = pygame.Surface((radius*2, radius*2))

Set a white color key which identifies the transparent color (set_colorkey) a nd draw a white (transparent) circle on the surface:

cover_surf.set_colorkey((255, 255, 255))
pygame.draw.circle(cover_surf, (255, 255, 255), (radius, radius), radius)

Define the center of the circular region which you want to see (in the following clip_center).
In the main application loop, clear the display and set the clipping region, the draw the scene. Before you update the display draw cover_surf in the clipping region:

while run:
    # [...]

    # clear screen and set clipping region
    screen.fill(0)    
    clip_rect = pygame.Rect(clip_center[0]-radius, clip_center[1]-radius, radius*2, radius*2)
    screen.set_clip(clip_rect)

    # draw the scene
    # [...]

    # draw transparent circle and update display
    screen.blit(cover_surf, clip_rect)
    pygame.display.flip()

Minimal example: repl.it/@Rabbid76/PyGame-ClipCircularRegion-2

import pygame
pygame.init()
screen = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()

radius = 50
cover_surf = pygame.Surface((radius*2, radius*2))
cover_surf.fill(0)
cover_surf.set_colorkey((255, 255, 255))
pygame.draw.circle(cover_surf, (255, 255, 255), (radius, radius), radius)

run = True
while run:
    clock.tick(60)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    clip_center = pygame.mouse.get_pos()

    # clear screen and set clipping region
    screen.fill(0)    
    clip_rect = pygame.Rect(clip_center[0]-radius, clip_center[1]-radius, radius*2, radius*2)
    screen.set_clip(clip_rect)

    # draw the scene
    for x in range(10):
        for y in range(10):
            color = (255, 255, 255) if (x+y) % 2 == 0 else (255, 0, 0)
            pygame.draw.rect(screen, color, (x*50, y*50, 50, 50))

    # draw transparent circle and update display
    screen.blit(cover_surf, clip_rect)
    pygame.display.flip()

If you want multiple circular drawing areas, then create a pygame.Surface.set_clip with the same size as the display and set whit color key:

cover_surf = pygame.Surface((400, 400))
cover_surf.set_colorkey((255, 255, 255))

Fill the entire surface black and draw white circles on the surface:

cover_surf.fill(0)
pygame.draw.circle(cover_surf, (255, 255, 255), (100, 100), 50)
pygame.draw.circle(cover_surf, (255, 255, 255), (300, 300), 70)

Blit the cover_surf on the window, before updating the display:

while run:
    # [...]

    # draw transparent circle and update display
    screen.blit(cover_surf, (0, 0))
    pygame.display.flip()

Minimal example: repl.it/@Rabbid76/PyGame-ClipCircularRegion-3

import pygame
pygame.init()
screen = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()

cover_surf = pygame.Surface((400, 400))
cover_surf.set_colorkey((255, 255, 255))

px = [100, 200, 300]
dx = [1, 2, 3] 

run = True
while run:
    clock.tick(60)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    # create cover surface
    cover_surf.fill(0)
    for i in range(3):
        radius = 40 + i*20
        pygame.draw.circle(cover_surf, (255, 255, 255), (px[i], 100+(i*100)), radius)
        px[i] += dx[i]
        if px[i] < radius or px[i] > 400 - radius:
            dx[i] = -dx[i]
        
    # draw the scene
    for x in range(10):
        for y in range(10):
            color = (255, 255, 255) if (x+y) % 2 == 0 else (255, 0, 0)
            pygame.draw.rect(screen, color, (x*50, y*50, 50, 50))

    # draw transparent circle and update display
    screen.blit(cover_surf, (0, 0))
    pygame.display.flip()