How to make my rectangle rotate with a rotating sprite

Question:

So I been having this problem with my rectangle/projectile rotation, I want it so that my rectangle/projectile will rotate with my rotating sprite but the code I’m trying for it is not working for me, The code that I’m trying is giving me this error. 'pygame.Surface' object has no attribute 'x' I have tried moving the code around, I have also tried to change the code so I wont get the error no more, and I have tried using a hitbox but I still keep getting the error. This is my two sprites

enter image description here
enter image description here

The code I’m trying

        self.dist = 100
        dx = self.pin.x + self.dist*math.cos(-self.pin.angle*(math.pi/180)) -65 # why offset needed ?
        dy = self.pin.y + self.dist*math.sin(-self.pin.angle*(math.pi/180)) -50 # why offset needed ?
        self.rect.topleft = (dx,dy)
        pygame.draw.rect(window,self.color,self.rect)

My full code

import pygame,math,random
pygame.init()

# Windowing screen width and height
width = 500
height = 500
window = pygame.display.set_mode((width,height))

# Name of window
pygame.display.set_caption("Game")

# The Background
background = pygame.image.load("img/BG.png")


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)
    
# Player class
class Player:
    def __init__(self,x,y,width,height,color):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.color = color
        self.speed = 4
        self.cannon = pygame.image.load("img/Cannon.png")
        self.cannon = pygame.transform.scale(self.cannon,(self.cannon.get_width()//2, self.cannon.get_height()//2))
        self.rect = pygame.Rect(x,y,width,height)
        self.hitbox = (self.x,self.y,30,30)
        self.image = self.cannon
        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.angle = 0
    def get_rect(self):
        self.rect.topleft = (self.x,self.y)
        return self.rect
    def draw(self):
        self.rect.topleft = (self.x,self.y)
        pygame.draw.rect(window,self.color,self.hitbox)
    
        player_rect = self.cannon.get_rect(center = self.get_rect().center)
        player_rect.centerx -= 0
        player_rect.centery += 90
    
        # Another part of cannon rotating     
        dx = self.look_at_pos[0] - self.rect.centerx
        dy = self.look_at_pos[1] - self.rect.centery 
        
        angle = (180/math.pi) * math.atan2(-dy, dx) - 90
  
        gun_size = self.image.get_size()
        pivot_abs = player_rect.centerx, player_rect.top + 13
        pivot_rel = (gun_size[0] // 2, 105)
        
        pygame.draw.rect(window,self.color,self.rect)
        blitRotate(window, self.image,pivot_abs, pivot_rel, angle)

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


        

# Players gun
class projectile(object):
    def __init__(self,x,y,dirx,diry,color):
        self.x = x
        self.y = y
        self.dirx = dirx
        self.diry = diry
        self.pin = pygame.image.load("img/Pin.png")
        self.pin = pygame.transform.scale(self.pin,(self.pin.get_width()//6, self.pin.get_height()//6))
        self.rect = self.pin.get_rect()
        self.topleft = ( self.x, self.y )
        self.speed = 10
        self.color = color
        self.hitbox = (self.x + 20, self.y, 30,40)
    def move(self):
        self.x += self.dirx * self.speed
        self.y += self.diry * self.speed
    def draw(self):
        self.rect.topleft = (round(self.x), round(self.y))
        
        window.blit(self.pin,self.rect)
        self.hitbox = (self.x + 20, self.y,30,30)

       # For rotating the the projectile
        self.dist = 100
        dx = self.pin.x + self.dist*math.cos(-self.pin.angle*(math.pi/180)) -65 # why offset needed ?
        dy = self.pin.y + self.dist*math.sin(-self.pin.angle*(math.pi/180)) -50 # why offset needed ?
        self.rect.topleft = (dx,dy)
        pygame.draw.rect(window,self.color,self.rect)


        


# The color white
white = (255,255,255)

# The xy cords, width, height and color of my classes[]

playerman = Player(350,385,34,75,white)



# This is where my balloons get hit by the bullet and disappers
# redrawing window
def redrawwindow():
    window.fill((0,0,0))

    # Drawing the window in
    window.blit(background,(0,0))

    # drawing the player in window
    playerman.draw()

    # Drawing the players bullet
    for bullet in bullets:
        bullet.draw()

# Frames for game
fps = 30
clock = pygame.time.Clock()
#projectile empty list
bullets = []
# main loop
run = True
while run:
    clock.tick(fps)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False


        if event.type == pygame.MOUSEBUTTONDOWN:

            if len(bullets) < 6700:
                mousex , mousey = pygame.mouse.get_pos()
                start_x , start_y = playerman.rect.x + 12, playerman.rect.y - 3
                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 > 900 or bullet.y < 0 or bullet.y > 900:
            bullets.pop(bullets.index(bullet))

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

   
                    
    # telling game that key means when a key get pressed
    keys = pygame.key.get_pressed()

    # The player moving when the key a is pressed
    if keys[pygame.K_a] and playerman.x > playerman.speed:
        playerman.x -= playerman.speed

    # The player moving when the key d is pressed
    if keys[pygame.K_d] and playerman.x < 500 - playerman.width - playerman.speed:
        playerman.x += playerman.speed

    # Calling the redraw function
    redrawwindow()
    # updating game
    pygame.display.update()
# quiting the game
pygame.quit()


Asked By: Window man

||

Answers:

You are already moving the pin in the projectile.move method. In the projectile.draw method you just have to rotate the pin. See How do I rotate an image around its center using PyGame? and How to rotate an image(player) to the mouse direction?:


class projectile(object):
    # [...]

    def draw(self):
        self.rect.center = (round(self.x), round(self.y))
        
        angle = math.degrees(math.atan2(-self.diry, self.dirx)) - 90
        rotated_pin = pygame.transform.rotate(self.pin, angle)
        rotated_rect = rotated_pin.get_rect(center = self.rect.center)

        pygame.draw.rect(window,self.color, rotated_rect)
        window.blit(rotated_pin, rotated_rect)
        self.hitbox = (self.x + 20, self.y,30,30)

You have to find a starting position of the pin. The pin should start somewhere on top of the blowpipe. Add a get_pivot method to the Player class:

class Player:
    # [...]

    def get_pivot(self):
        player_rect = self.cannon.get_rect(center = self.get_rect().center)
        return player_rect.centerx, player_rect.top + 103

Add a get_angle method that calculates the angle of the blowpipe:

class Player:
    # [...]

    def get_angle(self):
        pivot_abs = self.get_pivot()
        dx = self.look_at_pos[0] - pivot_abs[0]
        dy = self.look_at_pos[1] - pivot_abs[1]
        return math.degrees(math.atan2(-dy, dx))

Use this methods to compute the top of the blow pipes:

class Player:
    # [...]

    def get_top(self):
        pivot_x, pivot_y = self.get_pivot()
        angle = self.get_angle()
        length = 100
        top_x = pivot_x + length * math.cos(math.radians(angle))
        top_y = pivot_y - length * math.sin(math.radians(angle))
        return top_x, top_y

You can also use the get_pivot and get_angle methods in the draw method:

class Player:
    # [...]

    def draw(self):
        self.rect.topleft = (self.x,self.y)
        pygame.draw.rect(window,self.color,self.hitbox)

        gun_size = self.image.get_size()
        pivot_abs = self.get_pivot()
        pivot_rel = (gun_size[0] // 2, 105)
        angle = self.get_angle() - 90
        
        pygame.draw.rect(window,self.color,self.rect)
        blitRotate(window, self.image,pivot_abs, pivot_rel, angle)

Use get_top to set the starting position of the pin:

mousex, mousey = pygame.mouse.get_pos()
start_x, start_y = playerman.get_top()
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)

Draw the pins in before the blowpipe so it looks like the pins are coming out of the blowpipe:

def redrawwindow():
    window.fill((0,0,0))

    # Drawing the window in
    window.blit(background,(0,0))

    # Drawing the players bullet
    for bullet in bullets:
        bullet.draw()

    # drawing the player in window
    playerman.draw()

Complete examplee:

import pygame,math,random
pygame.init()

# Windowing screen width and height
width = 500
height = 500
window = pygame.display.set_mode((width,height))

# Name of window
pygame.display.set_caption("Game")

# The Background
background = pygame.image.load("img/BG.png")


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)
    
# Player class
class Player:
    def __init__(self,x,y,width,height,color):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.color = color
        self.speed = 4
        self.cannon = pygame.image.load("img/Cannon.png")
        self.cannon = pygame.transform.scale(self.cannon,(self.cannon.get_width()//2, self.cannon.get_height()//2))
        self.rect = pygame.Rect(x,y,width,height)
        self.hitbox = (self.x,self.y,30,30)
        self.image = self.cannon
        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.angle = 0
    def get_rect(self):
        self.rect.topleft = (self.x,self.y)
        return self.rect

    def get_pivot(self):
        player_rect = self.cannon.get_rect(center = self.get_rect().center)
        return player_rect.centerx, player_rect.top + 103

    def get_angle(self):
        pivot_abs = self.get_pivot()
        dx = self.look_at_pos[0] - pivot_abs[0]
        dy = self.look_at_pos[1] - pivot_abs[1]
        return math.degrees(math.atan2(-dy, dx))

    def get_top(self):
        pivot_x, pivot_y = self.get_pivot()
        angle = self.get_angle()
        length = 100
        top_x = pivot_x + length * math.cos(math.radians(angle))
        top_y = pivot_y - length * math.sin(math.radians(angle))
        return top_x, top_y

    def draw(self):
        self.rect.topleft = (self.x,self.y)
        pygame.draw.rect(window,self.color,self.hitbox)

        gun_size = self.image.get_size()
        pivot_abs = self.get_pivot()
        pivot_rel = (gun_size[0] // 2, 105)
        angle = self.get_angle() - 90
        
        pygame.draw.rect(window,self.color,self.rect)
        blitRotate(window, self.image,pivot_abs, pivot_rel, angle)
        
    def lookAt( self, coordinate ):
        self.look_at_pos = coordinate


        

# Players gun
class projectile(object):
    def __init__(self,x,y,dirx,diry,color):
        self.x = x
        self.y = y
        self.dirx = dirx
        self.diry = diry
        self.pin = pygame.image.load("img/Pin.png")
        self.pin = pygame.transform.scale(self.pin,(self.pin.get_width()//6, self.pin.get_height()//6))
        self.rect = self.pin.get_rect()
        self.center = ( self.x, self.y )
        self.speed = 10
        self.color = color
        self.hitbox = (self.x + 20, self.y, 30,40)
    def move(self):
        self.x += self.dirx * self.speed
        self.y += self.diry * self.speed
    def draw(self):
        self.rect.center = (round(self.x), round(self.y))
        
        angle = math.degrees(math.atan2(-self.diry, self.dirx)) - 90
        rotated_pin = pygame.transform.rotate(self.pin, angle)
        rotated_rect = rotated_pin.get_rect(center = self.rect.center)

        pygame.draw.rect(window,self.color, rotated_rect)
        window.blit(rotated_pin, rotated_rect)
        self.hitbox = (self.x + 20, self.y,30,30)
        


# The color white
white = (255,255,255)

# The xy cords, width, height and color of my classes[]

playerman = Player(350,385,34,75,white)



# This is where my balloons get hit by the bullet and disappers
# redrawing window
def redrawwindow():
    window.fill((0,0,0))

    # Drawing the window in
    window.blit(background,(0,0))

    # Drawing the players bullet
    for bullet in bullets:
        bullet.draw()

    # drawing the player in window
    playerman.draw()

# Frames for game
fps = 30
clock = pygame.time.Clock()
#projectile empty list
bullets = []
# main loop
run = True
while run:
    clock.tick(fps)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False


        if event.type == pygame.MOUSEBUTTONDOWN:

            if len(bullets) < 6700:
                mousex, mousey = pygame.mouse.get_pos()
                start_x, start_y = playerman.get_top()
                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 > 900 or bullet.y < 0 or bullet.y > 900:
            bullets.pop(bullets.index(bullet))

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

   
                    
    # telling game that key means when a key get pressed
    keys = pygame.key.get_pressed()

    # The player moving when the key a is pressed
    if keys[pygame.K_a] and playerman.x > playerman.speed:
        playerman.x -= playerman.speed

    # The player moving when the key d is pressed
    if keys[pygame.K_d] and playerman.x < 500 - playerman.width - playerman.speed:
        playerman.x += playerman.speed

    # Calling the redraw function
    redrawwindow()
    # updating game
    pygame.display.update()
# quiting the game
pygame.quit()
Answered By: Rabbid76
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.