2

I am trying to make my sprites rotate towards the player, but the rotation is out of place. Here is a video of the behaviour.

I am not sure how to fix this,

Sprite class

  #-------------------------------- enemy shoots left and right

    shotsright = pygame.image.load("canss.png")
    class enemyshoot:
        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)
            self.health = 10
            self.hitbox = (self.x + -20, self.y + 30, 31, 57)
           #-------------------------------------------------------
            # Make a Reference Copy of the bitmap for later rotation
            self.shootsright = pygame.image.load("canss.png")
            self.shootsright = pygame.transform.scale(self.shootsright,(self.shootsright.get_width()-150,self.shootsright.get_height()-150))            
            self.image    = self.shootsright
            self.rect     = self.image.get_rect()
            self.position = pygame.math.Vector2( (200, 180) )
            self.isLookingAtPlayer = False
        def draw(self):
            self.rect.topleft = (self.x,self.y)
            window.blit(self.image, self.rect)
            self.hits = (self.x + 20, self.y, 28,60)
            pygame.draw.rect(window, (255,0,0), (self.hitbox[0], self.hitbox[1] - 60, 100, 10)) # NEW
            pygame.draw.rect(window, (0,255,0), (self.hitbox[0], self.hitbox[1] - 60, 100 - (5 * (10 - self.health)), 10))
            self.hitbox = (self.x + 200, self.y + 200, 51, 65)
        def lookAt( self, coordinate ):
            # Rotate image to point in the new direction
            delta_vector  = coordinate - self.position
            radius, angle = delta_vector.as_polar()
            self.image    = pygame.transform.rotate(self.shootsright, -angle)
            # Re-set the bounding rectangle and position since 
            # the dimensions and centroid will have (probably) changed.
            current_pos      = self.rect.center
            self.rect        = self.image.get_rect()
            self.rect.center = current_pos
            


            
    black = (0,0,0)
    enemyshooting = []
    platformGroup = pygame.sprite.Group
    platformList = []
    level = ["                                                                                                                     p               p           p                         p                        p        ",
             "                                       ",
             "                             ",
             "                                      ",
             "                                  ",
             "                           ",
             "                                      ",
             "                                      ",
             "                                    ",
             "                                   ",
             "                    ",]
    for iy, row in enumerate(level):
        for ix, col in enumerate(row):
            if col == "p":
                new_platforms = enemyshoot(ix*10, iy*50, 10,10,(255,255,255))
                enemyshooting.append(new_platforms)

this is the rotating part


class enemyshoot:
     def __init__(self,x,y,height,width,color):
       # [................]
           #-------------------------------------------------------
            # Make a Reference Copy of the bitmap for later rotation
            self.shootsright = pygame.image.load("canss.png")
            self.shootsright = pygame.transform.scale(self.shootsright,(self.shootsright.get_width()-150,self.shootsright.get_height()-150))            
            self.image    = self.shootsright
            self.rect     = self.image.get_rect()
            self.position = pygame.math.Vector2( (200, 180) )      


        def lookAt( self, coordinate ):
            # Rotate image to point in the new direction
            delta_vector  = coordinate - pygame.math.Vector2(self.rect.center)
            radius, angle = delta_vector.as_polar()
            self.image    = pygame.transform.rotate(self.shootsright, -angle)
            # Re-set the bounding rectangle and position since 
            # the dimensions and centroid will have (probably) changed.
            current_pos      = self.rect.center
            self.rect        = self.image.get_rect()
            self.rect.center = current_pos



This is where I call the LookAt function to face the player:

      # so instead of this 
            for enemyshoot in enemyshooting:
                if not enemyshoot.isLookingAtPlayer:
                    enemyshoot.lookAt((playerman.x, playerman.y)) 

The rotation is out of place, and I can't figure out how to fix it. I am trying to make the mouth of the cannon rotate towards the player because that's where the bullets will append from.

Habib Ismail
  • 69
  • 5
  • 16
  • 1
    You need to make a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example), which is something we can run and debug. Without that, we can't help you. – 10 Rep Jun 23 '20 at 19:15

2 Answers2

1

self.position is set in the constructor, but it is not updated. Use the self.rect.center to compute the direction vector:

delta_vector = coordinate - pygame.math.Vector2(self.rect.center)

Use math.atan2 to compute the angle of rotation:
(See How do I make my player rotate towards mouse position?)

angle = (180 / math.pi) * math.atan2(-delta_vector.x, -delta_vector.y)

Setting the rectangle position in draw struggles with rotating the sprite in lookAt. Note the size of the rotated rectangle is enlarged. See How do I rotate an image around its center using Pygame?.

I recommend to set the look-at position in lookAt and to compute the rotated image in draw:

class enemyshoot:
    def __init__(self,x,y,height,width,color):
       # [...]

       self.look_at_pos = (x, y)

    def draw(self):
   
        self.rect = self.shootsright.get_rect(topleft = (self.x, self.y))

        dx = self.look_at_pos[0] - self.rect.centerx
        dy = self.look_at_pos[1] - self.rect.centery 
        angle = (180 / math.pi) * math.atan2(-dx, -dy)

        self.image = pygame.transform.rotate(self.shootsright, angle)
        self.rect  = self.image.get_rect(center = self.rect.center)

        window.blit(self.image, self.rect)
        self.hits = (self.x + 20, self.y, 28,60)
        pygame.draw.rect(window, (255,0,0), (self.hitbox[0], self.hitbox[1] - 60, 100, 10)) # NEW
        pygame.draw.rect(window, (0,255,0), (self.hitbox[0], self.hitbox[1] - 60, 100 - (5 * (10 - self.health)), 10))
        self.hitbox = (self.x + 200, self.y + 200, 51, 65)

    def lookAt(self, coordinate):
        self.look_at_pos = coordinate
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • I think the rotation became worse [vide](https://gyazo.com/4f4ef23e720df5ae0a290645f8c42616) – Habib Ismail Jun 23 '20 at 19:43
  • so instead of saying for enemyshoot in enemyshooting: if not enemyshoot.isLookingAtPlayer: enemyshoot.lookAt((playerman.x, playerman.y)) I could give it an update def at the bottom of the enemyshoot class* I updated the thread instead of getting the mouse position how could I get the player poisition to make it move like that? for proper rotating? [script](https://pastebin.com/UcMUJjzs) – Habib Ismail Jun 23 '20 at 20:22
  • I am getting an error `enemyshoot.lookAt((playerman.x, playerman.y)) TypeError: 'tuple' object is not callable` I dont see anything wrong with my [enemyshoot class](https://pastebin.com/A21ZZx5p) – Habib Ismail Jun 23 '20 at 20:43
  • 1
    @HabibIsmail My bad. Of course the name of the attribute and the function has to be different. I've changed `self.lookAt` -> `self.look_at_pos` – Rabbid76 Jun 23 '20 at 20:47
  • ` enemyshoot.lookAt((playerman.x, playerman.y)) TypeError: 'tuple' object is not callable` getting the same error again – Habib Ismail Jun 23 '20 at 20:53
  • @HabibIsmail Then did it wrong. Please read the answer carefully. – Rabbid76 Jun 23 '20 at 20:55
  • oh I fixed sorry but [video](https://gyazo.com/c143bce94977d2ebb7bb9c2a29b6d285) look its rotating nice but its moving up [update version of the script[(https://pastebin.com/0454qB2M) – Habib Ismail Jun 23 '20 at 21:04
  • @HabibIsmail Try `angle = (180/math.pi) * math.atan2(-dy, dx)` or `angle = (180/math.pi) * math.atan2(dy, dx)` – Rabbid76 Jun 23 '20 at 21:08
  • II forgot to do `self.look_at_pos = coordinate` ok I have tried the first one it works better then the second one but there is still rotation problem [video](https://gyazo.com/050b426bf547e4091377c333f8abdcdf) what I have done [script](https://pastebin.com/AmzQY872) – Habib Ismail Jun 24 '20 at 02:22
1

I concur with everything @Rabbid76 says in the above answer.

I suspect part of your problem may be that the "human readable" part of the bitmap is not centered around the bitmap's centroid. Thus when rotated, it "sweeps" through an arc, rather than being rotated "around itself". (The preserving of the centre co-ordinate is an important step here to maintain a smooth rotation about the centroid of the object).

Consider the two bitmaps (the image on the right has a large 3/4 transparent section top-left):

centred image off-centred image

Both are rotated around their centroid, but since the visible part on the 2nd image is not centred, it rotates weirdly.

example video

So ensure your actual bitmap is centred within itself.

Reference Code:

import pygame
import random

# Window size
WINDOW_WIDTH    = 800
WINDOW_HEIGHT   = 400
WINDOW_SURFACE  = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE

DARK_BLUE = (   3,   5,  54 )

class RotationSprite( pygame.sprite.Sprite ):
    def __init__( self, image, x, y, ):
        pygame.sprite.Sprite.__init__(self)
        self.original = image
        self.image    = image
        self.rect     = self.image.get_rect()
        self.rect.center = ( x, y )
        # for maintaining trajectory
        self.position    = pygame.math.Vector2( ( x, y ) )
        self.velocity    = pygame.math.Vector2( ( 0, 0 ) )

    def lookAt( self, co_ordinate ):
        # Rotate image to point in the new direction
        delta_vector  = co_ordinate - self.position
        radius, angle = delta_vector.as_polar()
        self.image    = pygame.transform.rotozoom( self.original, -angle, 1 )
        # Re-set the bounding rectagle
        current_pos      = self.rect.center
        self.rect        = self.image.get_rect()
        self.rect.center = current_pos



### initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption( "Rotation Example" )

### Missiles!
rotation_sprites = pygame.sprite.Group()
rotation_image1 = pygame.image.load( 'rot_offcentre_1.png' )
rotation_image2 = pygame.image.load( 'rot_offcentre_2.png' )
rotation_sprites.add( RotationSprite( rotation_image1, WINDOW_WIDTH//3, WINDOW_HEIGHT//2 ) )
rotation_sprites.add( RotationSprite( rotation_image2, 2*( WINDOW_WIDTH//3 ), WINDOW_HEIGHT//2 ) )


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

    # Record mouse movements for positioning the paddle
    mouse_pos = pygame.mouse.get_pos()
    for m in rotation_sprites:
        m.lookAt( mouse_pos )
    rotation_sprites.update()

    # Update the window, but not more than 60 FPS
    window.fill( DARK_BLUE )
    rotation_sprites.draw( window )
    pygame.display.flip()

    # Clamp FPS
    clock.tick_busy_loop(60)

pygame.quit()
Kingsley
  • 14,398
  • 5
  • 31
  • 53
  • I have already tried that I am not sure what you mean by centering it currently I followed the example you showed but I am still having rotation problem [VIDEO](https://gyazo.com/7f36da47929faf61c5cb5303f0d6e9ea) it only rotates slightly when the player is jumping [script](https://pastebin.com/tUbNTZf9) – Habib Ismail Jun 23 '20 at 23:57