4
\$\begingroup\$

Here's my code:

import sys
import pygame
pygame.init()

screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
running = True

class Actor:

    def __init__(self, x, y, w, h):
        self.x = x
        self.y = y
        self.w = w
        self.h = h
        self.surface = pygame.image.load("GFX/player.png")
        self.rotated_surface = self.surface.copy()
        self.rect = self.surface.get_rect()

class Player(Actor):

    def __init__(self):
        Actor.__init__(self, 0, 0, 32, 32)
        self.directions = [False, False, False, False]
        self.speed = 0.1

    def rotate(self, angle):
        rot_image = pygame.transform.rotozoom(self.surface, angle, 1)
        rot_rect = self.rect.copy()
        rot_rect.center = rot_image.get_rect().center
        self.rotated_surface = rot_image

    def move(self):
        if self.directions[0]:
            self.y -= self.speed
        if self.directions[1]:
            self.y += self.speed
        if self.directions[2]:
            self.x -= self.speed
        if self.directions[3]:
            self.x += self.speed

    def draw(self):
        screen.blit(self.rotated_surface, (self.x, self.y))

player = Player()

def redraw():
    screen.fill((75, 0, 0))
    player.draw()
    player.move()
    pygame.display.flip()

while (running):
    for e in pygame.event.get():
        if e.type == pygame.QUIT:
            sys.exit()
        elif e.type == pygame.KEYDOWN:
            if e.key == pygame.K_ESCAPE:
                sys.exit()
            if e.key == pygame.K_w:
                player.directions[0] = True
            if e.key == pygame.K_s:
                player.directions[1] = True
            if e.key == pygame.K_a:
                player.directions[2] = True
            if e.key == pygame.K_d:
                player.directions[3] = True
        elif e.type == pygame.KEYUP:
            if e.key == pygame.K_w:
                player.directions[0] = False
            if e.key == pygame.K_s:
                player.directions[1] = False
            if e.key == pygame.K_a:
                player.directions[2] = False
            if e.key == pygame.K_d:
                player.directions[3] = False
        elif e.type == pygame.MOUSEMOTION:
            mouse_x = pygame.mouse.get_pos()[0]
            mouse_y = pygame.mouse.get_pos()[1]
            angle = math.atan2(mouse_y - player.y, mouse_x - player.x)
            angle = angle * (180 / math.pi)
            player.rotate(angle)

    redraw()

Here's the image:

enter image description here

The blue arrow represents the "weapon" / "eyes" of the shooter, and the green arrow represents the mouse cursor. How can I use the transform module to rotate the image "in the direction" of the mouse cursor?

\$\endgroup\$
7
\$\begingroup\$

First you need to calculate the vector pointing from your player to the current mouse position. This can be done by subtracting the player's position with the mouse's position:

mouse_x, mouse_y = pygame.mouse.get_pos()
rel_x, rel_y = mouse_x - self.x, mouse_y - self.y

Then calculate the angle:

angle = math.atan2(rel_y, rel_x)

This will calculate the angle in radians while the pygame.transform.rotate(surface, angle)takes the angle in degree. It will also calculate the angle in the other direction (counter-clockwise) so you need to negate the result. If the image starts at the wrong direction just add/subtract 90 or 180 until it's right:

angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)

After rotating, you need to reposition your rect so that the image remains in the same spot. Rotating images changes it size which makes it look slightly out of position each rotation. I usually position it so its center is consistent.

self.image = pygame.transform.rotate(self.original_image, int(angle))
self.rect = self.image.get_rect(center=self.position)

Notice that I rotate an attribute named self.original_image. When applying rotation to an image it becomes slightly distorted. Doing this several times will eventually make your image unrecognizable. So always have a reference to the original image that's unaltered.

Also, naming your attribute self.image instead of self.surface and inheriting from pygame.sprite.Sprite allows you to use many convenient functions described in the pygame.sprite module.

The full code:

import math

def rotate(self):
    mouse_x, mouse_y = pygame.mouse.get_pos()
    rel_x, rel_y = mouse_x - self.x, mouse_y - self.y
    angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)
    self.image = pygame.transform.rotate(self.original_image, int(angle))
    self.rect = self.image.get_rect(center=self.position)
\$\endgroup\$
3
  • \$\begingroup\$ 360 / (2 * PI) is the same as 180 / PI \$\endgroup\$
    – Bálint
    Dec 8 '16 at 12:10
  • \$\begingroup\$ @Bálint Yes, you're correct, stupid mistake by me. Editing it in! \$\endgroup\$ Dec 8 '16 at 12:15
  • 1
    \$\begingroup\$ That's not a mistake, neither is it stupid \$\endgroup\$
    – Bálint
    Dec 8 '16 at 14:02
2
\$\begingroup\$

I don't have experience with python, but try: atan2(mouseY, mouseX) -and yes, thats (y, x) not (x,y).

\$\endgroup\$
0
\$\begingroup\$

You can also use vectors to get the angle. The pygame.math.Vector2 class has a .as_polar method which returns the polar coordinates, the radius (distance) and the angle to the target.

import pygame as pg
from pygame.math import Vector2


class Player(pg.sprite.Sprite):

    def __init__(self, pos):
        super().__init__()
        self.image = pg.Surface((50, 30), pg.SRCALPHA)
        pg.draw.polygon(self.image, pg.Color('steelblue2'),
                        [(0, 0), (50, 15), (0, 30)])
        self.orig_image = self.image  # Store a reference to the original.
        self.rect = self.image.get_rect(center=pos)
        self.pos = Vector2(pos)

    def update(self):
        self.rotate()

    def rotate(self):
        # The vector to the target (the mouse position).
        direction = pg.mouse.get_pos() - self.pos
        # .as_polar gives you the polar coordinates of the vector,
        # i.e. the radius (distance to the target) and the angle.
        radius, angle = direction.as_polar()
        # Rotate the image by the negative angle (y-axis in pygame is flipped).
        self.image = pg.transform.rotate(self.orig_image, -angle)
        # Create a new rect with the center of the old rect.
        self.rect = self.image.get_rect(center=self.rect.center)


pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group(Player((300, 220)))
done = False

while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True

    all_sprites.update()
    screen.fill((30, 30, 30))
    all_sprites.draw(screen)

    pg.display.flip()
    clock.tick(30)
\$\endgroup\$
0
\$\begingroup\$

You have to compute the angle of the vector from the player to the mouse. get the mouse position by pygame.mouse.get_pos() and the rectangle (pygame.Rect) around the player (self.rect ):

mx, my = pygame.mouse.get_pos()
player_rect = self.rect 

Calculate the vector from the player to the mouse and compute the angle of vector by math.atan2. The y-axis needs to be reversed (-dy) as the y-axis is generally pointing up, but in the PyGame coordinate system the y-axis is pointing down.

dx, dy = mx - player_rect.centerx, player_rect.centery - my
angle = math.degrees(math.atan2(-dy, dx)) - correction_angle

In addition, a correction angle must be deducted (- correction_angle). The correction angle depends on the Sprite. If the Sprite

is looking to the right, the correction angle is 0: correction_angle = 0
is looking up, the correction angle is 90: correction_angle = 90
is looking to the left, the correction angle is 180: correction_angle = 180
is looking down, the correction angle is 270: correction_angle = 270

Rotate the player with pygame.transform.rotate() by the angle around its center:
(See also How do I rotate an image around its center using Pygame?)

rot_image = pygame.transform.rotate(Player_1, angle)
rot_image_rect = rot_image.get_rect(center=player_rect.center)

Minimal example: repl.it/@Rabbid76/PyGame-RotateWithMouse

import math
import pygame

pygame.init()
window = pygame.display.set_mode((300, 300))
player = pygame.image.load("player.png").convert_alpha()

#   0 - image is looking to the right
#  90 - image is looking up
# 180 - image is looking to the left
# 270 - image is looking down
correction_angle = 90

run = True
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    player_pos  = window.get_rect().center
    player_rect = player.get_rect(center = player_pos)

    mx, my = pygame.mouse.get_pos()
    dx, dy = mx - player_rect.centerx, my - player_rect.centery
    angle = math.degrees(math.atan2(-dy, dx)) - correction_angle

    rot_image      = pygame.transform.rotate(player, angle)
    rot_image_rect = rot_image.get_rect(center = player_rect.center)

    window.fill((255, 255, 255))
    window.blit(rot_image, rot_image_rect.topleft)
    pygame.display.flip()

pygame.quit()
exit()

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.