1

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
Oscar K.
  • 71
  • 3
  • 9
  • 1
    this problem was few times on stackoverflow. You have to use `cos()`, `sin()` to calculaste `speed_x`, `speed_y` using some constant `speed` - not `from_player_x`, `from_player_y` because it is different when player is closer. You have to use `from_player_x` and `from_player_y` to calculate `angle` or `cos(angle)`,`sin(angle)` – furas Aug 19 '20 at 22:51

1 Answers1

2

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()
furas
  • 134,197
  • 12
  • 106
  • 148