web-dev-qa-db-ja.com

いくつかの画像からのアニメーションスプライト

Python Pygameを使用していくつかの画像から簡単なスプライトアニメーションを作成するための優れたチュートリアルを探していました。探しているものがまだ見つかりません。

私の質問は簡単です:いくつかの画像からアニメーション化されたスプライトを作成する方法(例:20x20pxの寸法の爆発のいくつかの画像を1つとしてアニメーション化するように作成する)

良いアイデアはありますか?

10
lbartolic

Spriteを変更して、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という2つのイメージがあることを前提としています。

16
Michael0x2a

アニメーションには、frame-dependenttime-dependentの2種類があります。 。どちらも同じように機能します。


メインループの前

  1. すべての画像をリストにロードします。
  2. 3つの変数を作成します:
    1. index、画像リストの現在のインデックスを追跡します。
    2. current_timeまたはcurrent_frameは、インデックスが最後に切り替えられてから現在の時間または現在のフレームを追跡します。
    3. animation_timeまたはanimation_framesは、画像を切り替えるまでに経過する秒数またはフレーム数を定義します。

メインループ中

  1. 最後にインクリメントしてから経過した秒数だけcurrent_timeをインクリメントするか、current_frameを1ずつインクリメントします。
  2. current_time >= animation_timeまたはcurrent_frame >= animation_frameかどうかを確認します。 trueの場合は3-5に進みます。
  3. current_time = 0またはcurrent_frame = 0をリセットします。
  4. インデックスが画像の量以上になる場合を除いて、インデックスを増やします。その場合は、index = 0をリセットしてください。
  5. それに応じて、スプライトの画像を変更します。

完全に機能する例

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. フレーム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

等々...

フレーム依存は、コンピュータがフレームレートを一貫して処理できる場合、より滑らかに見えます。ラグが発生すると、現在の状態で一時停止し、ラグが停止すると再開します。これにより、ラグがより目立ちます。デルタ時間(dt)を処理してすべてのオブジェクトに渡すのではなく、呼び出しごとにcurrent_frameを1だけインクリメントする必要があるため、この代替案は実装が少し簡単です。

スプライト

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

結果

enter image description here

16

すべてのSpriteアニメーションを1つの大きな「キャンバス」上に配置する必要があるため、3つの20x20爆発Spriteフレームの場合、60x20の画像になります。これで、画像の領域をロードすることで正しいフレームを取得できます。

Spriteクラス内では、おそらく更新メソッドでは、次のようなものが必要です(単純化のためにハードコーディングされているため、適切なアニメーションフレームの選択を担当する別のクラスを使用することをお勧めします)。 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])
2
Ruslan Osipov