How to Rotate the Sprite and Shoot the Bullets Towards the Mouse Position

How can you rotate the sprite and shoot the bullets towards the mouse position?

There are 2 issues. The current position of the bullets is multiplied by its the normalized direction vector. That doesn't make sense:

for bullet in list_of_bullets:
bullet[0] = bullet[0] * bullet[2][0]
bullet[1] = bullet[1] * bullet[2][1]

Add the direction vector to the position of the bullet:

for bullet in list_of_bullets:
bullet[0] += bullet[2][0]
bullet[1] += bullet[2][1]

If you want to increase the speed of the bullets, then you've to scale the vector by a certain speed. e.g.:

def spawn_bullet(x,y):
global list_of_bullets
initial_x = x
initial_y = y
mouse_x, mouse_y = pygame.mouse.get_pos()
vector_x, vector_y = mouse_x - x, mouse_y - y

distance = math.sqrt(vector_x ** 2 + vector_y **2)
speed = 5
move_vec = (speed*vector_x/distance, speed*vector_y/distance)

list_of_bullets.append([initial_x, initial_y, move_vec])

The 2nd issue is, that the display is cleared, after the bullets are draw, so you'll never "see" the bullets.

Draw the bullets in update_game after the display is cleared:

def update_game(x,y,width,height,char):
win.fill((0,0,0))
for bullet in list_of_bullets:
pygame.draw.rect(win,(0,0,255),(int(bullet[0]),int(bullet[1]),20,20))
point_to_mouse(x,y,char)
pygame.display.update()

(Delete the drawing for the main application loop)


Minimal example:

Sample Image

import math
import pygame

def blit_point_to_mouse(target_surf, char_surf, x, y):
mouse_x, mouse_y = pygame.mouse.get_pos()
vector_x, vector_y = mouse_x - x, mouse_y - y
angle = (180 / math.pi) * -math.atan2(vector_y, vector_x) - 90
rotated_surface = pygame.transform.rotate(char_surf, round(angle))
rotated_surface_location = rotated_surface.get_rect(center = (x, y))
target_surf.blit(rotated_surface, rotated_surface_location)

def spawn_bullet(list_of_bullets, x, y):
mouse_x, mouse_y = pygame.mouse.get_pos()
vector_x, vector_y = mouse_x - x, mouse_y - y
distance = math.hypot(vector_x, vector_y)
if distance == 0:
return
speed = 5
move_vec = (speed * vector_x / distance, speed * vector_y / distance)
list_of_bullets.append([x, y, move_vec])

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

rocket = pygame.image.load('Rocket64.png')
rocket_rect = rocket.get_rect(center = window.get_rect().center)
velocity = 6
list_of_bullets = []
bullet = pygame.Surface((20, 20), pygame.SRCALPHA)
pygame.draw.circle(bullet, (64, 64, 64), (10, 10), 10)
pygame.draw.circle(bullet, (96, 96, 96), (10, 10), 9)
pygame.draw.circle(bullet, (128, 128, 128), (9, 9), 7)
pygame.draw.circle(bullet, (160, 160, 160), (8, 8), 5)
pygame.draw.circle(bullet, (192, 192, 192), (7, 7), 3)
pygame.draw.circle(bullet, (224, 224, 224), (6, 6), 1)

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:
spawn_bullet(list_of_bullets, *rocket_rect.center)

for bullet_pos in list_of_bullets:
bullet_pos[0] += bullet_pos[2][0]
bullet_pos[1] += bullet_pos[2][1]
if not (0 <= bullet_pos[0] < window.get_width() and 0 < bullet_pos[1] < window.get_height()):
del list_of_bullets[list_of_bullets.index(bullet_pos)]
continue

keys = pygame.key.get_pressed()
rocket_rect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * velocity
rocket_rect.y += (keys[pygame.K_UP] - keys[pygame.K_DOWN]) * velocity
rocket_rect.clamp_ip(window.get_rect())

window.fill(0)
for bullet_pos in list_of_bullets:
window.blit(bullet, bullet.get_rect(center = (round(bullet_pos[0]),round(bullet_pos[1]))))
blit_point_to_mouse(window, rocket, *rocket_rect.center)
pygame.display.flip()

pygame.quit()
exit()

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

# [...]

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

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 do I move an object towards the mouse without having the mouse position affect speed in Pygame?

If you have to points

x1 = 10
y1 = 10

x2 = 100
y2 = 500

then you can calculate distance and use pygame.math.Vector2

import pygame

dx = x2-x1
dy = y2-y1

distance = pygame.math.Vector2(dx, dy)

or

v1 = pygame.math.Vector2(x1, y1)
v2 = pygame.math.Vector2(x2, y2)

distance = v2 - v1

and then you can normalize it

direction = distance.normalize()

It should always gives distance 1

print('distance:', direction[0]**2 + direction[1]**2)  # 0.999999999999
# or
print('distance:', direction.length() )

And then you move object using speed

pos[0] += direction[0] * speed
pos[1] += direction[1] * speed

EDIT:

If you will use Rect

SIZE = (10, 10)
bullet_rect = pygame.Rect((0, 0), SIZE)
bullet_rect.center = (x1, y1)

then you can also calculate

distance = v2 - bullet_rect.center

direction = distance.normalize()

and move it with one line

bullet_rect.center += direction * speed

Rect has many useful functions. But has one minus - it keeps position as integers so it rounds float values and sometimes it gives strange moves or lost one pixel every few moves.


Doc: PyGame.math



Related Topics



Leave a reply



Submit