That guy
That guy

Reputation: 133

Pygame Transformation: rotate shape on key press

I'm fairly new to Pygame, and can't seem to find a solid answer on this. I have a shape, specifically an ellipse, that I want to rotate both left and right. The key bind would be a and d, as the arrow keys are already binded to move left and right on an x,y axis.

I know that it involves pygame.transform.rotate, however I can't seem to implement this right.

def main():
    #Setup basic variables and methods for pygame
    pygame.init()
    windowWidth = 800
    windowHeight = 700
    fps = 45
    clock = pygame.time.Clock()
    gameWindow = pygame.display.set_mode((windowWidth, windowHeight))
    surface = pygame.Surface((50,50))
    BLACK = (0, 0, 0)
    WHITE = (255, 255, 255)

    shipX = windowWidth/2
    shipY = windowWidth/2
    shipSpeed = 4

    while(True):

        pygame.draw.ellipse(gameWindow, WHITE, (shipX, shipY, 20, 30))

        #Monitor the FPS of the game
        clock.tick(fps)

        for event in pygame.event.get():
            # ________________________________________
            if event.type == pygame.QUIT:
                gameExit()

        rotate = 0
        pressed = pygame.key.get_pressed()
        if pressed[pygame.K_UP] and shipY > shipSpeed: shipY -= shipSpeed
        if pressed[pygame.K_DOWN] and shipY < windowHeight - shipSpeed - 20: shipY += shipSpeed
        if pressed[pygame.K_LEFT] and shipX > shipSpeed:shipX -= shipSpeed
        if pressed[pygame.K_RIGHT] and shipX < windowWidth - shipSpeed - 20: shipX += shipSpeed
        if pressed[ord('a')]: rotate = pygame.transform.rotate(surface, -20)
        if pressed[ord('d')]: rotate = pygame.transform.rotate(surface, 20)

        gameWindow.fill(BLACK)
        # 'flip' display - always after drawing...
        pygame.display.flip()

The expected result is that the shape will change it's angle, and then move accordingly.

Again, I'm very new to pygame, so any detailed help would be appreciated.

Upvotes: 3

Views: 960

Answers (2)

sloth
sloth

Reputation: 101092

Your problem is that you draw the ellipse directly on the screen, but you should draw your ellipse on another Surface.

Then you can rotate that new Surface with pygame.transform.rotate.

Here's a simple example:

import pygame
import random

def main():
    pygame.init()
    screen = pygame.display.set_mode((500, 500))
    screen_rect = screen.get_rect()
    clock = pygame.time.Clock()
    surface = pygame.Surface((100, 200))
    surface.set_colorkey((2, 3, 4))
    surface.fill((2, 3, 4))
    rect = surface.get_rect(center=(100, 100))
    pygame.draw.ellipse(surface, pygame.Color('white'), (0, 0, 100, 200))
    angle = 0    
    dt = 0
    while True:
        events = pygame.event.get()
        for e in events:
            if e.type == pygame.QUIT:
                return

        pressed = pygame.key.get_pressed()
        if pressed[pygame.K_UP]: rect.move_ip(0, -5)
        if pressed[pygame.K_DOWN]: rect.move_ip(0, 5)
        if pressed[pygame.K_LEFT]: rect.move_ip(-5, 0)
        if pressed[pygame.K_RIGHT]: rect.move_ip(5, 0)
        if pressed[pygame.K_a]: angle += 1
        if pressed[pygame.K_d]: angle -= 1

        rotated = pygame.transform.rotate(surface, angle)
        rect = rotated.get_rect(center=rect.center)

        rect.clamp_ip(screen_rect)

        screen.fill(pygame.Color('dodgerblue'))
        screen.blit(rotated, rect.topleft)
        pygame.display.update()
        dt = clock.tick(60)

if __name__ == '__main__':
    main()

Note that I use a Rect to store the position of the object because it's easy then to rotate the Surface around its center (by setting its center attribute), and to ensure the Surface does not go outside the screen (by using clamp_ip).

Also, it's important to always rotate the source Surface, and not the already rotated Surface. Otherwise, you'll get distortions.


Note that we have three things here: an image, a position, and some behaviour logic. Whenever you see these things together, consider putting them together into a class. Pygame already offers a nice class for this, called Sprite.

Here's the same example, but Sprite-based:

import pygame
import random

class Thingy(pygame.sprite.Sprite):
    def __init__(self, area):
        super().__init__()
        # image is what get's painted on the screen
        self.image = pygame.Surface((100, 200))
        self.image.set_colorkey((2, 3, 4))
        self.image.fill((2, 3, 4))
        pygame.draw.ellipse(self.image, pygame.Color('white'), (0, 0, 100, 200))
        # we keep a reference to the original image
        # since we use that for rotation to prevent distortions
        self.original = self.image.copy()
        # rect is used to determine the position of a sprite on the screen
        # the Rect class also offers a lot of useful functions
        self.rect = self.image.get_rect(center=(100, 100))
        self.angle = 0  
        self.area = area

    def update(self, events, dt):
        pressed = pygame.key.get_pressed()
        if pressed[pygame.K_UP]: self.rect.move_ip(0, -5)
        if pressed[pygame.K_DOWN]: self.rect.move_ip(0, 5)
        if pressed[pygame.K_LEFT]: self.rect.move_ip(-5, 0)
        if pressed[pygame.K_RIGHT]: self.rect.move_ip(5, 0)
        if pressed[pygame.K_a]: self.angle += 1
        if pressed[pygame.K_d]: self.angle -= 1

        # let's rotate the image, but ensure that we keep the center position
        # so it doesn't "jump around"
        self.image = pygame.transform.rotate(self.original, self.angle)
        self.rect = self.image.get_rect(center=self.rect.center)
        self.rect.clamp_ip(self.area)

def main():
    pygame.init()
    screen = pygame.display.set_mode((500, 500))
    screen_rect = screen.get_rect()
    clock = pygame.time.Clock()
    sprites = pygame.sprite.Group(Thingy(screen_rect))
    dt = 0
    while True:
        # nice clean main loop
        # all game logic goes into the sprites

        # handle "global" events
        events = pygame.event.get()
        for e in events:
            if e.type == pygame.QUIT:
                return

        # update all sprites
        sprites.update(events, dt)

        # draw everything
        screen.fill(pygame.Color('dodgerblue'))
        sprites.draw(screen)
        pygame.display.update()
        dt = clock.tick(60)

if __name__ == '__main__':
    main()

Upvotes: 4

Alec
Alec

Reputation: 9575

You should make a class for your object:

class myRect(pygame.Surface):
    def __init__(self, parent, xpos, ypos, width, height):
      super(myRect, self).__init__(width, height)
      self.xpos = xpos
      self.ypos = ypos
      self.parent = parent

    def update(self, parent):
      parent.blit(self, (self.xpos, self.ypos))

    def rotate(self, angle):
      #(your rotation code goes here)

Upvotes: 0

Related Questions