Spawning multiple instances of the same object concurrently in python

I'm a beginner programmer who is starting with python and I'm starting out by making a game in pygame. The game basically spawns circles at random positions and when clicked, it gives you points.

Recently I've hit a roadblock when I want to spawn multiple instances of the same object (in this case circles) at the same time. I've tried stuff like sleep() and some other code related to counters, but it always results in the next circle spawned overriding the previous one (i.e the program spawns circle 1, but when circle 2 comes in, circle 1 disappears).

Does anyone know a solution to this? I would really appreciate your help!

import pygame
import random
import time

pygame.init()

window = pygame.display.set_mode((800,600))

class circle():
    def __init__(self, color, x, y, radius, width,):
        self.color = color
        self.x = x
        self.y = y
        self.radius = radius
        self.width = width

    def draw(self, win, outline=None):
        pygame.draw.circle(win, self.color, (self.x, self.y, self.radius, self.width), 0)

run=True
while run:
    window.fill((0, 0, 0))
    pygame.draw.circle(window, (255, 255, 255), (random.randint(0, 800),random.randint(0, 600)), 20, 20)
    time.sleep(1)
    pygame.display.update()

    for event in pygame.event.get():

        if event.type == pygame.QUIT:
            run=False
            pygame.quit()
            quit()

It does not work that way. time.sleep, pygame.time.wait() or pygame.time.delay is not the right way to control time and gameplay within an application loop. The game does not respond while you wait. The application loop runs continuously. You have to measure the time in the loop and spawn the objects according to the elapsed time.
pygame.Surface.fill clears the entire screen. Add the newly created objects to a list. Redraw all of the objects and the entire scene in each frame.
See also Time, timer event and clock


You have 2 options. Use pygame.time.get_ticks() to measure the time. Define a time interval after which a new object should appear. Create an object when the point in time is reached and calculate the point in time for the next object:

object_list = []
time_interval = 500 # 500 milliseconds == 0.1 seconds
next_object_time = 0 

while run:
    # [...]
    
    current_time = pygame.time.get_ticks()
    if current_time > next_object_time:
        next_object_time += time_interval
        object_list.append(Object())

Minimal example:

repl.it/@Rabbid76/PyGame-TimerSpawnObjects

import pygame, random
pygame.init()
window = pygame.display.set_mode((300, 300))

class Object:
    def __init__(self):
        self.radius = 50
        self.x = random.randrange(self.radius, window.get_width()-self.radius)
        self.y = random.randrange(self.radius, window.get_height()-self.radius)
        self.color = pygame.Color(0)
        self.color.hsla = (random.randrange(0, 360), 100, 50, 100)

object_list = []
time_interval = 200 # 200 milliseconds == 0.2 seconds
next_object_time = 0 

run = True
clock = pygame.time.Clock()
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
    
    current_time = pygame.time.get_ticks()
    if current_time > next_object_time:
        next_object_time += time_interval
        object_list.append(Object())
    
    window.fill(0)
    for object in object_list[:]:
        pygame.draw.circle(window, object.color, (object.x, object.y), round(object.radius))
        object.radius -= 0.2
        if object.radius < 1:
            object_list.remove(object)
    pygame.display.flip()

pygame.quit()
exit()

The other option is to use the pygame.event module. Use pygame.time.set_timer() to repeatedly create a USEREVENT in the event queue. The time has to be set in milliseconds. e.g.:

object_list = []
time_interval = 500 # 500 milliseconds == 0.1 seconds
timer_event = pygame.USEREVENT+1
pygame.time.set_timer(timer_event, time_interval)

Note, in pygame customer events can be defined. Each event needs a unique id. The ids for the user events have to be between pygame.USEREVENT (24) and pygame.NUMEVENTS (32). In this case pygame.USEREVENT+1 is the event id for the timer event.

Receive the event in the event loop:

while run:
    for event in pygame.event.get():
        if event.type == timer_event:
            object_list.append(Object())

The timer event can be stopped by passing 0 to the time argument of pygame.time.set_timer.

Minimal example:

repl.it/@Rabbid76/PyGame-TimerEventSpawn

import pygame, random
pygame.init()
window = pygame.display.set_mode((300, 300))

class Object:
    def __init__(self):
        self.radius = 50
        self.x = random.randrange(self.radius, window.get_width()-self.radius)
        self.y = random.randrange(self.radius, window.get_height()-self.radius)
        self.color = pygame.Color(0)
        self.color.hsla = (random.randrange(0, 360), 100, 50, 100)

object_list = []
time_interval = 200 # 200 milliseconds == 0.2 seconds
timer_event = pygame.USEREVENT+1
pygame.time.set_timer(timer_event, time_interval)

run = True
clock = pygame.time.Clock()
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        elif event.type == timer_event:
            object_list.append(Object())
    
    window.fill(0)
    for object in object_list[:]:
        pygame.draw.circle(window, object.color, (object.x, object.y), round(object.radius))
        object.radius -= 0.2
        if object.radius < 1:
            object_list.remove(object)
    pygame.display.flip()

pygame.quit()
exit()