Sprite animado a partir de algunas imágenes.
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?
Hay dos tipos de animación: dependiente del fotograma y dependiente del tiempo . Ambos funcionan de manera similar.
Antes del bucle principal
- Cargue todas las imágenes en una lista.
- Crea tres variables:
index
, que realiza un seguimiento del índice actual de la lista de imágenes.current_time
ocurrent_frame
que realiza un seguimiento del tiempo actual o del fotograma actual desde la última vez que se cambió el índice.animation_time
oanimation_frames
que definen cuántos segundos o fotogramas deben pasar antes de cambiar de imagen.
Durante el bucle principal
- Incremente
current_time
por la cantidad de segundos que han pasado desde la última vez que lo incrementamos, o incrementecurrent_frame
en 1. - Compruebe si
current_time >= animation_time
ocurrent_frame >= animation_frame
. Si es cierto, continúe con 3-5. - Restablezca el
current_time = 0
ocurrent_frame = 0
. - Incrementa el índice, a menos que sea igual o mayor que la cantidad de imágenes. En ese caso, reinicie
index = 0
. - 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:
- Cuadro 1; 0,00 segundos; imagen 1
- Cuadro 2; 0,05 segundos; imagen 1
- Cuadro 3; 0,10 segundos; imagen 2
- Cuadro 4; 0,15 segundos; imagen 1
- Cuadro 5; 0,20 segundos; imagen 1
- 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_frame
en 1 en cada llamada, en lugar de lidiar con el tiempo delta ( dt
) y pasarlo a cada objeto.
duendes
Resultado
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.png
y image2.png
dentro de la misma carpeta en la que se encuentra el código.
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 = 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])