Анимация не воспроизводится до конца

Я делаю игру на pygame,и на данный момент я завершаю делать анимации для персонажа. Все анимации работают правильно кроме анимации выстрела из пистолета. Она не проигрывается до конца, а отображается только первая картинка. Я пытался воспроизводить ее также как и анимацию прыжка (вместе с self.jump я добавил self.in_air. С помощью self.in_air анимация прыжка воспроизводится правильно). Такую же переменную я добавил и для выстрела (self.shooting). И вроде бы все работает, но после завершения анимации мне выдает ошибку:

self.image = self.animation_list[self.action][self.frame_index]
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
IndexError: list index out of range

Анимация состоит из 4 картинок, но self.frame_index равняется несуществующему индексу (4). Как это исправить? Вот код:

import pygame
import os

import time

pygame.init()

SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 720

screen = pygame.display.set_mode((SCREEN_WIDTH,SCREEN_HEIGHT))
pygame.display.set_caption('Shooter')

# set framerate
clock = pygame.time.Clock()
FPS = 60

# define game variables
GRAVITY = 0.75

# define colours
BG = (245,145,165)
BLACK = (0,0,0)

moving_left = False
moving_right = False
shoot = False
duck = False
grenade = False

#load images
#bullet
grenade_img = pygame.image.load('Sprites/grenade.png').convert_alpha()


def draw_bg():
    screen.fill(BG)
    pygame.draw.line(screen, BLACK,(0,500),(SCREEN_WIDTH,500))

class Hero(pygame.sprite.Sprite):
    def __init__(self,char_type, x,y,scale, speed):
        pygame.sprite.Sprite.__init__(self)
        self.alive = True

        self.char_type = char_type
        self.speed = speed
        self.shoot_cooldown = 0
        self.health = 100
        self.max_health = self.health
        self.shooting = True
        self.direction = 1
        self.vel_y =0
        self.jump = False
        self.in_air = True
        self.flip = False

        self.shooting = False
        self.animation_list = []
        self.frame_index = 0
        self.action = 0
        self.update_time = pygame.time.get_ticks()

        #load all images for the players
        animation_types = ['Idle','Run','Jump','Shoot','Death','Duck','Hurt']
        for animation in animation_types:
            #reset temporary list of images
            temp_list = []
            #count number of files in the folder
            num_of_frames = len(os.listdir(f'Sprites/{self.char_type}/{animation}'))
            for i in range(num_of_frames):
                img = pygame.image.load(f'Sprites/{self.char_type}/{animation}/{i}.png').convert_alpha()
                img = pygame.transform.scale(img, (img.get_width() * scale, img.get_height() * scale))
                temp_list.append(img)
            self.animation_list.append(temp_list)
        self.image = self.animation_list[self.action][self.frame_index]
        self.rect = self.image.get_rect()
        self.rect.center = (x, y)

    def update(self):

        self.update_animation()
        self.check_alive()

        #update cooldown
        if self.shoot_cooldown > 0 and self.shooting:

            self.shoot_cooldown -=1



    def move(self, moving_left, moving_right):
        #reset movement variables
        dx = 0
        dy = 0

        #assign movement variables if moving left or right
        if moving_left:
            dx = -self.speed
            self.flip = True
            self.direction = -1
        if moving_right:
            dx = self.speed
            self.flip = False
            self.direction = 1

        #jump
        if self.jump == True and self.in_air == False:
            self.vel_y = -15
            self.jump = False
            self.in_air = True

        #apply gravity
        self.vel_y += GRAVITY
        if self.vel_y > 10:
            self.vel_y
        dy += self.vel_y

        #check collusion with floor
        if self.rect.bottom + dy > 500:
            dy = 500 - self.rect.bottom
            self.in_air = False



        #update rectangle position
        self.rect.x += dx
        self.rect.y += dy

    def shoot(self):
        self.shooting = True
        if self.shooting:
            self.update_action(3)
        if self.shoot_cooldown == 0:

            self.shoot_cooldown = 25
            bullet = Bullet('Player-shoot', self.rect.centerx + (0.65 * self.rect.size[0] * self.direction),
                                self.rect.centery - 5, self.direction)
            bullet_group.add(bullet)






    def update_animation(self):
        ANIMATION_COOLDOWN = 100
        #update image depending on current frame
        self.image = self.animation_list[self.action][self.frame_index]
        #check it enough time has passed since the last update
        if pygame.time.get_ticks() - self.update_time > ANIMATION_COOLDOWN:
            self.update_time = pygame.time.get_ticks()
            self.frame_index+=1
        #if the animation has run out the reset back to the start
        if self.frame_index >= len(self.animation_list[self.action]):
            if self.action == 4:
                self.frame_index = len(self.animation_list[self.action]) - 1
            elif self.action == 5:
                self.frame_index = len(self.animation_list[self.action]) - 1
            elif self.action == 3:

                self.shooting = False

            else:
                self.frame_index =0

    def update_action(self, new_action):
        #check is the new action is different to the previous one
        if new_action != self.action:
            self.action = new_action
            #update the animation settings
            self.frame_index = 0
            self.update_time = pygame.time.get_ticks()

    def check_alive(self):
        if self.health <= 0:
            self.health = 0
            self.speed = 0
            self.alive = False
            self.update_action(4)





    def draw(self):
        screen.blit(pygame.transform.flip(self.image,self.flip,False), self.rect)


class Bullet(pygame.sprite.Sprite):
    def __init__(self,bullet_type, x,y,direction):
        pygame.sprite.Sprite.__init__(self)
        self.bullet_type = bullet_type
        self.speed = 10
        self.flip = False
        self.direction = direction

        self.animation_list = []
        self.frame_index = 0
        self.action = 0
        self.update_time = pygame.time.get_ticks()



            # reset temporary list of images
        temp_list = []
            # count number of files in the folder
        num_of_frames = len(os.listdir(f'Sprites/{self.bullet_type}'))
        for i in range(num_of_frames):
            img = pygame.image.load(f'Sprites/{self.bullet_type}/{i}.png')

            img = pygame.transform.scale(img, (int(img.get_width() * 2.2), int(img.get_height() * 2.2)))

            temp_list.append(img)
        self.animation_list.append(temp_list)
        self.image = self.animation_list[self.action][self.frame_index]
        self.rect = self.image.get_rect()

        self.rect.center = (x, y)


    def update(self):
        self.update_animation()
        self.check_flip()
        self.rect.x += (self.direction * self.speed)
        #check if bullet has gone off screen
        if self.rect.right <0 or self.rect.left>SCREEN_WIDTH:
            self.kill()

        #chrck collusions with character
        if pygame.sprite.spritecollide(hero, bullet_group, False):
            if hero.alive:
                hero.health -= 5
                self.kill()
        if pygame.sprite.spritecollide(cop, bullet_group, False):
            if cop.alive:
                cop.health -= 25
                self.kill()


    def check_flip(self):
        if self.direction == -1:
            self.flip = True
        else:
            self.flip = False
        if self.flip:
            self.image = pygame.transform.flip(self.image, self.flip, False)


    def update_animation(self):
        # update animation
        ANIMATION_COOLDOWN = 100
        # update image depending on current frame
        self.image = self.animation_list[self.action][self.frame_index]
        # check if enough time has passed since the last update
        if pygame.time.get_ticks() - self.update_time > ANIMATION_COOLDOWN:
            self.update_time = pygame.time.get_ticks()
            self.frame_index += 1
        # if the animation has run out the reset back to the start
        if self.frame_index >= len(self.animation_list[self.action]):
            self.frame_index = 0
    def update_action(self, new_action):
        #check is the new action is different to the previous one
        if new_action != self.action:
            self.action = new_action
            #update the animation settings
            self.frame_index = 0
            self.update_time = pygame.time.get_ticks()


class Grenade(pygame.sprite.Sprite):
    def __init__(self, x,y,direction):
        pygame.sprite.Sprite.__init__(self)
        self.timer = 100
        self.vel_y = -11
        self.speed = 7
        self.image = grenade_img
        self.image = pygame.transform.scale(self.image, (self.image.get_width() * 2.2, self.image.get_height() * 2.2))

        self.rect = self.image.get_rect()
        self.rect.center = (x, y)

        self.direction = direction



#create groups for bullets
bullet_group = pygame.sprite.Group()
grenade_group = pygame.sprite.Group()

hero = Hero('Player',200,200,2, 5)
cop = Hero('Cop',400,300,2, 5)


run = True
while run:

    clock.tick(FPS)
    draw_bg()
    hero.update()
    hero.draw()
    cop.update()
    cop.draw()

    # update and draw groups

    bullet_group.update()
    grenade_group.update()
    bullet_group.draw(screen)
    grenade_group.draw(screen)



    #update player actions
    if hero.alive:

        if duck:
            hero.speed = 0
            hero.update_action(5)
            hero.jump = False
        elif shoot:
            hero.shoot()
            hero.speed = 0
            hero.shooting = False

        # throw grenade
        elif grenade:
            grenade = Grenade(hero.rect.centerx + (0.5 * hero.rect.size[0]) * hero.direction, hero.rect.centery,hero.direction)
            grenade_group.add(grenade)
        elif hero.in_air:
            hero.update_action(2)  # 2: jump
        elif moving_right or moving_left:

            hero.update_action(1)#1: run

            hero.speed = 5
        else:
            hero.update_action(0)

    hero.move(moving_left,moving_right)


    for event in pygame.event.get():
        #quit game
        if event.type == pygame.QUIT or  (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
            run = False
        #keyboard
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_a:
                moving_left = True
                flip_bullet = True

            if event.key == pygame.K_d:
                moving_right = True
                flip_bullet = False

            if event.key == pygame.K_SPACE:
                shoot = True
            if event.key == pygame.K_q:
                grenade = True

            if event.key == pygame.K_s:
               duck = True



            if event.key == pygame.K_w and hero.alive:
                hero.jump = True
                #hero.shooting = True

        #keybaord button realised
        if event.type == pygame.KEYUP:
            if event.key == pygame.K_a:
                moving_left = False
            if event.key == pygame.K_d:
                moving_right = False
            if event.key == pygame.K_SPACE:
                pass
            if event.key == pygame.K_q:
                grenade = False
            if event.key == pygame.K_s:
                duck = False
    pygame.display.update()

pygame.quit()

Ответы (1 шт):

Автор решения: CrazyElf
#if the animation has run out the reset back to the start
if self.frame_index >= len(self.animation_list[self.action]):
    if self.action == 4:
        self.frame_index = len(self.animation_list[self.action]) - 1
    elif self.action == 5:
        self.frame_index = len(self.animation_list[self.action]) - 1
    elif self.action == 3: # <- проблема в этой ветке
        self.shooting = False
    else:
        self.frame_index = 0

Если внимательно посмотреть на этот код, то видно, что в ветке elif self.action == 3 (то есть как-раз 'Shoot') вы ничего не делаете с self.frame_index, он так и остаётся выходящим за пределы анимации.

→ Ссылка