pygame2dmouseangletopdown

How To Make Bullets Move Towards Cursor From Player Point?


I'm currently making a 2d top down game in pygame where the player (ship/cursor) moves with keyboard input and will always look towards the mouse point. I've also made it that when the player presses or holds the SPACE key the ship will shoot upwards from the middle of its rect.

The problem I'm having is that I need the bullets to face and go towards the mouse position. I've been trying to work on it for the past week to figure it out but I don't know how to do this.

What I want is for the bullets (a rectangle image) to go towards the point of the mouse at the time it was fired, and for the bullet to rotate towards the mouse as well like the ship already does. What this means is that even if the mouse moves, I want the bullet to continue in its same path until it hits the edge of the screen and to keep its same angle as before, if that makes sense.

Sorry about the spacing in the code, it just makes it easier for me to recognize what's to do with what.

Any help would be very appriciated :)

import math, pygame

pygame.init()

# === CONSTANTS ===

#window dimensions set into the name 'win'
win = pygame.display.set_mode((1280,800))

#cursor / player ship image
cursor = pygame.image.load("images/cursor.png").convert_alpha()


# === CLASSES ===

#bullet class, holds image and other points
class projectile(object):
    def __init__(self,x,y,radius,color):
        self.x = x
        self.y = y
        self.image = pygame.transform.smoothscale(pygame.image.load('images/bullet.png'), (30,60))
        self.vel = 3

    #gets called in the update_win() to draw the bullet on the screen
    def draw(self,win):
        win.blit(self.image, (self.x,self.y))



# === MAIN FUNCTIONS ===


#keeps the display.update() and other blit code for easier layout
def update_win():

    win.fill((31,27,24))

    for bullet in bullets:
        bullet.draw(win)

    win.blit(rot_image, rot_image_rect.topleft)
    
    pygame.display.update()



#   0 - image is looking to the right
#  90 - image is looking up
# 180 - image is looking to the left
# 270 - image is looking down
correction_angle = 90

cursor_pos = list(win.get_rect().center)

#this is where the bullets go into a list
bullets = []
#for control of how many bullets are fired and at what interval
shoot_loop = 0


# === MAIN LOOP ===

run = True
while run:
    pygame.time.delay(2)
    
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False


    
    #cursor postion and rectangle
    cursor_rect = cursor.get_rect(center = (cursor_pos))
    




    #simple movement / key presses
    keys = pygame.key.get_pressed()
    if keys[pygame.K_a] and cursor_rect.x > -10:
        cursor_pos[0] -= 1
    if keys[pygame.K_d] and cursor_rect.x < 1210:
        cursor_pos[0] += 1
    if keys[pygame.K_w] and cursor_rect.y > -10:
        cursor_pos[1] -= 1
    if keys[pygame.K_s] and cursor_rect.y < 730:
        cursor_pos[1] += 1
    if keys[pygame.K_SPACE]:
        x,y = pygame.mouse.get_pos()
        print(x,y)




    #controls how many bullets are shot (interval)
    if shoot_loop > 0:
        shoot_loop += 0.06
    if shoot_loop > 3:
        shoot_loop = 0


    #will move the bullet image in list and .pop it if it goes above screen
    for bullet in bullets:
        if bullet.y < 800 and bullet.y > -10:
            bullet.y -= bullet.vel  # Moves the bullet by its vel (3)
        else:
            bullets.pop(bullets.index(bullet))  # This will remove the bullet if it is off the screen


    #checks the bullet loop and will add another bullet to the loop if conditions are met
    if keys[pygame.K_SPACE] and shoot_loop == 0:
        if len(bullets) < 100:
            bullets.append(projectile(round(cursor_rect.x + 25), round(cursor_rect.y ), 6, (255,255,255)))

        shoot_loop = 1
        



    







    #calculates mouse position, angle and rotation for image
    mx, my = pygame.mouse.get_pos()
    dx, dy = mx - cursor_rect.centerx, my - cursor_rect.centery
    angle = math.degrees(math.atan2(-dy, dx)) - correction_angle

    #rotated image surface
    rot_image      = pygame.transform.rotate(cursor, angle)
    rot_image_rect = rot_image.get_rect(center = cursor_rect.center)





    update_win()


pygame.quit()
exit()

heres a gif to better understand

gif


Solution

  • Compute the unit direction vector of the bullet in the constructor of the class projectile (a Unit Vector has a lenght of 1):

    mx, my = pygame.mouse.get_pos()
    dx, dy = mx - self.x, my - self.y
    len = math.hypot(dx, dy)
    self.dx = dx / len
    self.dy = dy / len
    

    and rotate the projectile image:

    angle = math.degrees(math.atan2(-dy, dx)) - 90
    self.image = pygame.transform.rotate(self.image, angle)
    

    Add a method move to the class projectile:

    def move(self):
        self.x += self.dx * self.vel
        self.y += self.dy * self.vel
    

    Call move instead of bullet.y -= bullet.vel:

    for bullet in bullets:       
        if -10 < bullet.x < 1200 and -10 < bullet.y < 800:
            bullet.move()
        else:
            bullets.pop(bullets.index(bullet))
    

    Changes:

    class projectile(object):
        def __init__(self,x,y,radius,color):
            self.x = x
            self.y = y
            self.image = pygame.transform.smoothscale(pygame.image.load('images/bullet.png'), (30,60))
            self.vel = 3
            
            mx, my = pygame.mouse.get_pos()
            dx, dy = mx - self.x, my - self.y
            len = math.hypot(dx, dy)
            self.dx = dx / len
            self.dy = dy / len
    
            angle = math.degrees(math.atan2(-dy, dx)) - 90
            self.image = pygame.transform.rotate(self.image, angle)
    
        def move(self):
            self.x += self.dx * self.vel
            self.y += self.dy * self.vel
    
        def draw(self,win):
            win.blit( self.image, (round(self.x), round(self.y)))
    
    run = True
    while run:
        # [...]
    
        #will move the bullet image in list and .pop it if it goes above screen
        for bullet in bullets:       
            if -10 < bullet.x < 1200 and -10 < bullet.y < 800:
                bullet.move()
            else:
                bullets.pop(bullets.index(bullet))  # This will remove the bullet if it is off the screen
    
        # [...]