Bullets Dont Look Like They Are Comming Out Of Guns Mouth Pygame

Viewed 70

enter image description hereI am having an issue where my bullets dont look like they are coming out of my gun they look like they are coming out of the players body VIDEO as you can see in the video it shoots somewhere else or its the gun its the same thing for the left side it shoots good going up but it shoots bad going down VIDEO

*** what I am trying to say is how could I make my gun rotate exactly at my mouse position ***

I tried angeling my gun to 120 but what happens is everything good works for the right side not for the left side VIDEO as you can see it just glitches


    def draw(self,drawX,drawY):
 
        self.rect.topleft =  (drawX,drawY)
 
            # the guns hitbox
 
            # rotatiing the gun
        dx = self.look_at_pos[0] - self.rect.centerx
        dy = self.look_at_pos[1] - self.rect.centery 
            
        angle = (190/math.pi) * math.atan2(-dy, dx)
      
        gun_size = self.image.get_size()
        pivot = (8, gun_size[1]//2)
            
 
        blitRotate(window, self.image, self.rect.center, pivot, angle)

        if((angle > 90 or angle < -90) and self.gunDirection != "left"):
            self.gunDirection = "left"
            self.image = pygame.transform.flip(self.image, False, True)
        if((angle < 90 and angle > -90) and self.gunDirection != "right"):
            self.gunDirection = "right"
            self.image = pygame.transform.flip(self.image, False, True)





my full gun class

 
class handgun():
    def __init__(self,x,y,height,width,color):
        self.x = x
        self.y = y
        self.height = height
        self.width = width
        self.color = color
        self.rect = pygame.Rect(x,y,height,width)
 
 
        # LOL THESE IS THE HAND
        self.shootsright = pygame.image.load("hands.png")
        self.image = self.shootsright
        self.rect  = self.image.get_rect(center = (self.x, self.y))
        self.look_at_pos = (self.x, self.y)
 
        self.isLookingAtPlayer = False
        self.look_at_pos = (x,y)
            
 
 
 
        self.hitbox = (self.x + -18, self.y, 46,60)

 
        self.gunDirection = "right"
    def draw(self,drawX,drawY):
 
        self.rect.topleft =  (drawX,drawY)
 
            # the guns hitbox
 
            # rotatiing the gun
        dx = self.look_at_pos[0] - self.rect.centerx
        dy = self.look_at_pos[1] - self.rect.centery 
            
        angle = (120/math.pi) * math.atan2(-dy, dx)
      
        gun_size = self.image.get_size()
        pivot = (8, gun_size[1]//2)
            
 
        blitRotate(window, self.image, self.rect.center, pivot, angle)

        if((angle > 90 or angle < -90) and self.gunDirection != "left"):
            self.gunDirection = "left"
            self.image = pygame.transform.flip(self.image, False, True)
        if((angle < 90 and angle > -90) and self.gunDirection != "right"):
            self.gunDirection = "right"
            self.image = pygame.transform.flip(self.image, False, True)




    def lookAt( self, coordinate ):
            
        self.look_at_pos = coordinate
 
 
 
white = (255,255,255)
handgun1 = handgun(300,300,10,10,white)




how my images are blitted





```def blitRotate(surf, image, pos, originPos, angle):
 
        # calcaulate the axis aligned bounding box of the rotated image
    w, h = image.get_size()
    sin_a, cos_a = math.sin(math.radians(angle)), math.cos(math.radians(angle)) 
    min_x, min_y = min([0, sin_a*h, cos_a*w, sin_a*h + cos_a*w]), max([0, sin_a*w, -cos_a*h, sin_a*w - cos_a*h])
 
        # calculate the translation of the pivot 
    pivot        = pygame.math.Vector2(originPos[0], -originPos[1])
    pivot_rotate = pivot.rotate(angle)
    pivot_move   = pivot_rotate - pivot
 
        # calculate the upper left origin of the rotated image
    origin = (pos[0] - originPos[0] + min_x - pivot_move[0], pos[1] - originPos[1] - min_y + pivot_move[1])
 
        # get a rotated image
    rotated_image = pygame.transform.rotate(image, angle)
 
        # rotate and blit the image
    surf.blit(rotated_image, origin)
 

I think what I am trying to say is how could I make my gun rotate at exactly at my mouse poisition without any problems

my full code script

1 Answers

One way would be to work out what the ellipse is for the arc the tip of the gun moves through, relative to its x,y co-ordinate. Use this to create a "Lookup Table"

The lookup-table, when indexed by the 0→360°, where for the given the angle, it gives a pair of offsets to add to the "arm" origin-position. Added together this new point is the co-ordinate to the tip of the gun. For example, say the gun is at 30°, we access end_of_gun_lookup[ 30 ], and it returns ( 12, -6 ), which corresponds to the difference between the "arm" sprite position and the tip of the gun.

Examining your code, angles seem to run from about -120 to 120. Obviously we can't have a negative index to the lookup table, so we will just shift everything by 120, so end_of_gun_lookup[ 0 ] is for angle -120, end_of_gun_lookup[ 1 ] for angle -119, etc.

So now when you go to initially position the bullet, it should be created at "arm" bitmap origin, plus end_of_gun_lookup[ 120 + round( angle ) ]

But! How do we create that lookup table?

One way to do this, is to programmatically "follow" the tip of the gun as its bitmap rotates. Once the tip of the gun is found, immediately store that location in the lookup table for the known angle.

To find the tip of the gun, I modified the "arm" bitmap to have a bright green pixel-block (3x3) at the end of the gun. This could be any colour not used in the bitmap already, but I chose green ( 8, 255, 0 ).

gun with Super-green pixel

Then we create function that, during every rotation, laboriously looks for the green pixel in every pixel of the rotated bitmap. This is quite slow, and not something you want to do during game-play.

rotating arm

And here is that function. The end_of_gun_lookup is the final lookup table. First we fill it with "no value" None for every possible angle.

# Create empty look-up point for green-pixel offsets
SUPA_GREEN = ( 8, 255, 0 )
end_of_gun_lookup = [ None ] * 360

The findGreenPixels() function fills this global list whenever it is able to find the pixel location:

def findGreenPixels( origin, image, rotation_angle ):
    global end_of_gun_lookup
    
    result = None
    # Bitmap offset 
    origin_x, origin_y = origin
    # find the Super Green pixel at the end of the gun.
    # very slow, and inefficient 
    width = image.get_rect().width
    height = image.get_rect().height
    for y in range( height ):
        for x in range( width ):
            pixel_colour = image.get_at( ( x, y ) )
            if ( pixel_colour == SUPA_GREEN ):
                #print( "GREEN AT %d -> %d,%d" % ( rotation_angle, x, y ) )
                result = ( round( origin_x - x ), round( origin_y - y ) )
                # results go from -120 -> 120, so offset
                # before storing the point.
                # Distance is relative to bitmap orgin too
                end_of_gun_lookup[ round( rotation_angle ) + 120 ] = ( result )
            if ( result != None ):
                break
        if ( result != None ):
            break
    return result

It basically iterates through every pixel, looking for that green. If found, the lookup table is populated. I used a 3x3 block of pixels, because during image rotation, pixels are shaded and blurred, and a block of pixels has a better change of staying the exact same colour.

At the end of the program, we dump out the lookup table:

pygame.quit()

### PRINT THE LOOKUP TABLE
end_of_gun_lookup = fillLookupHoles( end_of_gun_lookup )
print( "end_of_gun_lookup = "+ str( end_of_gun_lookup ) )

During my tests, no matter how slow I moved the mouse, sometimes there was undefined positions within the table. Although I did manage a perfect table just once. Anyway, I added some code that uses the midpoint line algorithm to estimate the position of a single missing value. This cleaned those holes up. Obviously you can't generate points at the ends of the table, and these stay as None. Maybe the code needs to handle these, maybe they never happen.

This gave me a result like:

end_of_gun_lookup = [(108, 160), (109, 162), (110, 163), (111, 163), (111, 163), (112, 164), (111, 166), (113, 167), (114, 168), (114, 170), (115, 171), (116, 173), (116, 174), (118, 175), (118, 177), (120, 177), (120, 178), (121, 180), (122, 182), (124, 183), (125, 185), (95, 181), (126, 188), (129, 189), (130, 191), (130, 193), (133, 196), (103, 194), (104, 197), (137, 199), (107, 201), (106, 199), (137, 197), (105, 196), (104, 194), (101, 193), (101, 191), (101, 189), (130, 183), (99, 186), (99, 184), (99, 183), (97, 182), (98, 180), (98, 178), (95, 179), (96, 177), (96, 175), (94, 174), (95, 172), (94, 171), (94, 170), (94, 170), (93, 168), (94, 166), (95, 165), (95, 164), (95, 163), (95, 162), (94, 161), (94, 160), (95, 159), (95, 158), (96, 157), (97, 156), (98, 155), (98, 154), (98, 155), (98, 154), (98, 152), (101, 151), (101, 151), (101, 150), (102, 150), (102, 149), (104, 148), (105, 148), (106, 148), (107, 146), (108, 147), (109, 146), (110, 146), (110, 146), (112, 146), (114, 145), (115, 145), (117, 144), (117, 144), (119, 145), (119, 144), (122, 145), (123, 144), (126, 146), (126, 146), (128, 145), (129, 145), (131, 146), (134, 145), (134, 145), (136, 147), (138, 147), (140, 147), (142, 147), (144, 148), (144, 148), (148, 149), (149, 150), (151, 150), (153, 150), (154, 152), (158, 151), (159, 152), (161, 153), (163, 153), (166, 154), (169, 156), (170, 156), (172, 158), (175, 158), (177, 159), (179, 160), (178, 159), (176, 157), (174, 156), (173, 154), (171, 154), (169, 152), (168, 149), (164, 149), (163, 149), (163, 147), (161, 146), (160, 144), (159, 143), (155, 142), (154, 141), (154, 141), (154, 140), (151, 138), (151, 138), (148, 138), (146, 137), (145, 136), (144, 135), (144, 134), (143, 134), (142, 133), (139, 133), (140, 132), (138, 132), (137, 131), (136, 131), (135, 130), (134, 130), (133, 129), (133, 129), (132, 130), (130, 130), (130, 129), (129, 129), (129, 129), (128, 129), (127, 128), (128, 129), (126, 129), (126, 129), (125, 129), (125, 130), (124, 130), (124, 130), (123, 131), (124, 131), (124, 132), (123, 132), (122, 132), (122, 134), (122, 134), (123, 134), (122, 134), (122, 135), (122, 136), (122, 137), (123, 139), (122, 139), (122, 140), (123, 140), (123, 142), (123, 142), (123, 143), (124, 145), (124, 146), (124, 147), (125, 147), (125, 149), (126, 151), (126, 151), (127, 153), (128, 154), (129, 156), (129, 157), (129, 158), (130, 159), (130, 160), (132, 163), (133, 165), (134, 165), (135, 167), (136, 169), (105, 169), (137, 172), (107, 174), (137, 172), (103, 171), (103, 169), (102, 167), (131, 163), (98, 165), (99, 162), (96, 160), (96, 159), (93, 159), (93, 157), (93, 155), (93, 154), (91, 152), (89, 153), (89, 151), (89, 149), (87, 147), (86, 146), (85, 146), (84, 145), (85, 143), (84, 143), (82, 142), (83, 140), (82, 139), (83, 139), (82, 139), (82, 137), (82, 136), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None]

All those Nones at the end probably can't happen. At least not through mouse-rotation.

So once this lookup table is defined, it can be incorporated into the code, and the generated code removed (or commented out).

EDIT:

So add the definition of the end_of_gun_lookup lookup table generated by the previous step to your source. Then modify blitRotate() to use it.

def blitRotate(surf, image, pos, originPos, angle):

    ... 

    # use lookup table to find the end-of gun at this angle
    x_origin, y_origin = origin
    x_offset, y_offset = end_of_gun_lookup[ round( angle ) + 120 ]
    final_pos = ( x_origin + x_offset, y_origin + y_offset )

    # rotate and blit the image
    surf.blit( rotated_image, final_pos )

Reference Code: (use the static bitmap above for nug.png)

import pygame
import random
import math

# Window size
WINDOW_WIDTH    = 400
WINDOW_HEIGHT   = 400
WINDOW_SURFACE  = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
DARK_BLUE = (   3,   5,  54)


# Create empty look-up point for green-pixel offsets
SUPA_GREEN = ( 8, 255, 0 )
end_of_gun_lookup = [ None ] * 360

def midpoint( point_a, point_b ):
    """ Use the midpoint-line formula to return the point between 
        point_a and point_b """
    mid_x = round( ( point_a[0] + point_b[0] ) / 2 ) 
    mid_y = round( ( point_a[1] + point_b[1] ) / 2 ) 
    return ( mid_x, mid_y )

def fillLookupHoles( coord_list ):
    """ Find any gaps in the lookup table, by finding the mid-point line
        pixel between the two points, giving an estimated position """
    for i in range( 1, len ( coord_list )-1 ):  # we can't fix end-points, ignore first & last
        before = coord_list[ i-1 ]
        after  = coord_list[ i+1 ]
        if ( coord_list[ i ] == None and before != None and after != None ):
            coord_list[ i ] = midpoint( before, after )
            print( "Filled hole at angle %d" % ( i - 120 ) )
    return coord_list

def findGreenPixels( origin, image, rotation_angle ):
    global end_of_gun_lookup

    result = None
    # Bitmap offset
    origin_x, origin_y = origin
    # find the Super Green pixel at the end of the gun.
    # very slow, and inefficient
    width = image.get_rect().width
    height = image.get_rect().height
    for y in range( height ):
        for x in range( width ):
            pixel_colour = image.get_at( ( x, y ) )
            if ( pixel_colour == SUPA_GREEN ):
                #print( "GREEN AT %d -> %d,%d" % ( rotation_angle, x, y ) )
                result = ( round( origin_x - x ), round( origin_y - y ) )
                # results go from -120 -> 120, so offset
                # before storing the point.
                # Distance is relative to bitmap orgin too
                end_of_gun_lookup[ round( rotation_angle ) + 120 ] = ( result )
            if ( result != None ):
                break
        if ( result != None ):
            break
    return result



def blitRotate(surf, image, pos, originPos, angle):
 
    # calcaulate the axis aligned bounding box of the rotated image
    w, h = image.get_size()
    sin_a, cos_a = math.sin(math.radians(angle)), math.cos(math.radians(angle)) 
    min_x, min_y = min([0, sin_a*h, cos_a*w, sin_a*h + cos_a*w]), max([0, sin_a*w, -cos_a*h, sin_a*w - cos_a*h])
 
        # calculate the translation of the pivot 
    pivot        = pygame.math.Vector2(originPos[0], -originPos[1])
    pivot_rotate = pivot.rotate(angle)
    pivot_move   = pivot_rotate - pivot
 
        # calculate the upper left origin of the rotated image
    origin = (pos[0] - originPos[0] + min_x - pivot_move[0], pos[1] - originPos[1] - min_y + pivot_move[1])
 
        # get a rotated image
    rotated_image = pygame.transform.rotate(image, angle)

    end_of_gun_coord = findGreenPixels( origin, rotated_image, angle ) 
 
        # rotate and blit the image
    surf.blit(rotated_image, origin)
 


 
class handgun():
    def __init__(self,x,y,height,width,color):
        self.x = x
        self.y = y
        self.height = height
        self.width = width
        self.color = color
        self.rect = pygame.Rect(x,y,height,width)
 
        # LOL THESE IS THE HAND
        self.shootsright = pygame.image.load("nug.png")
        self.image = self.shootsright
        self.rect  = self.image.get_rect(center = (self.x, self.y))
        self.look_at_pos = (self.x, self.y)
 
        self.isLookingAtPlayer = False
        self.look_at_pos = (x,y)
        self.hitbox = (self.x + -18, self.y, 46,60)

        self.gunDirection = "right"

    def draw(self,drawX,drawY):
        self.rect.topleft =  (drawX,drawY)
 
        # the guns hitbox
        # rotatiing the gun
        dx = self.look_at_pos[0] - self.rect.centerx
        dy = self.look_at_pos[1] - self.rect.centery 
            
        angle = (120/math.pi) * math.atan2(-dy, dx)
        gun_size = self.image.get_size()
        pivot = (8, gun_size[1]//2)
 
        blitRotate(window, self.image, self.rect.center, pivot, angle)

        if((angle > 90 or angle < -90) and self.gunDirection != "left"):
            self.gunDirection = "left"
            self.image = pygame.transform.flip(self.image, False, True)
        if((angle < 90 and angle > -90) and self.gunDirection != "right"):
            self.gunDirection = "right"
            self.image = pygame.transform.flip(self.image, False, True)

    def lookAt( self, coordinate ):
        self.look_at_pos = coordinate
 
 

 
white = (255,255,255)
handgun1 = handgun(300,300,10,10,white)
        



### initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption("Track Path of Green")


### Main Loop
clock = pygame.time.Clock()
done = False
while not done:

    # Handle user-input
    for event in pygame.event.get():
        if ( event.type == pygame.QUIT ):
            done = True
        elif ( event.type == pygame.MOUSEBUTTONUP ):
            # On mouse-click
            pass

    handgun1.direction = "right"

    # gun rotation
    mousex, mousey = pygame.mouse.get_pos()
    if not handgun1.isLookingAtPlayer:
        handgun1.lookAt((mousex, mousey))


    # Update the window, but not more than 60fps
    window.fill( DARK_BLUE )
    handgun1.draw( 200, 200 )
    pygame.display.flip()

    # Clamp FPS
    clock.tick_busy_loop(60)

pygame.quit()

### PRINT THE LOOKUP TABLE
end_of_gun_lookup = fillLookupHoles( end_of_gun_lookup )
print( "end_of_gun_lookup = "+ str( end_of_gun_lookup ) )
Related