4

I know there have been several topics on this but I still can't figure out how to make my ship shoot bullets.. I want to add to my MOUSEBUTTONDOWN bullets shooting from the ship as the sound effect plays. thanks for the help!

import sys, pygame, pygame.mixer
from pygame.locals import *

pygame.init()

size = width, height = 800, 600
screen = pygame.display.set_mode(size)

clock = pygame.time.Clock()

background = pygame.image.load("bg.png")
ship = pygame.image.load("ship.png")
ship = pygame.transform.scale(ship,(64,64))

shot = pygame.mixer.Sound("shot.wav")
soundin = pygame.mixer.Sound("sound.wav")

soundin.play()

while 1:
  for event in pygame.event.get():
    if event.type == pygame.QUIT:
      sys.exit()

    elif event.type == MOUSEBUTTONDOWN:
      shot.play()

  clock.tick(60)

  mx,my = pygame.mouse.get_pos()

  screen.blit(background,(0,0))
  screen.blit(ship,(mx-32,500))
  pygame.display.flip()
user3273323
  • 51
  • 1
  • 1
  • 5

2 Answers2

9

There are several steps you have to go through to do this. You will need a picture of the bullet, a way to store the locations of the bullets, a way to create the bullets, a way to render the bullets, and a way to update the bullets. You appear to know how to import pictures already, so I'll skip that part.

There are several ways that you can store pieces of information. I will be using a list of the top left corner of the bullets. Create the list anywhere before the final loop with bullets = [].

To create the bullets, you will want to use the location of the mouse. Add in bullets.append([event.pos[0]-32, 500]) after shot.play(), indented the same amount.

To render the bullets, you will be adding a for loop into your game loop. After the line screen.blit(background, (0, 0)), add the following code:

for bullet in bullets:
    screen.blit(bulletpicture, pygame.Rect(bullet[0], bullet[1], 0, 0)

To update the bullets, you need to put something somewhere in your game loop that looks like this:

for b in range(len(bullets)):
    bullets[b][0] -= 10

Finally, you need to remove the bullets when they reach the top of the screen. Add this after the for loop you just created (iterate over a slice copy, because lists shouldn't be modified during the iteration):

for bullet in bullets[:]:
    if bullet[0] < 0:
        bullets.remove(bullet)

After putting this all into your code, it should look something like this:

import sys, pygame, pygame.mixer
from pygame.locals import *

pygame.init()

size = width, height = 800, 600
screen = pygame.display.set_mode(size)

clock = pygame.time.Clock()

bullets = []

background = pygame.image.load("bg.png").convert()
ship = pygame.image.load("ship.png").convert_alpha()
ship = pygame.transform.scale(ship, (64, 64))
bulletpicture = pygame.image.load("You know what to do").convert_alpha()

shot = pygame.mixer.Sound("shot.wav")
soundin = pygame.mixer.Sound("sound.wav")

soundin.play()

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

        elif event.type == MOUSEBUTTONDOWN:
            shot.play()
            bullets.append([event.pos[0]-32, 500])

    clock.tick(60)

    mx, my = pygame.mouse.get_pos()

    for b in range(len(bullets)):
        bullets[b][0] -= 10

    # Iterate over a slice copy if you want to mutate a list.
    for bullet in bullets[:]:
        if bullet[0] < 0:
            bullets.remove(bullet)

    screen.blit(background, (0, 0))

    for bullet in bullets:
        screen.blit(bulletpicture, pygame.Rect(bullet[0], bullet[1], 0, 0))

    screen.blit(ship, (mx-32, 500))
    pygame.display.flip()

If both you and I have done everything correctly, this should give you functioning bullets. Please, don't hesitate to ask me any questions if you don't understand what is going on or if something doesn't work.

Note that images/pygame.Surfaces should be converted with the convert or convert_alpha methods to improve the performance.

skrx
  • 19,980
  • 5
  • 34
  • 48
ThisIsAQuestion
  • 1,887
  • 14
  • 20
  • After studying the code for a while I am to the point now that I think I understand the majority of what is happening but I am only able to get the bullets to shoot left or right and I'd like to shoot them towards the top of the screen. Also for some reason the bullets aren't being deleted and after a minute or two they fly back in. Thanks again! – user3273323 Feb 05 '14 at 13:31
  • @user3273323 The bullets' location is being stored with a list [x, y], so all you have to do is change `bullets[b][0]-=10` to `bullets[b][1]-=10`. Note that you can change -= to += to make it fly in different directions, and you can change 10 to change the speed. In the for loop after that line, the if statement will change based what was on the line just discussed. [0] should change to whatever you have after [b] on the previous line, and < should be used only if you are using -=. Use > otherwise. – ThisIsAQuestion Feb 05 '14 at 15:05
  • @user3273323 After reviewing my code, I realized something that you probably already did: in the line `bullets.append([event.pos[0]-32, 500])` the -32 is unnecessary. Taking it out will make the bullet be created in the center of the ship rather than the edge. Just thought I'd let you know. – ThisIsAQuestion Feb 05 '14 at 15:07
  • Thank you so much, now I better understand how that works I knew I somehow needed to change from x to y but could not for the life of me figure that out but I did get the += and -= changing left and right. Hope I get good enough at this to pay it forward.. Also any idea why the bullets will eventually come back from the bottom of the screen as though they are scrolling from one edge around to the other instead of being deleted from the list? btw I did change "event.pos[0]-32" to "mx" which was my variable for mouse x. – user3273323 Feb 05 '14 at 17:51
  • @user3273323 did you make sure to change (in the line `if bullet[0]<0:`) [0] to [1] if you are changing the y and < to > and 0 to the size of the screen in the dimension in which the bullet is moving? If you have and it still doesn't work, send me all of the new code in the for loop and I'll take a look at it. – ThisIsAQuestion Feb 05 '14 at 19:58
  • Note that lists shouldn't be modified while you're iterating over them. In this example it's not very important because the bullets will still be removed, but sometimes a few frames later. It would be better to iterate over a copy of the list `for bullet in bullets[:]:`. – skrx Aug 21 '17 at 07:04
5

Here's an example that demonstrates how pygame.sprite.Sprites and pygame.sprite.Groups can be used to create bullets. It also shows how to shoot continuously with the help of a timer variable and the dt (delta time) that clock.tick returns.

pygame.sprite.groupcollide is used for the collision detection between the bullets and the enemies sprite group. I iterate over the items in the hits dict to reduce the health points of the enemies.

import random
import pygame as pg


pg.init()

BG_COLOR = pg.Color('gray12')
PLAYER_IMG = pg.Surface((30, 50), pg.SRCALPHA)
pg.draw.polygon(PLAYER_IMG, pg.Color('dodgerblue'), [(0, 50), (15, 0), (30, 50)])
ENEMY_IMG = pg.Surface((50, 30))
ENEMY_IMG.fill(pg.Color('darkorange1'))
BULLET_IMG = pg.Surface((9, 15))
BULLET_IMG.fill(pg.Color('aquamarine2'))


class Player(pg.sprite.Sprite):

    def __init__(self, pos, all_sprites, bullets):
        super().__init__()
        self.image = PLAYER_IMG
        self.rect = self.image.get_rect(center=pos)
        self.all_sprites = all_sprites
        self.add(self.all_sprites)
        self.bullets = bullets
        self.bullet_timer = .1

    def update(self, dt):
        self.rect.center = pg.mouse.get_pos()

        mouse_pressed = pg.mouse.get_pressed()
        self.bullet_timer -= dt  # Subtract the time since the last tick.
        if self.bullet_timer <= 0:
            self.bullet_timer = 0  # Bullet ready.
            if mouse_pressed[0]:  # Left mouse button.
                # Create a new bullet instance and add it to the groups.
                Bullet(pg.mouse.get_pos(), self.all_sprites, self.bullets)
                self.bullet_timer = .1  # Reset the timer.


class Enemy(pg.sprite.Sprite):

    def __init__(self, pos, *sprite_groups):
        super().__init__(*sprite_groups)
        self.image = ENEMY_IMG
        self.rect = self.image.get_rect(center=pos)
        self.health = 30

    def update(self, dt):
        if self.health <= 0:
            self.kill()


class Bullet(pg.sprite.Sprite):

    def __init__(self, pos, *sprite_groups):
        super().__init__(*sprite_groups)
        self.image = BULLET_IMG
        self.rect = self.image.get_rect(center=pos)
        self.pos = pg.math.Vector2(pos)
        self.vel = pg.math.Vector2(0, -450)
        self.damage = 10

    def update(self, dt):
        # Add the velocity to the position vector to move the sprite.
        self.pos += self.vel * dt
        self.rect.center = self.pos  # Update the rect pos.
        if self.rect.bottom <= 0:
            self.kill()


class Game:

    def __init__(self):
        self.clock = pg.time.Clock()
        self.screen = pg.display.set_mode((800, 600))

        self.all_sprites = pg.sprite.Group()
        self.enemies = pg.sprite.Group()
        self.bullets = pg.sprite.Group()
        self.player = Player((0, 0), self.all_sprites, self.bullets)

        for i in range(15):
            pos = (random.randrange(30, 750), random.randrange(500))
            Enemy(pos, self.all_sprites, self.enemies)

        self.done = False

    def run(self):
        while not self.done:
            # dt = time since last tick in milliseconds.
            dt = self.clock.tick(60) / 1000
            self.handle_events()
            self.run_logic(dt)
            self.draw()

    def handle_events(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.done = True

    def run_logic(self, dt):
        self.all_sprites.update(dt)

        # hits is a dict. The enemies are the keys and bullets the values.
        hits = pg.sprite.groupcollide(self.enemies, self.bullets, False, True)
        for enemy, bullet_list in hits.items():
            for bullet in bullet_list:
                enemy.health -= bullet.damage

    def draw(self):
        self.screen.fill(BG_COLOR)
        self.all_sprites.draw(self.screen)
        pg.display.flip()


if __name__ == '__main__':
    Game().run()
    pg.quit()
skrx
  • 19,980
  • 5
  • 34
  • 48
  • To learn how sprites, sprite groups and classes work check out [Program Arcade Games](http://programarcadegames.com/index.php?chapter=introduction_to_sprites&lang=en#section_13) (chapter 12 is about classes). – skrx Aug 21 '17 at 07:43
  • If you want to shoot in arbitrary directions, I recommend using vectors. Here's an example: https://stackoverflow.com/a/42281315/6220679 – skrx Nov 17 '18 at 12:03
  • A much better approach. – Was' Nov 16 '22 at 15:03