How can I shoot a bullet with the space bar?

Question:

Here’s my code:

import pygame, os

os.environ["SDL_VIDEO_CENTERED"] = "1"
pygame.init()

win = pygame.display
d = win.set_mode((1200, 600))

class player:
    def __init__(self, x, y, height, width):
        self.x = x
        self.y = y
        self.height = height
        self.width = width
        self.speed = 2

    def draw(self):
        pygame.draw.rect(d, (0, 0, 0), (self.x, self.y, self.width, self.height))

    def move_left(self):
        self.x -= self.speed

    def move_right(self):
        self.x += self.speed


class bullet:
    def __init__(self):
        self.radius = 10
        self.speed = 20


    def shoot(self):
        x = p.x
        y = p.y
        self.shooting = True
        while self.shooting:
            d.fill((98, 98, 98))
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    quit()

            y -= self.speed
            pygame.draw.circle(d, (255, 0, 0), (x, y), self.radius)
            pygame.time.Clock().tick(100)
            win.update()

            if y <= 0:
                self.shooting = False


b = bullet()
p = player(600, 500, 50, 30)
while True:
    d.fill((98, 98, 98))
    p.draw()
    for event in pygame.event.get():
        pass

    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_SPACE:
            b.shoot()
        if event.key == pygame.K_LEFT:
            p.move_left()
        if event.key == pygame.K_RIGHT:
            p.move_right()

    win.update()

This is what I could come up with after a few trial and errors, but it is really ineffective. Firstly, the player disappears when I press the space bar. I guess this is obvious as I have a different loops for shooting and player, but I don’t know how to get around it and implement both shooting and player in the same loop.

The second problem I am having is breaking the while self.shooting: loop. I tried breaking it when y reaches a certain point by doing this:

 if y <= 0:
     self.shooting = False

But this doesn’t break. Instead, it restarts the loop all over again.

Another weird problem I am having is that every time I move the mouse (slightly fast) or press a bunch of buttons at once, it breaks the while self.shooting loop.

Asked By: user12291970

||

Answers:

The general approach to firing bullets is to store the positions of the bullets in a list (bullet_list). When a bullet is fired, add the bullet’s starting position ([start_x, start_y]) to the list. The starting position is the position of the object (player or enemy) that fires the bullet. Use a for-loop to iterate through all the bullets in the list. Move position of each individual bullet in the loop. Remove a bullet from the list that leaves the screen (bullet_list.remove(bullet_pos)). For this reason, a copy of the list (bullet_list[:]) must be run through (see How to remove items from a list while iterating?). Use another for-loop to blit the remaining bullets on the screen:

bullet_list = []

while run == True:
    # [...]

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

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                bullet_list.append([start_x, start_y])

    for bullet_pos in bullet_list[:]:
        bullet_pos[0] += move_bullet_x
        bullet_pos[1] += move_bullet_y
        if not screen.get_rect().colliderect(bullet_image.get_rect(center = bullet_pos))
            bullet_list.remove(bullet_pos)

    # [...]

    for bullet_pos in bullet_list[:]
        screen.blit(bullet_image, bullet_image.get_rect(center = bullet_pos))

    # [...]

See also Shoot bullet.


Please note, that class names should normally use the CapWords convention.
(See Style Guide for Python Code – Class names)
That means it has to be Player and Bullet rather than player and bullet

You have an application loop, so use it. All the objects are continuously updated and drawn in the main application loop, in each frame.

The class Bullet do not need any loop. The constructor has to have parameters for the position (x, y). Further it needs on method which changes the position and one which draws the bullet:

class Bullet:
    def __init__(self, x, y):
        self.radius = 10
        self.speed = 10
        self.x = x
        self.y = y

    def update(self):
        self.y -= self.speed#

    def draw(self):
        pygame.draw.circle(d, (255, 0, 0), (self.x, self.y), self.radius)

Use a list of bullets. Create a new bullet when space is pressed. Move the bullets (update) in every frame an remove a bullet if it is out of the window. Draw the remaining bullets in every frame:

bullets = []

# [...]
while run:

    for event in pygame.event.get():
        # [...]
        if event.type ==  pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                bullets.append(Bullet(p.x+p.width//2, p.y))

    # [...]
    for b in bullets:
        b.update()
        if b.y < 0:
            bullets.remove(b)

    # [...]
    for b in bullets:
        b.draw()

Furthermore use pygame.key.get_pressed() use to get the state of the keys in every frame and to update the position of the player:

while run:

    # [...]

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        p.move_left()
    if keys[pygame.K_RIGHT]:
        p.move_right()

Complete example:

import pygame, os

os.environ["SDL_VIDEO_CENTERED"] = "1"
pygame.init()

win = pygame.display
d = win.set_mode((1200, 600))
clock = pygame.time.Clock()

class Player:
    def __init__(self, x, y, height, width):
        self.x = x
        self.y = y
        self.height = height
        self.width = width
        self.speed = 2

    def draw(self):
        pygame.draw.rect(d, (0, 0, 0), (self.x, self.y, self.width, self.height))

    def move_left(self):
        self.x -= self.speed

    def move_right(self):
        self.x += self.speed


class Bullet:
    def __init__(self, x, y):
        self.radius = 10
        self.speed = 10
        self.x = x
        self.y = y

    def update(self):
        self.y -= self.speed#
    
    def draw(self):
        pygame.draw.circle(d, (255, 0, 0), (self.x, self.y), self.radius)


bullets = []
p = Player(600, 500, 50, 30) 

run = True
while run:
    clock.tick(100)
        
    # handel events
    for event in pygame.event.get():
        if event.type ==  pygame.QUIT:
            run = False
        if event.type ==  pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                bullets.append(Bullet(p.x+p.width//2, p.y))

    # update objects
    keys = pygame.key.get_pressed()        
    if keys[pygame.K_LEFT]:
        p.move_left()
    if keys[pygame.K_RIGHT]:
        p.move_right()
    for b in bullets:
        b.update()
        if b.y < 0:
            bullets.remove(b)

    # clear display
    d.fill((98, 98, 98))

    # draw scene
    for b in bullets:
        b.draw()
    p.draw()

    # update display
    win.update()
Answered By: Rabbid76
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.