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
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()