Search code examples
pythonpygamecursor

How to shoot a bullet towards mouse cursor in pygame


I'm trying to create a top down shooter in pygame. I figured out how to shoot bullets towards the cursor, but I've got a problem with my implementation. When the mouse cursor is on or very close to the player the bullets that are shot out are really slow. I don't know why. How do I fix this?

My code:

mouse_x, mouse_y = pygame.mouse.get_pos()
from_player_x, from_player_y = mouse_x - self.rect.x, mouse_y - self.rect.y
x_speed, y_speed = round(from_player_x * 0.1), round(from_player_y * 0.1)
self.speed_x = x_speed
self.speed_y = y_speed
...
self.rect.x += self.speed_x
self.rect.y += self.speed_y

Solution

  • To calculate speed correctly you can't use directly from_player_x and from_player_y but use it to calculate angle and then use sin(), cos() to calculate speed_x, speed_y

        player = pygame.Rect(screen_rect.centerx, screen_rect.bottom, 0, 0)
        SPEED = 5
        
        #---
    
        mouse_x, mouse_y = pygame.mouse.get_pos()
        
        distance_x = mouse_x - player.x
        distance_y = mouse_y - player.y
        
        angle = math.atan2(distance_y, distance_x)
        
        speed_x = SPEED * math.cos(angle)
        speed_y = SPEED * math.sin(angle)
    

    Minimal working example

    import pygame
    import math
    
    # === CONSTANS === (UPPER_CASE names)
    
    BLACK = (  0,   0,   0)
    WHITE = (255, 255, 255)
    
    RED   = (255,   0,   0)
    GREEN = (  0, 255,   0)
    BLUE  = (  0,   0, 255)
    
    SCREEN_WIDTH  = 600
    SCREEN_HEIGHT = 400
    
    # === MAIN === (lower_case names)
    
    # --- init ---
    
    pygame.init()
    
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    screen_rect = screen.get_rect()
    
    # --- objects ---
    
    player = pygame.Rect(screen_rect.centerx, screen_rect.bottom, 0, 0)
    start = pygame.math.Vector2(player.center)
    end = start
    length = 50
    
    SPEED = 5
    
    all_bullets = []
    
    # --- mainloop ---
    
    clock = pygame.time.Clock()
    is_running = True
    
    
    while is_running:
    
        # --- events ---
    
        for event in pygame.event.get():
    
            # --- global events ---
    
            if event.type == pygame.QUIT:
                is_running = False
    
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    is_running = False
            
            elif event.type == pygame.MOUSEMOTION:
                mouse = pygame.mouse.get_pos()
                end = start + (mouse - start).normalize() * length
            
            elif event.type == pygame.MOUSEBUTTONDOWN:
                mouse_x, mouse_y = pygame.mouse.get_pos()
                
                distance_x = mouse_x - player.x
                distance_y = mouse_y - player.y
                
                angle = math.atan2(distance_y, distance_x)
                
                # speed_x, speed_y can be `float` but I don't convert to `int` to get better position
                speed_x = SPEED * math.cos(angle)
                speed_y = SPEED * math.sin(angle)
                
                # I copy `player.x, player.y` because I will change these values directly on list
                all_bullets.append([player.x, player.y, speed_x, speed_y])
                
            # --- objects events ---
    
                # empty
    
        # --- updates ---
    
        # move using speed - I use indexes to change directly on list
        for item in all_bullets:
            # speed_x, speed_y can be `float` but I don't convert to `int` to get better position
            item[0] += item[2]  # pos_x += speed_x
            item[1] += item[3]  # pos_y -= speed_y
    
        # --- draws ---
    
        screen.fill(BLACK)
    
        pygame.draw.line(screen, RED, start, end)
    
        for pos_x, pos_y, speed_x, speed_y in all_bullets:
            # need to convert `float` to `int` because `screen` use only `int` values
            pos_x = int(pos_x)
            pos_y = int(pos_y)
            pygame.draw.line(screen, (0,255,0), (pos_x, pos_y), (pos_x, pos_y))
            
        pygame.display.update()
    
        # --- FPS ---
    
        clock.tick(25)
    
    # --- the end ---
    
    pygame.quit()
    

    PyGame has module pygame.math and object Vector2 which can make calculation simpler

        player = pygame.Rect(screen_rect.centerx, screen_rect.bottom, 0, 0)
        start = pygame.math.Vector2(player.center)
    
        SPEED = 5
    
        # ---
    
        mouse = pygame.mouse.get_pos()
    
        distance = mouse - start
    
        position = pygame.math.Vector2(start) # duplicate # start position in start of canon
        #position = pygame.math.Vector2(end)   # duplicate # start position in end of canon
    
        speed = distance.normalize() * SPEED
    

    and later

        position += speed
    

    Minimal working example

    import pygame
    
    # === CONSTANS === (UPPER_CASE names)
    
    BLACK = (  0,   0,   0)
    WHITE = (255, 255, 255)
    
    RED   = (255,   0,   0)
    GREEN = (  0, 255,   0)
    BLUE  = (  0,   0, 255)
    
    SCREEN_WIDTH  = 600
    SCREEN_HEIGHT = 400
    
    # === MAIN === (lower_case names)
    
    # --- init ---
    
    pygame.init()
    
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    screen_rect = screen.get_rect()
    
    # --- objects ---
    
    player = pygame.Rect(screen_rect.centerx, screen_rect.bottom, 0, 0)
    start = pygame.math.Vector2(player.center)
    end = start
    length = 50
    
    SPEED = 5
    
    all_bullets = []
    
    # --- mainloop ---
    
    clock = pygame.time.Clock()
    is_running = True
    
    
    while is_running:
    
        # --- events ---
    
        for event in pygame.event.get():
    
            # --- global events ---
    
            if event.type == pygame.QUIT:
                is_running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    is_running = False
            elif event.type == pygame.MOUSEMOTION:
                mouse = pygame.mouse.get_pos()
                end = start + (mouse - start).normalize() * length
            elif event.type == pygame.MOUSEBUTTONDOWN:
                mouse = pygame.mouse.get_pos()
    
                distance = mouse - start
    
                position = pygame.math.Vector2(start) # duplicate # start position in start of canon
                #position = pygame.math.Vector2(end)   # duplicate # start position in end of canon
                speed = distance.normalize() * SPEED
                
                all_bullets.append([position, speed])
                
            # --- objects events ---
    
                # empty
    
        # --- updates ---
    
        for position, speed in all_bullets:
            position += speed
    
        # --- draws ---
    
        screen.fill(BLACK)
    
        pygame.draw.line(screen, RED, start, end)
    
        for position, speed in all_bullets:
            # need to convert `float` to `int` because `screen` use only `int` values
            pos_x = int(position.x)
            pos_y = int(position.y)
            pygame.draw.line(screen, (0,255,0), (pos_x, pos_y), (pos_x, pos_y))
            
        pygame.display.update()
    
        # --- FPS ---
    
        clock.tick(25)
    
    # --- the end ---
    
    pygame.quit()