Shooting a Bullet in Pygame in the Direction of Mouse

Shooting a bullet in pygame in the direction of mouse

First of all pygame.transform.rotate does not transform the object itself, but creates a new rotated surface and returns it.

If you want to fire a bullet in a certain direction, the direction is defined the moment the bullet is fired, but it does not change continuously.
When the bullet is fired, set the starting position of the bullet and calculate the direction vector to the mouse position:

self.pos = (x, y)
mx, my = pygame.mouse.get_pos()
self.dir = (mx - x, my - y)

The direction vector should not depend on the distance to the mouse, but it has to be a Unit vector.
Normalize the vector by dividing by the Euclidean distance

length = math.hypot(*self.dir)
if length == 0.0:
self.dir = (0, -1)
else:
self.dir = (self.dir[0]/length, self.dir[1]/length)

Compute the angle of the vector and rotate the bullet. In general, the angle of a vector can be computed by atan2(y, x). The y-axis needs to be reversed (atan2(-y, x)) as the y-axis generally points up, but in the PyGame coordinate system the y-axis points down (see How to know the angle between two points?):

angle = math.degrees(math.atan2(-self.dir[1], self.dir[0]))

self.bullet = pygame.Surface((7, 2)).convert_alpha()
self.bullet.fill((255, 255, 255))
self.bullet = pygame.transform.rotate(self.bullet, angle)

To update the position of the bullet, it is sufficient to scale the direction (by a velocity) and add it to the position of the bullet:

self.pos = (self.pos[0]+self.dir[0]*self.speed, 
self.pos[1]+self.dir[1]*self.speed)

To draw the rotated bullet in the correct position, take the bounding rectangle of the rotated bullet and set the center point with self.pos (see How do I rotate an image around its center using PyGame?):

bullet_rect = self.bullet.get_rect(center = self.pos)
surf.blit(self.bullet, bullet_rect)

See also Shoot bullets towards target or mouse


Minimal example: Sample Image repl.it/@Rabbid76/PyGame-FireBulletInDirectionOfMouse

Sample Image

import pygame
import math

pygame.init()
window = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()

class Bullet:
def __init__(self, x, y):
self.pos = (x, y)
mx, my = pygame.mouse.get_pos()
self.dir = (mx - x, my - y)
length = math.hypot(*self.dir)
if length == 0.0:
self.dir = (0, -1)
else:
self.dir = (self.dir[0]/length, self.dir[1]/length)
angle = math.degrees(math.atan2(-self.dir[1], self.dir[0]))

self.bullet = pygame.Surface((7, 2)).convert_alpha()
self.bullet.fill((255, 255, 255))
self.bullet = pygame.transform.rotate(self.bullet, angle)
self.speed = 2

def update(self):
self.pos = (self.pos[0]+self.dir[0]*self.speed,
self.pos[1]+self.dir[1]*self.speed)

def draw(self, surf):
bullet_rect = self.bullet.get_rect(center = self.pos)
surf.blit(self.bullet, bullet_rect)

bullets = []
pos = (250, 250)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN:
bullets.append(Bullet(*pos))

for bullet in bullets[:]:
bullet.update()
if not window.get_rect().collidepoint(bullet.pos):
bullets.remove(bullet)

window.fill(0)
pygame.draw.circle(window, (0, 255, 0), pos, 10)
for bullet in bullets:
bullet.draw(window)
pygame.display.flip()

How do you shoot a bullet towards mouse in pygame at a constant speed?

Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.

The coordinates for Rect objects are all integers. [...]

The fraction part of the coordinates gets lost when the new position of the object is assigned to the Rect object.

You have to do it the other way around. Change self.x_loc and self.y_loc, but update self.bullet_rect.center:

class Bullet:
# [...]

def update(self):
self.x_loc += self.x_move
self.y_loc += self.y_move
self.bullet_rect.center = round(self.x_loc), round(self.y_loc)

rect = screen.blit(self.image, self.bullet_rect)
if not screen.get_rect().contains(rect):
bullets.remove(self)

See also these questions:
Shooting a bullet in pygame in the direction of mouse and How can you rotate the sprite and shoot the bullets towards the mouse position.

How to shoot a bullet towards mouse cursor in pygame

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

Pygame- make bullet shoot toward cursor direction

See Shooting a bullet in pygame in the direction of mouse and calculating direction of the player to shoot pygame.
The issue is that you calculate the the direction of the bullet by vector from the current position of the bullet to the target point (bullet.targetX, bullet.targetY).

Once the bullet reaches the target, then this direction is (0, 0) and the bullet doesn't move any more.

Don't store the target position in bullet. Store the initial direction vector instead. e.g.:

bullet.diffX = targetX - bullet.x
bullet.diffY = targetY - bullet.y
ang = math.atan2(bullet.diffY, bullet.diffX)
bullet.x += math.cos(ang)*bullet.vel
bullet.y += math.sin(ang)*bullet.vel

Use pygame.Rect and .collidepoint() to verify if the bullet is inside the window:

for bullet in bullets:
if pygame.Rect(0, 0, 1000, 800).collidepoint(bullet.x, bullet.y):
# [...]

Or even .colliderect:

for bullet in bullets:
radius = 5
bullet_rect = pygame.Rect(-radius, -radius, radius, radius);
bullet_rect.center = (bullet.x, bullet.y)
if pygame.Rect(0, 0, 1000, 800).colliderect(bullet_rect):
# [...]

I recommend to use pygame.math.Vector2 for calculation of the movement of the bullet e.g.:

bullet.pos = pg.math.Vector2(bullet.x, bullet.y)
bullet.dir = pg.math.Vector2(targetX, targetY) - bullet.pos
bullet.dir = bullet.dir.normalize()
for bullet in bullets:

if #[...]

bullet.pos += bullet.dir * bullet.vel
bullet.x, bullet.y = (round(bullet.pos.x), round(bullet.pos.y))

How to shoot bullets from a character facing in direction of cursor in pygame?

See Shooting a bullet in pygame in the direction of mouse and calculating direction of the player to shoot pygame.
Pass the mouse position to rotator.shoot(), when the mouse button is pressed:

if event.type == pg.MOUSEBUTTONDOWN:
rotator.shoot(event.pos)

Calculate the direction of from the rotator to the mouse position and pass it the constructor of the new bullet object:

def shoot(self, mousepos):
dx = mousepos[0] - self.rect.centerx
dy = mousepos[1] - self.rect.centery
if abs(dx) > 0 or abs(dy) > 0:
bullet = Bullet(self.rect.centerx, self.rect.centery, dx, dy)
all_sprites.add(bullet)
bullets.add(bullet)

Use pygame.math.Vector2 to store the current positon of the bullet and the normalized direction of the bullet (Unit vector):

class Bullet(pg.sprite.Sprite):
def __init__(self, x, y, dx, dy):
pg.sprite.Sprite.__init__(self)
self.image = pg.transform.smoothscale(pg.image.load('bullet.png').convert_alpha(), (10,10))
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.speed = 8
self.pos = pg.math.Vector2(x, y)
self.dir = pg.math.Vector2(dx, dy).normalize()

Calcualate the new position of the bullet in update() (self.pos += self.dir * self.speed) and update the .rect attribute by the new position.

.kill() the bullet when it leaves the screen. This can be checked by self.rect.colliderect():

class Bullet(pg.sprite.Sprite):

# [...]

def update(self):

self.pos += self.dir * self.speed
self.rect.center = (round(self.pos.x), round(self.pos.y))

if not self.rect.colliderect(0, 0, width, height):
self.kill()

How do I make the bullets to shoot towards my mouse when I leftclick in this code?

When you click the mouse, you need to calculate the normalized direction vector (Unit vector) from the start position of the bullet to the mouse position. Define the speed of the bullet and multiply the direction vector by the speed:

dx = event.pos[0] - bulletX
dy = event.pos[1] - bulletY
dist = math.sqrt(dx*dx + dy*dy)
bulletX_change = bullet_speed * dx/dist
bulletY_change = bullet_speed * dy/dist

relevant changes:

def fire_bullet(x, y):
screen.blit(bulletImg, (x + 35, y + 10))

running = True
while running:
# [...]

for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# [...]

if event.type == pygame.MOUSEBUTTONUP and event.button == LEFT:
if bullet_state == "ready":
#bullet_Sound = mixer.Sound('.wav')
#bullet_Sound.play()
bulletX = playerX
bulletY = playerY
bullet_state = "fire"
dx = event.pos[0] - bulletX
dy = event.pos[1] - bulletY
dist = math.sqrt(dx*dx + dy*dy)
bulletX_change = bullet_speed * dx/dist
bulletY_change = bullet_speed * dy/dist

# [...]

if playerX < 0 or bulletY < 0 or playerX > 1280 or bulletY > 720:
bullet_state = "ready"
if bullet_state == "fire":
bulletX += bulletX_change
bulletY += bulletY_change
fire_bullet(bulletX, bulletY)

pygame.display.update()

How To Make Bullets Move Towards Cursor From Player Point?

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

Sample Image

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

# [...]

How Can I Shoot Projectiles with my mouse at any position?

You have to add the moving direction (dirx, diry) to the class projectile. Further add a method which moves the bullet:

class projectile(object):
def __init__(self, x, y, dirx, diry, color):
self.x = x
self.y = y
self.dirx = dirx
self.diry = diry
self.slash = pygame.image.load("heart.png")
self.rect = self.slash.get_rect()
self.rect.topleft = ( self.x, self.y )
self.speed = 10
self.color = color

def move(self):
self.x += self.dirx * self.speed
self.y += self.diry * self.speed

def draw(self, window):
self.rect.topleft = (round(self.x), round(self.y))

window.blit(slash, self.rect)

Compute the direction form the player to the mouse when the mouse button is pressed and spawn a new bullet.
The direction is given by the vector form the player to the muse position (mouse_x - start_x, mouse_y - start_y).
The vector has to be normalized (Unit vector) by dividing the vector components by the Euclidean distance:

for event in pygame.event.get():
# [...]

if event.type == pygame.MOUSEBUTTONDOWN:

if len(bullets) < 2:

start_x, start_y = playerman.x+playerman.width//2, playerman.y + playerman.height-54
mouse_x, mouse_y = event.pos

dir_x, dir_y = mouse_x - start_x, mouse_y - start_y
distance = math.sqrt(dir_x**2 + dir_y**2)
if distance > 0:
new_bullet = projectile(start_x, start_y, dir_x/distance, dir_y/distance, (0,0,0))
bullets.append(new_bullet)

Move the bullets in a loop in te main application loop and remove the bullet if it is out of the window

run = True
while run:

# [...]

for event in pygame.event.get():
# [...]

for bullet in bullets[:]:
bullet.move()

if bullet.x < 0 or bullet.x > 500 or bullet.y < 0 or bullet.y > 500:
bullets.pop(bullets.index(bullet))

# [...]

Example code

runninggame = True
while runninggame:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
runninggame = False

if event.type == pygame.MOUSEBUTTONDOWN:

if len(bullets) < 2:

start_x, start_y = playerman.x+playerman.width//2, playerman.y + playerman.height-54
mouse_x, mouse_y = event.pos

dir_x, dir_y = mouse_x - start_x, mouse_y - start_y
distance = math.sqrt(dir_x**2 + dir_y**2)
if distance > 0:
new_bullet = projectile(start_x, start_y, dir_x/distance, dir_y/distance, (0,0,0))
bullets.append(new_bullet)

for bullet in bullets[:]:
bullet.move()
if bullet.x < 0 or bullet.x > 800 or bullet.y < 0 or bullet.y > 800:
bullets.pop(bullets.index(bullet))

# [...]

All the bullets are moving when I move the mouse instead of just the one being shot

The bullets not being deleted when they pass x=0, simply because you don't cull them on "bad-x", as the "else pop" clause is only called on "bad-y". It's easy to fix:

    def playerBulletUpdates():
for bullet in bullets:
if bullet.x >= 0 and bullet.x <= width and \
bullet.y >= 0 and bullet.y <= height:
...
else:
# Bullet has gone off-screen
bullets.pop(bullets.index(bullet))

The mouse-movement of existing bullets is because, during this same loop, you re-compute the direction vectors for every bullet, not just the new one.

Your Projectile class needs to store its direction vector during initialisation - one for every bullet, as it is probably different for every bullet. So I would re-work the constructor here, then add an update() function to handle the continued movement.

class Projectile:
def __init__(self, x, y, dx, dy, radius):
self.x = x
self.y = y
self.radius = radius
self.dx = dx
self.dy = dy

def draw(self, window):
# pygame.draw.circle(window, self.color, (self.x, self.y), self.radius)
window.blit(BULLETSPRITE, (self.x, self.y))

def update( self ):
""" Move the projectile along its path """
self.x += self.dx
self.y += self.dy

shoot() now calculates the direction vecotor ~

def shoot( mousex, mousey, player ):
""" Creates a player's bullets """
global shotTimer

if now - shotTimer >= player.cooldown:
global bulletVel

xDiff = mousex - player.x
yDiff = mousey - player.y
angle = math.atan2(yDiff, xDiff)

# Direction of travel
changeX = math.cos(angle) * bulletVel
changeY = math.sin(angle) * bulletVel

# Start position
new_bullet_x = round(player.x + player.width / 2)
new_bulley_y = round(player.y + player.height / 2)

bullets.append( Projectile( new_bullet_x, new_bulley_y, changeX, changeY, 6))
shotTimer = now

Making the bullet update function much simpler too:

def playerBulletUpdates():
for bullet in bullets:
if bullet.x >= 0 and bullet.x <= width and bullet.y >= 0 and bullet.y <= height:
# Move the bullet
bullet.update()
else:
# Bullet has gone off-screen
bullets.pop(bullets.index(bullet))

While you're changing these things around. It might be worthwhile investigating PyGame Rect objects using their positioning rather than discrete x and y, as that would give access to the excellent collision functions.



Related Topics



Leave a reply



Submit