Sprite animado a partir de algunas imágenes.

Resuelto lbartolic asked hace 11 años • 4 respuestas

He estado buscando algún buen tutorial sobre cómo hacer animaciones de sprites simples a partir de algunas imágenes en Python usando Pygame. Todavía no he encontrado lo que estoy buscando.

Mi pregunta es simple: cómo hacer un sprite animado a partir de pocas imágenes (por ejemplo: hacer algunas imágenes de explosión con dimensiones de 20x20px para que queden como una sola pero animada)

¿Alguna buena idea?

lbartolic avatar Dec 27 '12 00:12 lbartolic
Aceptado

Hay dos tipos de animación: dependiente del fotograma y dependiente del tiempo . Ambos funcionan de manera similar.


Antes del bucle principal

  1. Cargue todas las imágenes en una lista.
  2. Crea tres variables:
    1. index, que realiza un seguimiento del índice actual de la lista de imágenes.
    2. current_timeo current_frameque realiza un seguimiento del tiempo actual o del fotograma actual desde la última vez que se cambió el índice.
    3. animation_timeo animation_framesque definen cuántos segundos o fotogramas deben pasar antes de cambiar de imagen.

Durante el bucle principal

  1. Incremente current_timepor la cantidad de segundos que han pasado desde la última vez que lo incrementamos, o incremente current_frameen 1.
  2. Compruebe si current_time >= animation_timeo current_frame >= animation_frame. Si es cierto, continúe con 3-5.
  3. Restablezca el current_time = 0o current_frame = 0.
  4. Incrementa el índice, a menos que sea igual o mayor que la cantidad de imágenes. En ese caso, reinicie index = 0.
  5. Cambia la imagen del objeto en consecuencia.

Un ejemplo de trabajo completo

import os
import pygame
pygame.init()

SIZE = WIDTH, HEIGHT = 720, 480
BACKGROUND_COLOR = pygame.Color('black')
FPS = 60

screen = pygame.display.set_mode(SIZE)
clock = pygame.time.Clock()


def load_images(path):
    """
    Loads all images in directory. The directory must only contain images.

    Args:
        path: The relative or absolute path to the directory to load images from.

    Returns:
        List of images.
    """
    images = []
    for file_name in os.listdir(path):
        image = pygame.image.load(path + os.sep + file_name).convert()
        images.append(image)
    return images


class AnimatedSprite(pygame.sprite.Sprite):

    def __init__(self, position, images):
        """
        Animated sprite object.

        Args:
            position: x, y coordinate on the screen to place the AnimatedSprite.
            images: Images to use in the animation.
        """
        super(AnimatedSprite, self).__init__()

        size = (32, 32)  # This should match the size of the images.

        self.rect = pygame.Rect(position, size)
        self.images = images
        self.images_right = images
        self.images_left = [pygame.transform.flip(image, True, False) for image in images]  # Flipping every image.
        self.index = 0
        self.image = images[self.index]  # 'image' is the current image of the animation.

        self.velocity = pygame.math.Vector2(0, 0)

        self.animation_time = 0.1
        self.current_time = 0

        self.animation_frames = 6
        self.current_frame = 0

    def update_time_dependent(self, dt):
        """
        Updates the image of Sprite approximately every 0.1 second.

        Args:
            dt: Time elapsed between each frame.
        """
        if self.velocity.x > 0:  # Use the right images if sprite is moving right.
            self.images = self.images_right
        elif self.velocity.x < 0:
            self.images = self.images_left

        self.current_time += dt
        if self.current_time >= self.animation_time:
            self.current_time = 0
            self.index = (self.index + 1) % len(self.images)
            self.image = self.images[self.index]

        self.rect.move_ip(*self.velocity)

    def update_frame_dependent(self):
        """
        Updates the image of Sprite every 6 frame (approximately every 0.1 second if frame rate is 60).
        """
        if self.velocity.x > 0:  # Use the right images if sprite is moving right.
            self.images = self.images_right
        elif self.velocity.x < 0:
            self.images = self.images_left

        self.current_frame += 1
        if self.current_frame >= self.animation_frames:
            self.current_frame = 0
            self.index = (self.index + 1) % len(self.images)
            self.image = self.images[self.index]

        self.rect.move_ip(*self.velocity)

    def update(self, dt):
        """This is the method that's being called when 'all_sprites.update(dt)' is called."""
        # Switch between the two update methods by commenting/uncommenting.
        self.update_time_dependent(dt)
        # self.update_frame_dependent()


def main():
    images = load_images(path='temp')  # Make sure to provide the relative or full path to the images directory.
    player = AnimatedSprite(position=(100, 100), images=images)
    all_sprites = pygame.sprite.Group(player)  # Creates a sprite group and adds 'player' to it.

    running = True
    while running:

        dt = clock.tick(FPS) / 1000  # Amount of seconds between each loop.

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RIGHT:
                    player.velocity.x = 4
                elif event.key == pygame.K_LEFT:
                    player.velocity.x = -4
                elif event.key == pygame.K_DOWN:
                    player.velocity.y = 4
                elif event.key == pygame.K_UP:
                    player.velocity.y = -4
            elif event.type == pygame.KEYUP:
                if event.key == pygame.K_RIGHT or event.key == pygame.K_LEFT:
                    player.velocity.x = 0
                elif event.key == pygame.K_DOWN or event.key == pygame.K_UP:
                    player.velocity.y = 0

        all_sprites.update(dt)  # Calls the 'update' method on all sprites in the list (currently just the player).

        screen.fill(BACKGROUND_COLOR)
        all_sprites.draw(screen)
        pygame.display.update()


if __name__ == '__main__':
    main()

Cuando elegir cual

La animación dependiente del tiempo le permite reproducir la animación a la misma velocidad, sin importar cuán lenta/rápida sea la velocidad de fotogramas o cuán lenta/rápida sea su computadora. Esto permite que su programa cambie libremente la velocidad de cuadros sin afectar la animación y también será consistente incluso si la computadora no puede mantener el ritmo de la velocidad de cuadros. Si el programa se retrasa, la animación alcanzará el estado que debería haber tenido, como si no hubiera ocurrido ningún retraso.

Sin embargo, puede suceder que el ciclo de animación no se sincronice con la velocidad de fotogramas, lo que hace que el ciclo de animación parezca irregular. Por ejemplo, digamos que tenemos los fotogramas actualizándose cada 0,05 segundos y la animación cambia de imagen cada 0,075 segundos, entonces el ciclo sería:

  1. Cuadro 1; 0,00 segundos; imagen 1
  2. Cuadro 2; 0,05 segundos; imagen 1
  3. Cuadro 3; 0,10 segundos; imagen 2
  4. Cuadro 4; 0,15 segundos; imagen 1
  5. Cuadro 5; 0,20 segundos; imagen 1
  6. Cuadro 6; 0,25 segundos; imagen 2

Etcétera...

La función dependiente de fotogramas puede verse más fluida si su computadora puede manejar la velocidad de fotogramas de manera consistente. Si ocurre un retraso, se detendrá en su estado actual y se reiniciará cuando el retraso se detenga, lo que hace que el retraso sea más notorio. Esta alternativa es un poco más fácil de implementar ya que solo necesita incrementar current_frameen 1 en cada llamada, en lugar de lidiar con el tiempo delta ( dt) y pasarlo a cada objeto.

duendes

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Resultado

ingrese la descripción de la imagen aquí

Ted Klein Bergman avatar Feb 02 '2017 22:02 Ted Klein Bergman

Podrías intentar modificar tu objeto para que cambie su imagen por una diferente en el interior update. De esa manera, cuando se renderice el objeto, se verá animado.

Editar :

Aquí hay un ejemplo rápido que elaboré:

import pygame
import sys

def load_image(name):
    image = pygame.image.load(name)
    return image

class TestSprite(pygame.sprite.Sprite):
    def __init__(self):
        super(TestSprite, self).__init__()
        self.images = []
        self.images.append(load_image('image1.png'))
        self.images.append(load_image('image2.png'))
        # assuming both images are 64x64 pixels

        self.index = 0
        self.image = self.images[self.index]
        self.rect = pygame.Rect(5, 5, 64, 64)

    def update(self):
        '''This method iterates through the elements inside self.images and 
        displays the next one each tick. For a slower animation, you may want to 
        consider using a timer of some sort so it updates slower.'''
        self.index += 1
        if self.index >= len(self.images):
            self.index = 0
        self.image = self.images[self.index]

def main():
    pygame.init()
    screen = pygame.display.set_mode((250, 250))

    my_sprite = TestSprite()
    my_group = pygame.sprite.Group(my_sprite)

    while True:
        event = pygame.event.poll()
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit(0)

        # Calling the 'my_group.update' function calls the 'update' function of all 
        # its member sprites. Calling the 'my_group.draw' function uses the 'image'
        # and 'rect' attributes of its member sprites to draw the sprite.
        my_group.update()
        my_group.draw(screen)
        pygame.display.flip()

if __name__ == '__main__':
    main()

Se supone que tienes dos imágenes llamadas image1.pngy image2.pngdentro de la misma carpeta en la que se encuentra el código.

Michael0x2a avatar Dec 26 '2012 17:12 Michael0x2a

Deberías tener todas tus animaciones de sprites en un gran "lienzo", por lo que para 3 fotogramas de sprites de explosión de 20x20 tendrás una imagen de 60x20. Ahora puedes obtener los fotogramas correctos cargando un área de la imagen.

Dentro de tu clase de sprite, lo más probable es que en el método de actualización tengas algo como esto (codificado para simplificar, prefiero tener una clase separada que sea responsable de elegir el fotograma de animación correcto). self.f = 0en __init__.

def update(self):
    images = [[0, 0], [20, 0], [40, 0]]
    self.f += 1 if self.f < len(images) else 0
    self.image = your_function_to_get_image_by_coordinates(images[i])
Ruslan Osipov avatar Dec 26 '2012 18:12 Ruslan Osipov