Search code examples
pythonfunctionloopspygamedelay

How to create a cooldown function in pygame?


I'm making a game where I want the character only to be able to shoot every 0.4 seconds or so. Currently, the character can spam bullets and it looks like a long strip of bullets coming out of the character. How can I make the charcacter wait until the next bullet comes out.

I think this would work with pygame.time.get_clicks() but I don't really know how.

I tried doing this but then no bullets comes out. How can I make it work?

def fire():
    last = pygame.time.get_ticks()
    cooldown = 300
    for bullet in bullets:
        if bullet.y < 500 and bullet.y > 0:
            bullet.y -= bullet.vel
        else:
            bullets.pop(bullets.index(bullet))
    if keys[pygame.K_SPACE]:
        now = pygame.time.get_ticks()
        if now - last >= cooldown:
            bullets.append(projectile((man.x + man.width // 2), (man.y - 7), 7, 3, (255, 0, 0)))

Here is the entire code if anybody wants it (I made it a rectangle instead of a picture sprite so anyone could try it.


pygame.init()

win = pygame.display.set_mode((500, 500))

class player():
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.vel = 4
        self.left = False
        self.right = False

    def draw(self, win):
        pygame.draw.rect(win, (0, 200, 0), (man.x, man.y, man.height, man.width))
        if self.left:
            pygame.draw.rect(win, (0, 200, 0), (man.x, man.y, man.height, man.width))
        elif self.right:
            pygame.draw.rect(win, (0, 200, 0), (man.x, man.y, man.height, man.width))

class projectile():
    def __init__(self, x, y, width, height, color):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.color = color
        self.vel = 7

    def draw(self, win):
        pygame.draw.rect(win, self.color, (self.x, self.y, self.height, self. width))


def move():
    if keys[pygame.K_LEFT] and man.x > man.vel:
        man.x -= man.vel
        man.left = True
        man.right = False

    elif keys[pygame.K_RIGHT] and man.x < 500 - man.width - man.vel:
        man.x += man.vel
        man.left = False
        man.right = True


def fire():
    last = pygame.time.get_ticks() # remove this if you want to try it working
    cooldown = 400 # remove this if you want to try it working
    for bullet in bullets:
        if bullet.y < 500 and bullet.y > 0:
            bullet.y -= bullet.vel
        else:
            bullets.pop(bullets.index(bullet))
    if keys[pygame.K_SPACE]:
        now = pygame.time.get_ticks() # remove this if you want to try it working
        if now - last >= cooldown: # remove this if you want to try it working
            bullets.append(projectile((man.x + man.width // 2), (man.y - 7), 7, 3, (255, 0, 0)))


def re_draw():
    win.fill((0, 0, 0))
    man.draw(win)
    for bullet in bullets:
        bullet.draw(win)
    pygame.display.update()


man = player(400, 400, 64, 64)
bullets = []
run = True
shoot_loop = 0
clock = pygame.time.Clock()


while run:

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

    keys = pygame.key.get_pressed()
    fire()
    move()
    clock.tick(60)
    re_draw()

pygame.quit()

Solution

  • As @gregk says, the error is that you set last = pygame.time.get_ticks() inside the fire function, which is called each iteration of the main loop.
    You are saying to the program that the time of the last shot is always the time of the last iteration, even if there was not a shot. Hence now - last >= cooldown will never be True, since last is always updated to the later time.

    This can be solved in many ways, but the key point is how to track the flowing time. You don't have to update last each iteration.

    A close approach to your current code (to avoid to edit a lot of stuff) is the following. Edit your fire() function this way:

    def fire():
        global cooldown_tracker
        cooldown_tracker += clock.get_time()
        if cooldown_tracker > 400:
            cooldown_tracker = 0
    
        for bullet in bullets:
            if bullet.y < 500 and bullet.y > 0:
                bullet.y -= bullet.vel
            else:
                bullets.pop(bullets.index(bullet))
    
        if keys[pygame.K_SPACE] and cooldown_tracker == 0:
            bullets.append(projectile((man.x + man.width // 2), (man.y - 7), 7, 3, (255, 0, 0)))
    

    And add

    cooldown_tracker = 0
    

    before the main loop (just before while run).
    In this solution, rather than pygame.time.get_ticks() I use the clock object you already have.

    I use a cooldown_tracker variable, each iteration (when the fire() function is called) the time of the last iteration is added to the tracker, and if this time is greater than 400 ms, the tracker is reset to 0. A bullet is added to the list (a shot) only if the tracker is 0, meaning that the cooldown time has elapsed.