Shooting a bullet in pygame in the direction of mouse

Question:

I just cant figure out why my bullet is not working. I made a bullet class and here it is:

class Bullet:
    def __init__(self):
        self.x = player.x
        self.y = player.y
        self.height = 7
        self.width = 2
        self.bullet = pygame.Surface((self.width, self.height))
        self.bullet.fill((255, 255, 255))

Now I added several functions in my game class and here is the new code:

class Game:
    def __init__(self):
        self.bullets = []
    
    def shoot_bullet(self):
         if self.bullets:
            for bullet in self.bullets:
                rise = mouse.y - player.y
                run = mouse.x - player.x
                angle = math.atan2(rise, run)

                bullet.x += math.cos(angle)
                bullet.y += math.sin(angle)

                pygame.transform.rotate(bullet.bullet, -math.degrees(angle))
                D.blit(bullet.bullet, (bullet.x, bullet.y))


    def generate_bullet(self):
        if  mouse.is_pressed():
            self.bullets.append(Bullet())

What I was expecting the code to do was a Bullet() would get added to game.bullets every time I pressed the mouse button, then game.shoot_bullet would calculate the angle between the player and the mouse and shoot the bullet accordingly in the direction of the mouse. However, the result is a complete mess and the bullets actually don’t rotate and don’t move. They get generated and move weirdly to the left of the screen. I am not sure if I have messed up something or the method I have used is completely wrong.

Asked By: user12291970

||

Answers:

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()
Answered By: Rabbid76

I noticed that the cycle where you checking the condition if
lenghts is equal to 0 it doesn’t really make any sense.
Of-course the value of sqrt(0) is always 0, but how is this even possible in reality to get? 0% (i guess)

So, while its looking for a path by calculate a hypotenuse

if length == 0.0:
   self.dir = (0, -1)

In which scenario where it returns 0 is possible ?
P.S. if i am missing any thing feel free to suggest

Answered By: John4rced
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.