Search code examples
pythonanimationpygamesprite-sheet

pygame animation sprite sheet


I would like to create a top down RPG in pygame using sprite sheets.

I want to be able to press space, for example, to attack which would trigger the attacking animation and then go back to normal

import pygame
from pygame.locals import *

pygame.init()

image = pygame.image.load("sprite_sheet.png")

clock = pygame.time.Clock()

screen =  pygame.display.set_mode((400, 250))

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()

        self.current_animation = 0
        self.max_animation = 5

        self.animation_cooldown = 150
        self.last_animation = pygame.time.get_ticks()

        self.status = {"prev": "standing",
                       "now": "standing"}

    def animate_attack(self):
        time_now = pygame.time.get_ticks()

        if time_now - self.last_animation >= self.animation_cooldown:
            self.last_animation = pygame.time.get_ticks()
            if self.current_animation == self.max_animation:
                self.current_animation = 0

                joshua.status["now"] = joshua.status["prev"]
            else:
                self.current_animation += 1


joshua = Player()

while True:
    screen.fill(0)
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                joshua.status["prev"] = joshua.status["now"]
                joshua.status["now"] = "attacking"

    if joshua.status["now"] == "attacking":
        joshua.animate_attack()

    screen.blit(image, (0, 0), (joshua.current_animation * 64, 0, 64, 64))

    pygame.display.flip()

    clock.tick(60)

The above piece of code is what i have. If i press space once it will go through the animation and stop but if i double press space it will loop it because of how it is programmed.

Would like some help in animation, thank you


Solution

  • The problem is caused by the following call when space is pressed the second time:

    joshua.status["prev"] = joshua.status["now"]
    

    This sets both the "prev" and "now" states to be "attacking". As a result, when the state is reset in animate_attack() method, it will remain "attacking":

    joshua.status["now"] = joshua.status["prev"]
    

    As a quick fix, make sure to only change the state if it isn't already set:

    if event.key == pygame.K_SPACE:
        if not joshua.status["now"] == "attacking":
            joshua.status["prev"] = joshua.status["now"]
            joshua.status["now"] = "attacking"
    

    As a better fix, you should encapsulate the state, so that only the Player class ever deals with its own state, for example:

    class Player():
        def __init__(self):
            self.current_animation = 0
            self.max_animation = 5
            self.animation_cooldown = 150
            self.last_animation = pygame.time.get_ticks()
            self.status = "standing" # Simplified state
    
        def attack(self):
            self.status = "attacking"
    
        def animate_attack(self):
            if self.status == "attacking":
                time_now = pygame.time.get_ticks()
                if time_now - self.last_animation >= self.animation_cooldown:
                    self.last_animation = pygame.time.get_ticks()
                    if self.current_animation == self.max_animation:
                        self.current_animation = 0
                        self.status = "standing" # Reset state
                    else:
                        self.current_animation += 1
    

    This way, there is no need to know anything about the state outside of the class:

    while True:
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    joshua.attack()
    
        joshua.animate_attack()
    
        screen.fill(0)
        screen.blit(image, (0, 0), (joshua.current_animation * 64, 0, 64, 64))
        pygame.display.flip()
        clock.tick(60)