Python 来自少数图像的动画精灵

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14044147/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 10:15:43  来源:igfitidea点击:

Animated sprite from few images

pythonanimationspritepygame

提问by lbartolic

I've been searching for some good tutorial about making simple sprite animation from few images in Python using Pygame. I still haven't found what I'm looking for.

我一直在寻找一些关于使用 Pygame 从 Python 中的少量图像制作简单精灵动画的好教程。我还没有找到我要找的东西。

My question is simple: how to make an animated sprite from few images (for an example: making few images of explosion with dimensions 20x20px to be as one but animated)

我的问题很简单:如何从几个图像制作动画精灵(例如:制作一些尺寸为 20x20px 的爆炸图像作为一个但动画)

Any good ideas?

有什么好主意吗?

采纳答案by Michael0x2a

You could try modifying your sprite so that it swaps out its image for a different one inside update. That way, when the sprite is rendered, it'll look animated.

您可以尝试修改您的精灵,以便将其图像换成内部不同的图像update。这样,当精灵被渲染时,它会看起来很动画。

Edit:

编辑

Here's a quick example I drew up:

这是我草拟的一个快速示例:

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

It assumes that you have two images called image1.pngand image2.pnginside the same folder the code is in.

它假设您有两个图像被调用image1.png并且image2.png在代码所在的同一文件夹中。

回答by Ruslan Osipov

You should have all your sprite animations on one big "canvas", so for 3 20x20 explosion sprite frames you will have 60x20 image. Now you can get right frames by loading an area of the image.

您应该将所有精灵动画放在一个大“画布”上,因此对于 3 个 20x20 爆炸精灵帧,您将拥有 60x20 图像。现在您可以通过加载图像区域来获得正确的帧。

Inside your sprite class, most likely in update method you should have something like this (hardcoded for simplicity, I prefer to have separate class to be responsible for picking the right animation frame). self.f = 0on __init__.

在你的精灵类中,很可能在 update 方法中你应该有这样的东西(为了简单起见,硬编码,我更喜欢有单独的类来负责选择正确的动画帧)。self.f = 0__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])

回答by Ted Klein Bergman

There are two types of animation: frame-dependentand time-dependent. Both work in similar fashion.

有两种类型的动画:frame-dependenttime-dependent。两者都以类似的方式工作。



Before the main loop

在主循环之前

  1. Load all images into a list.
  2. Create three variable:
    1. index, that keeps track on the current index of the image list.
    2. current_timeor current_framethat keeps track on the current time or current frame since last the index switched.
    3. animation_timeor animation_framesthat define how many seconds or frames should pass before switching image.
  1. 将所有图像加载到列表中。
  2. 创建三个变量:
    1. index,跟踪图像列表的当前索引。
    2. current_time或者current_frame跟踪自上次索引切换以来的当前时间或当前帧。
    3. animation_time或者animation_frames定义切换图像之前应该经过多少秒或帧。


During the main loop

在主循环期间

  1. Increment current_timeby the amount of seconds that has passed since we last incremented it, or increment current_frameby 1.
  2. Check if current_time >= animation_timeor current_frame >= animation_frame. If true continue with 3-5.
  3. Reset the current_time = 0or current_frame = 0.
  4. Increment the index, unless if it'll be equal or greater than the amount of images. In that case, reset index = 0.
  5. Change the sprite's image accordingly.
  1. 增量current_time由已通过我们上次增加,或增量的秒数current_frame1。
  2. 检查是否current_time >= animation_timecurrent_frame >= animation_frame。如果为真,继续 3-5。
  3. 重置current_time = 0current_frame = 0
  4. 增加索引,除非它等于或大于图像数量。在这种情况下,重置index = 0
  5. 相应地更改精灵的图像。


A full working example

一个完整的工作示例

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


When to chose which

什么时候选哪个

Time-dependentanimation allows you to play the animation at the same speed, no matter how slow/fast the frame-rate is or slow/fast your computer is. This allows your program to freely change the framerate without affecting the animation and it'll also be consistent even if the computer cannot keep up with the framerate. If the program lags the animation will catch up to the state it should've been as if no lag had happened.

时间相关动画允许您以相同的速度播放动画,无论帧速率多慢/多快或计算机多慢/多快。这允许您的程序在不影响动画的情况下自由更改帧率,即使计算机跟不上帧率,它也将保持一致。如果程序滞后,动画将赶上状态,就好像没有发生滞后一样。

Although, it might happen that the animation cycle don't synch up with the framerate, making the animation cycle seem irregular. For example, say that we have the frames updating every 0.05 second and the animation switch image every 0.075 second, then the cycle would be:

虽然,可能会发生动画周期与帧速率不同步的情况,使动画周期看起来不规则。例如,假设我们每 0.05 秒更新一次帧,每 0.075 秒更新一次动画切换图像,那么循环将是:

  1. Frame 1; 0.00 seconds; image 1
  2. Frame 2; 0.05 seconds; image 1
  3. Frame 3; 0.10 seconds; image 2
  4. Frame 4; 0.15 seconds; image 1
  5. Frame 5; 0.20 seconds; image 1
  6. Frame 6; 0.25 seconds; image 2
  1. 第 1 帧;0.00 秒;图 1
  2. 第 2 帧;0.05 秒;图 1
  3. 第 3 帧;0.10 秒;图 2
  4. 第 4 帧;0.15 秒;图 1
  5. 第 5 帧;0.20 秒;图 1
  6. 第 6 帧;0.25 秒;图 2

And so on...

等等...

Frame-dependentcan look smoother if your computer can handle the framerate consistently. If lag happens it'll pause in its current state and restart when the lag stops, which makes the lag more noticeable. This alternative is slightly easier to implement since you just need to increment current_framewith 1 on each call, instead of dealing with the delta time (dt) and passing it to every object.

如果您的计算机可以始终如一地处理帧率,则依赖于帧的画面看起来会更流畅。如果发生滞后,它将在当前状态暂停并在滞后停止时重新启动,这使得滞后更加明显。这种替代方案更容易实现,因为您只需要current_frame在每次调用时增加1,而不是处理增量时间 ( dt) 并将其传递给每个对象。

Sprites

精灵

enter image description hereenter image description hereenter image description hereenter image description hereenter image description hereenter image description here

在此处输入图片说明在此处输入图片说明在此处输入图片说明在此处输入图片说明在此处输入图片说明在此处输入图片说明

Result

结果

enter image description here

在此处输入图片说明