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\$
5
\$\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\$
  • \$\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\$ – Ted Klein Bergman Dec 8 '16 at 12:15
  • \$\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\$

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.