Sprite animado de pocas imágenes.

He estado buscando un buen tutorial sobre cómo hacer una animación simple de sprites desde pocas 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 pocas imágenes de explosión con dimensiones de 20x20px para ser como una pero animadas)

¿Alguna buena idea?

Puedes intentar modificar tu sprite para que cambie su imagen por otra diferente dentro de la update . De esa manera, cuando el sprite se renderice, se verá animado.

Editar :

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

 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() 

Asume que tienes dos imágenes llamadas image1.png e image2.png dentro de la misma carpeta en la que se encuentra el código.

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


Antes del bucle principal

  1. Cargar 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_time o current_frame que realiza un seguimiento de la hora actual o del fotogtwig actual desde la última vez que se cambió el índice.
    3. animation_time o animation_frames que definen cuántos segundos o fotogtwigs deben pasar antes de cambiar de imagen.

Durante el bucle principal

  1. Incrementa current_time por la cantidad de segundos transcurridos desde la última vez que lo incrementamos, o incrementa current_frame en 1.
  2. Compruebe si current_time >= animation_time o current_frame >= animation_frame . Si es cierto, continúa con 3-5.
  3. Restablezca current_time = 0 o current_frame = 0 .
  4. Incremente el índice, a menos que sea igual o mayor que la cantidad de imágenes. En ese caso, restablecer el index = 0 .
  5. Cambia la imagen del sprite en consecuencia.

Un ejemplo completo de trabajo.

 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() 

Cuándo elegir cuál

La animación dependiente del tiempo le permite reproducir la animación a la misma velocidad, sin importar qué tan lenta o rápida sea la velocidad de fotogtwigs o lenta o rápida sea su computadora. Esto le permite a su progtwig cambiar libremente la velocidad de cuadros sin afectar la animación y también será consistente incluso si la computadora no puede mantenerse al día con la velocidad de cuadros. Si el progtwig se retrasa, la animación alcanzará el estado en el que debería haber estado como si no hubiera pasado ningún retraso.

Aunque puede suceder que el ciclo de animación no se sincronice con la velocidad de cuadros, lo que hace que el ciclo de animación parezca irregular. Por ejemplo, digamos que tenemos los cuadros que se actualizan cada 0.05 segundos y la imagen de cambio de animación cada 0.075 segundos, entonces el ciclo sería:

  1. Fotogtwig 1; 0.00 segundos; imagen 1
  2. Fotogtwig 2; 0.05 segundos; imagen 1
  3. Fotogtwig 3; 0.10 segundos; imagen 2
  4. Fotogtwig 4; 0.15 segundos; imagen 1
  5. Fotogtwig 5; 0.20 segundos; imagen 1
  6. Fotogtwig 6; 0.25 segundos; imagen 2

Y así…

La función dependiente del cuadro puede verse mejor si su computadora puede manejar la tasa de cuadros de manera constante. 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 notable. Esta alternativa es un poco más fácil de implementar, ya que solo necesita incrementar current_frame con 1 en cada llamada, en lugar de tratar con el tiempo delta ( dt ) y pasarlo a cada objeto.

Sprites

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

Resultado

introduzca la descripción de la imagen aquí

Debes tener todas tus animaciones de sprite en un gran “canvas”, por lo que para 3 marcos de sprite de explosión de 20×20 tendrás una imagen de 60×20. Ahora puedes obtener los marcos correctos cargando un área de la imagen.

Dentro de su clase de sprites, lo más probable es que en el método de actualización usted deba tener algo como esto (codificado por simplicidad, prefiero tener una clase separada para ser responsable de elegir el cuadro de animación correcto). self.f = 0 en __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])