Search code examples
pythonfunctionooppygamebullet

Is there a way to limit how many times a function can be called in PyGame?


I am programming a target shooter game for a school project. In the game, you click and a bullet flies from the player sprite to your cursor. I currently have programmed target drawing, player sprite drawing and a bullet function however I would like to limit how many times, say, per second the player can shoot. This is partly to avoid spam shooting ruining the game's performance and also making it more challenging than just rapidly left clickign everywhere. I would assume that I could use a 'pygame_clock' command but I'm not quite sure how. Below is the code:

import pygame

#Setting window dimensions and caption. (Module 1)

pygame.init()
window = pygame.display.set_mode((800, 575))
pygame.display.set_caption("TARGET PRACTICE")

#Colour variables. (Module 1)

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (200, 0, 0)
GREEN = (0, 200, 0)
BLUE = (0, 0, 200)

#py_clock tracks framerate of program for other 'pygame.time' commands. (Module 8)

py_clock = pygame.time.Clock()

#Target class created. (Module 5)

class Target:
  def __init__(self, x, y, h, w, v):
    self.x = x
    self.y = y
    self.h = h
    self.w = w
    self.v = v


#Instantiation of targets. (Module 5)

target_1 = Target(0, 80, 60, 40, 0.5)
target_2 = Target(0, 100, 60, 40, 0.5)
target_3 = Target(0, 50, 60, 40, 0.5)
target_4 = Target(0, 75, 60, 40, 0.5)
target_5 = Target(0, 45, 60, 40, 0.5)
target_6 = Target(0, 85, 60, 40, 0.5)


#Declaring variables to be used in the while loop. (Module 5)

clock = 0

target_2_threshold = 500
target_3_threshold = 1000
target_4_threshold = 1500
target_5_threshold = 2000
target_6_threshold = 2500


#Setting player sprite dimension variables. (Module 6)

player_sprite_x = 357.5
player_sprite_y = 450
player_sprite_h = 125
player_sprite_w = 85

#all_bullets list to store bullets made by function inside loop. (Module7)

all_bullets = []

exec = True

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

    #'IF' statement to trigger the shooting function. (Module 7)

    if event.type == pygame.MOUSEBUTTONDOWN:
        if event.button == 1:
            dx = event.pos[0] - (player_sprite_x+ player_sprite_w//2)
            dy = event.pos[1] - player_sprite_y
            direction = pygame.math.Vector2(dx, dy).normalize()
            bullet = {'x': player_sprite_x+42, 'y': player_sprite_y, 'direction': direction}
            all_bullets.append(bullet)

  #Defines movement of targets and sets delay between drawings. (Module 5)   

  clock += 1
  target_1.x += target_1.v
  if clock > target_2_threshold:
        target_2.x += target_2.v
  if clock > target_3_threshold:
        target_3.x += target_3.v
  if clock > target_4_threshold:
        target_4.x += target_4.v
  if clock > target_5_threshold:
        target_5.x += target_5.v
  if clock > target_6_threshold:
        target_6.x += target_6.v

  #all_bullets_keep list combined with FOR loop retains only bullets in the arena. (Module 7)

  all_bullets_keep = []

  for item in all_bullets:
    item['x'] += item['direction'][0] # item['direction'][0] * 2
    item['y'] += item['direction'][1] # item['direction'][1] * 2

    if 0 < item['x'] < 800 and 0 < item['y'] < 575:
          all_bullets_keep.append(item)

  all_bullets = all_bullets_keep

  #Fill the background (Module 5)

  window.fill(RED)

  #Redraw each target in every frame. (Module 5)

  pygame.draw.rect(window, BLUE, (target_1.x, target_1.y, target_1.h, target_1.w))
  if clock > target_2_threshold:
      pygame.draw.rect(window, BLUE, (target_2.x, target_2.y, target_2.h, target_2.w)) 
  if clock > target_3_threshold:
      pygame.draw.rect(window, BLUE, (target_3.x, target_3.y, target_3.h, target_3.w))
  if clock > target_4_threshold:
      pygame.draw.rect(window, BLUE, (target_4.x, target_4.y, target_4.h, target_4.w))
  if clock > target_5_threshold:
      pygame.draw.rect(window, BLUE, (target_5.x, target_5.y, target_5.h, target_5.w))
  if clock > target_6_threshold:
      pygame.draw.rect(window, BLUE, (target_6.x, target_6.y, target_6.h, target_6.w))

  #Draw the player sprite. (Module 6)

  pygame.draw.rect(window, BLUE, (player_sprite_x, player_sprite_y, player_sprite_w, player_sprite_h))

  #Draw each item in all_bullets. (Module 7)

  for item in all_bullets:
    pygame.draw.rect(window, BLUE, (item['x']-5, item['y']-5, 10, 10))

  pygame.display.update()

  #tick_busy_loop limits number of times the game can refresh per second. (Module 8)

  py_clock.tick_busy_loop(120)

pygame.quit()

Solution

  • A possibility is to limit the number of bullets. e.g:

    if event.button == 1 and len(all_bullets) < 5:
        dx = event.pos[0] - (player_sprite_x+ player_sprite_w//2)
        dy = event.pos[1] - player_sprite_y
        direction = pygame.math.Vector2(dx, dy).normalize()
        bullet = {'x': player_sprite_x+42, 'y': player_sprite_y, 'direction': direction}
        all_bullets.append(bullet)
    

    An other option would be to ensure that a certain amount of time is passed between 2 clicks. Use pygame.time.get_ticks() to get the current time in milliseconds. When a new bullet has been spawned, then compute the time when the next bullet is allowed to be spawned. Define the minimum time spawn between 2 bullets and init the time point for the 1st bullet by 0:

    bullet_delay = 500 # 0.5 seconds
    next_bullet_time = 0
    

    Get the current time in the application loop:

    current_time = pygame.time.get_ticks()
    

    On click, verify of the current time is grater than next_bullet_time. If the condition is fulfilled, then spawn the bullet and compute the time point for the next bullet:

    if event.button == 1 and current_time > next_bullet_time:
        next_bullet_time = current_time + bullet_delay
    

    e.g.:

    bullet_delay = 500 # 0.5 seconds
    next_bullet_time = 0
    
    while exec:
        current_time = pygame.time.get_ticks()
    
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                exec = False
    
            if event.type == pygame.MOUSEBUTTONDOWN:
    
                if event.button == 1 and current_time > next_bullet_time:
                    next_bullet_time = current_time + bullet_delay
    
                    dx = event.pos[0] - (player_sprite_x+ player_sprite_w//2)
                    dy = event.pos[1] - player_sprite_y
                    direction = pygame.math.Vector2(dx, dy).normalize()
                    bullet = {'x': player_sprite_x+42, 'y': player_sprite_y, 'direction': direction}
                    all_bullets.append(bullet)