Calculating Direction of the Player to Shoot Pygame

calculating direction of the player to shoot pygame

The unit of the angle for the mathematical operations math.sin and math.cos is radian, but the unit of the angel for pygame.transform.rotate() is degrees. Furthermore you have to create a pygame.Surface with the flag SRCALPHA and to fill the surface with the bullet color, before it is rotated:

class Bullet:
def __init__(self):
self.pos = [player1.pos[0], player1.pos[1]]
self.direction = math.radians(player1.rotation_angle)
self.bullet = pygame.Surface((10, 5), pygame.SRCALPHA)
self.bullet.fill((100, 200, 120))
self.rotated_bullet = pygame.transform.rotate(self.bullet, player1.rotation_angle)
self.time = 0

In pygame the y axis points form the top to the bottom. That is the opposite direction than in a usual Cartesian coordinate system. Because of that the y coordinate of the direction has to be inverted:

class Bullet:
# [...]

def shoot(self):
self.pos[0] += math.cos(self.direction) * self.time
self.pos[1] -= math.sin(self.direction) * self.time
self.time += 0.5

See the example:

Sample Image

import pygame
import math

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

class Player():
def __init__(self):
self.rotation_angle = 0
self.player = pygame.Surface((20, 20), pygame.SRCALPHA)
self.player.fill((0, 255, 0))
self.rotated_player = self.player
self.pos = (WIDTH//2, HEIGHT//2)

def rotate(self, keys, left, right):
if keys[right]:
self.rotation_angle -= 0.5
if keys[left]:
self.rotation_angle += 0.5
self.rotated_player = pygame.transform.rotate(self.player, (self.rotation_angle))

class Bullet:
def __init__(self):
self.pos = [player1.pos[0], player1.pos[1]]
self.direction = math.radians(player1.rotation_angle)
self.bullet = pygame.Surface((10, 5), pygame.SRCALPHA)
self.bullet.fill((100, 200, 120))
self.rotated_bullet = pygame.transform.rotate(self.bullet, player1.rotation_angle)
self.time = 0

def shoot(self):
self.pos[0] += math.cos(self.direction) * self.time
self.pos[1] -= math.sin(self.direction) * self.time
self.time += 0.5

player1 = Player()

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.KEYDOWN and event.key == pygame.K_SPACE:
bullets.append(Bullet())

keys = pygame.key.get_pressed()
player1.rotate(keys, pygame.K_LEFT, pygame.K_RIGHT)

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

window.fill(0)
window.blit(player1.rotated_player, player1.rotated_player.get_rect(center=player1.pos))
for bullet in bullets:
window.blit(bullet.rotated_bullet, bullet.rotated_bullet.get_rect(center=bullet.pos))
pygame.display.flip()



See also

Why aren't any bullets appearing on screen? - pygame

Shooting a bullet in pygame in the direction of mouse

Pygame- How to shoot in direction of player sprite?

The angle is stored in the self.angle attribute of the player sprite. The angle_speed is just the speed by which it rotates.

There's no need to calculate the angle and velocity of the bullets, since you can just pass the angle and direction of the player sprite.

import math
import pygame
from pygame.math import Vector2

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)

class Player(pygame.sprite.Sprite):

def __init__(self, pos=(420, 420)):
super(Player, self).__init__()
self.image = pygame.Surface([20, 40], pygame.SRCALPHA)
self.image.fill(RED)
self.original_image = self.image
self.rect = self.image.get_rect(center=pos)
self.position = Vector2(pos)
# The vector points upwards.
self.direction = Vector2(0, -1)
self.speed = 0
self.angle_speed = 0
self.angle = 0

def update(self):
if self.angle_speed != 0:
# Rotate the direction vector and then the image
self.direction.rotate_ip(self.angle_speed)
self.angle += self.angle_speed
self.image = pygame.transform.rotate(self.original_image, -self.angle)
self.rect = self.image.get_rect(midtop=self.rect.midtop)
# Update the position vector and the rect.
self.position += self.direction * self.speed
self.rect.center = self.position

class Bullet(pygame.sprite.Sprite):
""" This class represents the bullet. """

def __init__(self, pos, direction, angle):
"""Take the pos, direction and angle of the player."""
super(Bullet, self).__init__()
self.image = pygame.Surface([4, 10], pygame.SRCALPHA)
self.image.fill(BLACK)
# Rotate the image by the player.angle (negative since y-axis is flipped).
self.image = pygame.transform.rotozoom(self.image, -angle, 1)
# Pass the center of the player as the center of the bullet.rect.
self.rect = self.image.get_rect(center=pos)
self.position = Vector2(pos) # The position vector.
self.velocity = direction * 11 # Multiply by desired speed.

def update(self):
"""Move the bullet."""
self.position += self.velocity # Update the position vector.
self.rect.center = self.position # And the rect.

if self.rect.x < 0 or self.rect.x > SCREEN_WIDTH or self.rect.y < 0 or self.rect.y > SCREEN_HEIGHT:
self.kill()

def main():
pygame.init()
pygame.key.set_repeat(500,30)

screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])
screen_rect = screen.get_rect()

all_sprites_list = pygame.sprite.Group()
bullet_group = pygame.sprite.Group() # "group" not "list".

player = Player()
all_sprites_list.add(player)

MAXSPEED = 15
MINSPEED = -5

clock = pygame.time.Clock()

done = False
while not done:
clock.tick(60)

for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP and player.speed > MINSPEED:
player.speed += 3
if event.key == pygame.K_DOWN and player.speed < MAXSPEED:
player.speed -= 3
if event.key == pygame.K_LEFT:
player.angle_speed = -3
if event.key == pygame.K_RIGHT:
player.angle_speed = 3
if event.key == pygame.K_SPACE:
# Just pass the rect.center, direction vector and angle.
bullet = Bullet(
player.rect.center, player.direction, player.angle)
all_sprites_list.add(bullet)
bullet_group.add(bullet)
elif event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
player.angle_speed = 0
elif event.key == pygame.K_RIGHT:
player.angle_speed = 0

all_sprites_list.update()
player.rect.clamp_ip(screen_rect)

screen.fill(WHITE)
all_sprites_list.draw(screen)
pygame.display.flip()

if __name__ == '__main__':
main()
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()

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 to shoot a projectile in the way the player is looking in a TDS

pygame.sprite.Group.draw() and pygame.sprite.Group.update() are methods which are provided by pygame.sprite.Group.

The former delegates the to the update method of the contained pygame.sprite.Sprites - you have to implement the method. See pygame.sprite.Group.update():

Calls the update() method on all Sprites in the Group [...]

The later uses the image and rect attributes of the contained pygame.sprite.Sprites to draw the objects - you have to ensure that the pygame.sprite.Sprites have the required attributes. See pygame.sprite.Group.draw():

Draws the contained Sprites to the Surface argument. This uses the Sprite.image attribute for the source surface, and Sprite.rect. [...]

A Sprite kan be removed from all Groups and thus delted by calling kill.

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. If this is done every frame, the position error will accumulate over time.

If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location (e.g. .center) of the rectangle.

Write a Sprite class with an update method that moves the bullet in a certain direction. Destroy the bullet when it goes out of the display:

class Bullet(pygame.sprite.Sprite):
def __init__(self, pos, angle):
super().__init__()
self.image = pygame.image.load('graphics/weapons/bullets/default_bullet.png')
self.image = pygame.transform.rotate(self.image, angle)
self.rect = self.image.get_rect(center = pos)
self.speed = 15
self.pos = pos
self.dir_vec = pygame.math.Vector2()
self.dir_vec.from_polar((self.speed, -angle))

def update(self, screen):
self.pos += self.dir_vec
self.rect.center = round(self.pos.x), round(self.pos.y)
if not screen.get_rect().colliderect(self.rect):
self.kill()

pygame.key.get_pressed() returns a list with the state of each key. If a key is held down, the state for the key is True, otherwise False. Use pygame.key.get_pressed() to evaluate the current state of a button and get continuous movement.

The keyboard events (see pygame.event module) occur only once when the state of a key changes. The KEYDOWN event occurs once every time a key is pressed. KEYUP occurs once every time a key is released. Use the keyboard events for a single action.

Use an keyboard event to generate a new Bullet object (e.g. TAB). Call bullet_group.update(screen) and bullet_group.draw(screen) in the application loop:

running = True
while running:

for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_TAB:
pos = player_sprite.rect.center
dir_vec = pygame.math.Vector2()
new_bullet = Bullet(pos, player_sprite.rotate_vel)
bullet_group.add(new_bullet)

bullet_group.update(screen)

screen.fill('grey')
player.update()
bullet_group.draw(screen)
pygame.display.update()

clock.tick(FPS)

Sample Image

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

# [...]


Related Topics



Leave a reply



Submit