Make bullets fire off in the direction the player is facing

Question:

I was just getting some help to figure out how to get my player fire bullets when I realized that they only go (kinda expected this but however as only had y value for movement). I don’t know how I’ll make the bullets fire off in the direction the player is facing.

I have some idea of what to but I just don’t know how to do it… I thought I could somehow use the cursor and player tracking that’s in this game for the visuals but I don’t know how to make that a one-time thing instead of a constant. For diagonal movement of the bullet, I have no clue.

Code below (split into two parts/file Main.py and PlayerSprite.py):

Main:

py.init()
py.mixer.init()
screen = py.display.set_mode((WIDTH, HEIGHT))
py.display.set_caption("Dimensional Drifter")
clock = py.time.Clock()

all_sprites = py.sprite.Group()
NPCs = py.sprite.Group()
bullets = py.sprite.Group()
player = Player()
all_sprites.add(player)
for i in range(14):
    n = NPC(player)
    all_sprites.add(n)
    NPCs.add(n)
# Game loop
running = True
while running:
    # keep loop running at the right speed
    clock.tick(FPS)

    for event in py.event.get():
        # check for closing window
        if event.type == py.QUIT:
            running = False
        elif event.type == py.KEYDOWN:
            if event.key == py.K_SPACE:
                New_bullet = player.Shoot()
                all_sprites.add(New_bullet)
                bullets.add(New_bullet)

    # Update
    all_sprites.update()
    # # check if there a collision between the bullet and NPC
    hits = py.sprite.groupcollide(NPCs, bullets, True, True)
    # check if there a collision between the player and NPC
    hits = py.sprite.spritecollide(player, NPCs, True)
    if hits:
        running = False

    # updates the position of of mouse and rotates it towards the mouse position
    mouse_x, mouse_y = py.mouse.get_pos()
    player.rotate(mouse_x, mouse_y)
    # render
    screen.fill(BLACK)
    all_sprites.draw(screen)
    # flip the display
    py.display.flip()

py.quit()

PlayerSprite

import pygame as py
import math
import random

WIDTH = 800
HEIGHT = 600
FPS = 60

# define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)


class Player(py.sprite.Sprite):
    def __init__(self):
        py.sprite.Sprite.__init__(self)
        self.image = py.Surface((40, 40), py.SRCALPHA)
        self.image.fill(GREEN)
        self.rect = self.image.get_rect()
        self.rect.centerx = WIDTH / 2
        self.rect.bottom = HEIGHT / 2
        self.Yspeed = 0
        self.rotatableimage = self.image

    def update(self):
        self.Xspeed = 0
        self.Yspeed = 0
        # line below allow for key press to equate to move of sprite
        keypreesed = py.key.get_pressed()
        if keypreesed[py.K_a]:
            self.Xspeed = - 11
        if keypreesed[py.K_d]:
            self.Xspeed = 11
        if keypreesed[py.K_w]:
            self.Yspeed = - 11
        if keypreesed[py.K_s]:
            self.Yspeed = 11
        self.rect.x += self.Xspeed
        self.rect.y += self.Yspeed
        # line below allow the sprite to wrap around the screen
        if self.rect.left > WIDTH:
            self.rect.right = 0
        if self.rect.right < 0:
            self.rect.left = WIDTH
        if self.rect.top > HEIGHT:
            self.rect.top = 0
        if self.rect.bottom < 0:
            self.rect.bottom = HEIGHT

    def rotate(self, mouse_x, mouse_y):
        rel_x = mouse_x - self.rect.x
        rel_y = mouse_y - self.rect.y
        angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)

        self.image = py.transform.rotate(self.rotatableimage, int(angle))
        self.rect = self.image.get_rect(center=(self.rect.centerx, self.rect.centery))
        return

    def Shoot(self):
       return Bullet(self.rect.centerx, self.rect.top)



class NPC(py.sprite.Sprite):
    def __init__(self, player):
        py.sprite.Sprite.__init__(self)
        self.player = player
        self.image = py.Surface((30, 30)).convert_alpha()
        self.image.fill(RED)
        self.originalimage = self.image
        self.rect = self.image.get_rect()
        self.spawn()

    # allows of spawning from all four side of the screen and set the x, y speed and spawn position
    def spawn(self):
        self.direction = random.randrange(4)
        if self.direction == 0:
            self.rect.x = random.randrange(WIDTH - self.rect.width)
            self.rect.y = random.randrange(-100, -40)
            self.Xspeed = random.randrange(-2, 2)
            self.Yspeed = random.randrange(4, 8)
        elif self.direction == 1:
            self.rect.x = random.randrange(WIDTH - self.rect.width)
            self.rect.y = random.randrange(HEIGHT, HEIGHT + 60)
            self.Xspeed = random.randrange(-2, 2)
            self.Yspeed = -random.randrange(4, 8)
        elif self.direction == 2:
            self.rect.x = random.randrange(-100, -40)
            self.rect.y = random.randrange(HEIGHT - self.rect.height)
            self.Xspeed = random.randrange(4, 8)
            self.Yspeed = random.randrange(-2, 2)
        elif self.direction == 3:
            self.rect.x = random.randrange(WIDTH, WIDTH + 60)
            self.rect.y = random.randrange(HEIGHT - self.rect.height)
            self.Xspeed = -random.randrange(4, 8)
            self.Yspeed = random.randrange(-2, 2)

    def update(self):
        self.rect.x += self.Xspeed
        self.rect.y += self.Yspeed
        # makes it so that NPC point to wards the player as it passes from side to side
        dir_x, dir_y = self.player.rect.x - self.rect.x, self.player.rect.y - self.rect.y
        self.rot = (180 / math.pi) * math.atan2(-dir_x, -dir_y)
        self.image = py.transform.rotate(self.originalimage, self.rot)
        # Respawns the NPC when they hit an side
        if self.direction == 0:
            if self.rect.top > HEIGHT + 10:
                self.spawn()
        elif self.direction == 1:
            if self.rect.bottom < -10:
                self.spawn()
        elif self.direction == 2:
            if self.rect.left > WIDTH + 10:
                self.spawn()
        elif self.direction == 3:
            if self.rect.right < -10:
                self.spawn()


class Bullet(py.sprite.Sprite):
    def __init__(self, x, y):
        py.sprite.Sprite.__init__(self)
        self.image = py.Surface((5, 5))
        self.image.fill(YELLOW)
        self.rect = self.image.get_rect()
        self.rect.bottom = y
        self.rect.centerx = x
        self.Yspeed = -10

    def update(self):
        self.rect.y += self.Yspeed
        # kill if moved of screen
        if self.rect.bottom > HEIGHT or self.rect.top < 0:
            self.kill()
        if self.rect.right > WIDTH or self.rect.left < 0:
            self.kill()
Asked By: Sir Braindmage

||

Answers:

Add 2 attributes self.lastX and self.lastY to the class Player and change the attributes when the player changes the direction:

class Player(py.sprite.Sprite):
    def __init__(self):
        # [...]

        self.lastX = 0
        self.lastY = -10   

    def update(self):
        # [...]
        self.rect.x += self.Xspeed
        self.rect.y += self.Yspeed

        if self.Xspeed != 0 or self.Yspeed != 0:
            self.lastX = self.Xspeed
            self.lastY = self.Yspeed  

Add an argument Xspeed ans Yspeed to the class Bullet

class Bullet(py.sprite.Sprite):
    def __init__(self, x, y, Xspeed, Yspeed):
        py.sprite.Sprite.__init__(self)
        self.image = py.Surface((5, 5))
        self.image.fill(YELLOW)
        self.rect = self.image.get_rect()
        self.rect.bottom = y
        self.rect.centerx = x
        self.Xspeed = Xspeed
        self.Yspeed = Yspeed

    def update(self):
        self.rect.x += self.Xspeed
        self.rect.y += self.Yspeed
        # [...]

Set the attributes when the bullet spawns

class Player(py.sprite.Sprite):
    # [...]

    def Shoot(self):
       return Bullet(self.rect.centerx, self.rect.centery, self.lastX, self.lastY)

Alternatively it is also possible to set the speed dependent on the direction to the mouse cursor.

Get the position of the player and the mouse cursor and compute the x and y distance (Vector ):

pos = self.rect.center
mpos = py.mouse.get_pos()
vx = mpos[0] - pos[0]
vy = mpos[1] - pos[1]

If the mouse position and the bullet position are equal, that does not make any sense, thus the bullet is skipped

if vx == 0 and vy == 0:
    return None

Of course this vector is far to long, if you would use it for the direction (Xspeed, Yspeed) directly, then the bullet would step to the mouse cursor in one turn.
In the following I use pygame.math.Vector2, because it provides the handy method scale_to_length, that scales a vector to a specified Euclidean length:

direction = py.math.Vector2(vx, vy)
direction.scale_to_length(10)

Now the x and y component of the vector contain the x and y component of the speed. Since the components are floating point values, they are round to integral values:

return Bullet(pos[0], pos[1], round(direction.x), round(direction.y))

Method Shoot:

class Player(py.sprite.Sprite):
    # [...]

    def Shoot(self):
        pos = self.rect.center
        mpos = py.mouse.get_pos()
        vx, vy = mpos[0] - pos[0], mpos[1] - pos[1]
        if vx == 0 and vy == 0:
            return None
        direction = py.math.Vector2(vx, vy)
        direction.scale_to_length(10) 
        return Bullet(pos[0], pos[1], round(direction.x), round(direction.y))

Note, if you set the bullet dependent on the direction to the mouse cursor, then it may be useful to spawn the bullet by a mouse click:

while running:
    # [...]

    for event in py.event.get():
        if event.type == py.QUIT:
            # [...]


        elif event.type == py.MOUSEBUTTONDOWN:
             if event.button == 1:
                New_bullet = player.Shoot()
                if New_bullet:
                    all_sprites.add(New_bullet)
                    bullets.add(New_bullet)
Answered By: Rabbid76

You can use pygames Vector2 to move in any direction. You calculate the angle of the player you can use that.

class Player(py.sprite.Sprite):
    def __init__(self):
        ...
        self.angle = 0

    def rotate(self, mouse_x, mouse_y):
        ...
        self.angle = -angle #make negative otherwise it will be going away from mouse

    def Shoot(self):
         return Bullet(self.rect.centerx, self.rect.top, py.math.Vector2(1,0).rotate(self.angle))

then in your bullet class, get the direction and add to its position

class Bullet(py.sprite.Sprite):
    def __init__(self, x, y, Dir):
        ...
        self.Dir = Dir

    def update(self):
        self.rect.y += self.Dir[1] * self.speed
        self.rect.x += self.Dir[0] * self.speed
        ...
Answered By: The Big Kahuna
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.