rotate the currently playing animation with mouse movement

Viewed 76

I tried with a for loop, so every animation that is executed according to the state in the Animations class is stored in an instance variable belonging to the Player class, i.e. self.images is matched with the instance variable self.original_image and then rotates according to the specified angle, but this doesn't work, pixel in the picture becomes messy and rotates aimlessly and soon pygame raises an error : pygame.error: Out of memory. I've tried other ways, such as replacing the original animation list with a rotated image list, but the result is the same.

import os
import sys
import math

import pygame


clock = pygame.time.Clock()
screen = pygame.display.set_mode((500, 500))

FPS = 60


class Animations:
    def __init__(self):
        self.frame = 0
        self.animations_loop = []
        self.animations_db = {}

    @staticmethod
    def search_png(path):
        return [img for img in os.listdir(path) if img.endswith(".png")]

    @staticmethod
    def load_image(path):
        img = pygame.image.load(path)
        trans = pygame.transform.scale(img, (80, 80))

        return trans

    def load_images(self, path):
        images = []
        
        images_dir = self.search_png(path)
        for image in images_dir:
            load = self.load_image(f"{path}/{image}")

            images.append(load)

        return images

    def load_animations(self, name, images, frame_durations, loop=True):
        images = self.load_images(images)

        if loop:
            self.animations_loop.append(name)

        data = []
        for image in range(len(images)):
            for _ in range(frame_durations):
                data.append(images[image])

        self.animations_db[name] = data

    def get_animations(self):
        return self.animations_db[self.state]

    def set_state(self, new_state):
        self.state = new_state

    def frame_control(self):
        if self.frame >= len(self.animations_db[self.state]):
            self.frame = 0 if self.state in self.animations_loop else len(self.animations_db[self.state])

    def frame_increase(self):
        self.frame += 1

    def play(self, screen, position):
        self.frame_increase()
        self.frame_control()
        screen.blit(self.animations_db[self.state][self.frame-1], position)


class Player:
    def __init__(self):
        path = "..\..\sprites\sprite Rotoscoped zombie and soldier"
        self.anim = Animations()
        self.anim.load_animations("idle", f"{path}\soldier-idle", 4)
        self.anim.load_animations("walk", f"{path}\soldier-walk", 4)
        self.anim.set_state("idle")
        self.images = self.anim.get_animations()
        self.original_image = self.images
        self.position = pygame.math.Vector2((200, 200))
        self.rect = self.images[0].get_rect(center=self.position)

    def move(self, up=False, down=False, right=False, left=False):
        move = [0, 0]
        
        if up:
            move[1] = -2
        elif down:
            move[1] = 2
        elif right:
            move[0] = 2
        elif left:
            move[0] = -2

        self.rect.x += move[0]
        self.rect.y += move[1]

        if move[0] != 0 or move[1] != 0:
            self.anim.set_state("walk")

    def rotate(self, mx, my):
        for image in range(len(self.images)):
            rel_x, rel_y = self.position[0] - mx, self.position[1] - my
            angle = (180 / math.pi) * -math.atan2(rel_x, rel_y)
            self.images[image] = pygame.transform.rotate(self.original_image[image], angle)
            self.rect = self.images[0].get_rect(center=self.position)

    def draw(self, screen):
        self.anim.play(screen, (self.rect.x, self.rect.y))


# init
soldier = Player()


while True:
    clock.tick(FPS)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == pygame.KEYUP:
            soldier.anim.set_state("idle")

    mx, my = pygame.mouse.get_pos()
    soldier.rotate(mx, my)

    key_pressed = pygame.key.get_pressed()
    if key_pressed[pygame.K_w]:
        soldier.move(up=True)
    elif key_pressed[pygame.K_s]:
        soldier.move(down=True)
    elif key_pressed[pygame.K_d]:
        soldier.move(right=True)
    elif key_pressed[pygame.K_a]:
        soldier.move(left=True)

    screen.fill((0, 0, 0))

    soldier.draw(screen)

    pygame.display.update()
1 Answers

Actually only have 1 image list because self.original_image = self.images doesn't create a new list. self.images and self.original_image are references to the same list. What you want are 2 lists with the same objects. Create a Shallow copy:

self.original_image = self.images

self.original_image = self.images[:]

In addition, you can simplify the rotate method. If you rotate the images, the self.images list canbe generated from the self.original_image list:

def rotate(self, mx, my):
    
    rel_x, rel_y = self.position[0] - mx, self.position[1] - my
    angle = (180 / math.pi) * -math.atan2(rel_x, rel_y)

    for i, img in enumerate(self.original_image):
        self.images[i] = pygame.transform.rotate(img, angle)
            
    self.rect = self.images[0].get_rect(center=self.position)
Related