Анимированный спрайт из нескольких изображений
Я искал хороший учебник о создании простой анимации спрайта из нескольких изображений в Python с помощью Pygame. Я все еще не нашел то, что искал.
мой вопрос прост: как сделать анимированный спрайт из нескольких изображений (например: сделать несколько изображений взрыва с размерами 20x20px как один, но анимированный)
хорошие идеи?
3 ответов
вы можете попробовать изменить свой спрайт, чтобы он менял свое изображение на другое внутри update
. Таким образом, когда спрайт отображается, он будет выглядеть анимированным.
редактировать:
вот краткий пример, который я составил:
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()
предполагается, что у вас есть два изображения, называемые image1.png
и image2.png
внутри той же папке кода.
существует два типа анимации: зависит от кадра и зависит от времени. Оба работают одинаково.
перед главным циклом
- Загрузите все изображения в список.
- создать три переменных:
-
index
, который отслеживает текущий индекс списка изображений. -
current_time
илиcurrent_frame
который отслеживает текущее время или текущий кадр с момента последнего индекс поменялся. -
animation_time
илиanimation_frames
это определяет, сколько секунд или кадров должно пройти перед переключением изображения.
-
во время основного цикла
- инкремент
current_time
на количество секунд, прошедших с момента последнего увеличения или увеличенияcurrent_frame
к 1. - проверить, если
current_time >= animation_time
илиcurrent_frame >= animation_frame
. Если true, продолжайте с 3-5. - сбросить
current_time = 0
илиcurrent_frame = 0
. - увеличить индекс, если он не будет равен или больше, чем количество изображений. В этом случае сбросьте
index = 0
. - измените изображения спрайта.
полный рабочий пример
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()
когда выбирал
зависит от времени анимация позволяет воспроизводить анимацию с той же скоростью, независимо от того, как медленно / быстро частота кадров или медленно / быстро ваш компьютер. Это позволяет вашей программе свободно изменять частоту кадров, не влияя на анимацию, и она также будет согласована, даже если компьютер не может идти в ногу с частотой кадров. Если программа отстает, анимация догонит состояние, как будто никакого отставания не произошло.
хотя может случиться так, что цикл анимации не синхронизируется с частотой кадров, что делает цикл анимации нерегулярным. Например, скажем, что у нас есть кадры, обновляющие каждый 0,05 секунды и анимация переключают изображение каждые 0,075 секунды, тогда цикл будет:
- Кадр 1; 0.00 секунд; изображение 1
- Кадр 2; 0,05 секунды; изображение 1
- Кадр 3; 0,10 секунды; изображение 2
- Кадр 4; 0,15 секунды; изображение 1
- Кадр 5; 0,20 секунды; изображение 1
- кадр 6; 0,25 секунды; изображение 2
и так далее...
зависит от кадра можно посмотреть более плавный, если ваш компьютер может обрабатывать частоту кадров последовательно. Если отставание произойдет, оно приостановится в текущем состоянии и перезапустится, когда отставание остановится, что делает отставание более заметным. Эта альтернатива немного проще реализовать, так как вам просто нужно увеличить current_frame
С 1 на каждом вызове, вместо того, чтобы иметь дело с Дельта-временем (dt
) и передавая его каждому объекту.
спрайты
результат
У вас должны быть все ваши анимации спрайтов на одном большом "холсте", поэтому для 3 кадров спрайта взрыва 20x20 у вас будет изображение 60x20. Теперь вы можете получить правильные кадры, загрузив область изображения.
внутри вашего класса sprite, скорее всего, в методе обновления у вас должно быть что-то вроде этого (жестко закодированный для простоты, я предпочитаю иметь отдельный класс, который будет отвечать за выбор правильного кадра анимации). self.f = 0
on __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])