首页 > 解决方案 > 如何使两帧动画工作pygame

问题描述

我正在 pygame 上制作游戏,我想要一些关于如何运行简单的两帧动画并在按下键时退出的建议。这是代码,附上完整的文件。

                screen.blit(win_screen1, [0, 0])
                pygame.display.update()
                clock.tick(13)
                screen.blit(win_screen2, [0, 0])
                clock.tick(13)
                pygame.display.update()
                for event in pygame.event.get():
                    if event.type == pygame.KEYDOWN:
                        playing = True
                        win = False
                        reset()

我原以为这会使动画以 13 FPS 的速度运行,并在按下某个键时停止,但它的播放确实不一致且缓慢,并且按下一个键什么也没做。我在另一篇文章中看到有人说他们使用了pygame.time.Clock(),但我不确定它是如何工作的,而且许多方法只是限制帧率,而不是设置它。

标签: pythonpygame

解决方案


将动画项目包装成一个对象,这样它就可以保持自己的内部时序,并绘制正确的位图。我会为此使用 PyGame 精灵,但你也可以自己制作。

这个想法是使用内部 PyGame 时钟来记录当前帧首次显示的时间。然后在某种每帧更新函数中,确定是否经过了足够的毫秒以保证移动到下一帧。您需要 13 FPS,所以这是 1000/13 -> 每 77 毫秒。

这给了我们一个粗略的功能:

drawAnimation()
    What is the time now?
    Has 77 milliseconds elapsed since the last update?
       Advance to the next image in the list
    Draw the image

在循环帧和维护时间戳方面有一些家务,但它并不比上面的伪代码复杂多少。诀窍是将所有内容保存在可以传递的单个数据对象中。这使其他帧定时(如窗口 FPS)很好地分开。正如我所说,PyGame Sprite 非常适合保存所有这些数据,但即使是简单的 python 列表也可以在紧要关头工作。

示例图像

动画来自:https ://rvros.itch.io/animated-pixel-hero (在许可许可下使用)。缩放 200%

这是一个基于 Sprite 的实现。如果您有疑问,请添加评论。

class MultiFrameSprite(pygame.sprite.Sprite):
    def __init__( self, fps, list_of_frames ):
        pygame.sprite.Sprite.__init__( self )

        # Load all the specified animation frames
        self.frames = []
        for filename in list_of_frames:
            self.frames.append( pygame.image.load( filename ).convert_alpha() )

        # Start the animation at the first frame
        self.image = self.frames[0]
        self.rect  = self.image.get_rect()

        # Frame handling - index frame, and time used
        self.millisec_rate = 1000 // fps   # inter-frame delay in milliseconds
        self.current_frame = 0
        self.last_frame_at = 0

        # we need to be somewhere on-screen
        self.rect.center = ( WINDOW_WIDTH//2, WINDOW_HEIGHT//2 )

    def update(self):
        # Compare the time of the last frame to time-now
        # and determine if it's time to show the next frame
        time_now = pygame.time.get_ticks()
        if ( time_now > self.last_frame_at + self.millisec_rate ):
            # new frame needed!
            self.last_frame_at = time_now
            # Advance to the next frame
            self.current_frame += 1
            if ( self.current_frame == len( self.frames ) ):
                self.current_frame = 0                           # wrap frame loop index
            # Set the new image
            self.image = self.frames[ self.current_frame ]
            self.rect  = self.image.get_rect()
            # TODO: handle any x/y changes needed by differing animation sizes

请注意,我们从不只制作 2、3 或 10 帧动画。它只是N个图像的列表。当我们到达列表的末尾时(不管它有多长),它就会回到开头。这样,相同的代码可以用于一个222帧动画。

参考代码:

import pygame
import random

# Window size
WINDOW_WIDTH    = 300
WINDOW_HEIGHT   = 300
WINDOW_SURFACE  = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE

DARK_BLUE = (   3,   5,  54)

class MultiFrameSprite(pygame.sprite.Sprite):
    def __init__( self, fps, list_of_frames ):
        pygame.sprite.Sprite.__init__( self )

        # Load all the specified animation rames
        self.frames = []
        for filename in list_of_frames:
            self.frames.append( pygame.image.load( filename ).convert_alpha() )

        # Start the animation at the first frame
        self.image = self.frames[0]
        self.rect  = self.image.get_rect()

        # Frame handling
        self.millisec_rate = 1000 // fps   # inter-frame delay in milliseconds
        self.current_frame = 0
        self.last_frame_at = 0

        # we need to be somewhere on-screen
        self.rect.center = ( WINDOW_WIDTH//2, WINDOW_HEIGHT//2 )

    def update(self):
        # Compare the time of the last frame to time-now
        # and determine if it's time to show the next frame
        time_now = pygame.time.get_ticks()
        if ( time_now > self.last_frame_at + self.millisec_rate ):
            # new frame needed!
            self.last_frame_at = time_now
            # preserve the centroid in case the frames are differing sizes
            x,y = self.rect.center
            # Advance to the next frame
            self.current_frame += 1
            if ( self.current_frame == len( self.frames ) ):
                self.current_frame = 0                           # wrap frame loop index
            # Set the new image
            self.image = self.frames[ self.current_frame ]
            self.rect  = self.image.get_rect()
            self.rect.center = (x,y)                             # restore the centroid co-ordinate


### initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption( "Multi-Frame Sprite" )


### Create Sprites
all_sprites = pygame.sprite.Group()
adventurer  = MultiFrameSprite( 5, [ 'adventurer-idle-00.png', 
                                     'adventurer-idle-01.png', 
                                     'adventurer-idle-02.png', 
                                     'adventurer-idle-03.png' ] )
all_sprites.add( adventurer )


### Main Loop
clock = pygame.time.Clock()
done = False
while not done:

    # Handle user-input
    for event in pygame.event.get():
        if ( event.type == pygame.QUIT ):
            done = True

    # Update the window, but not more than 60fps
    all_sprites.update()
    window.fill( DARK_BLUE )
    all_sprites.draw( window )       # Paint ALL the sprites
    pygame.display.flip()

    # Clamp FPS
    clock.tick_busy_loop(60)


pygame.quit()

注意:我在上面的代码中使用了 5 FPS,因为它非常适合动画。


推荐阅读