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: repl.it/@Rabbid76/PyGame-FireBulletInDirectionOfMouse
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))
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
Python Subprocess Get Children's Output to File and Terminal
Importerror: No Module Named Requests
How to Check If a String Contains an Element from a List in Python
Finding Local Maxima/Minima with Numpy in a 1D Numpy Array
How to Convert JSON Data into a Python Object
Why Don't These List Operations Return the Resulting List
What Soap Client Libraries Exist for Python, and Where Is the Documentation for Them
Writing a Connection String When Password Contains Special Characters
Difference Between Class and Instance Methods
Python Garbage Collector Documentation
Shooting a Bullet in Pygame in the Direction of Mouse
How to Use Python to Login to a Webpage and Retrieve Cookies for Later Usage
Return a Default Value If a Dictionary Key Is Not Available
How to Convert a Utc Datetime to a Local Datetime Using Only Standard Library
Error After Upgrading Pip: Cannot Import Name 'Main'
How to Split a String of Space Separated Numbers into Integers