Search code examples
pythonanimationpygamesprite-sheet

pygame spritesheet sprite animation


I am trying to make a walking animation for my sprite (4 directional) by using 4 different sprite sheets (Movement up, down, left and right). As well as this, I would like to replace the coloured square with the image/s but am unsure as how I am supposed to do this. Below is the main.py game file:

import pygame as pg
import sys
from settings import *
from os import path
from sprites import *
from tilemap import *

class Spritesheet:
    def __init__(self, filename, cols, rows):
        self.sheet = pg.image.load(filename)

        self.cols = cols  #no. of columns in spritesheet
        self.rows = rows  #no. of rows
        self.totalCellCount = cols*rows
        self.rect=self.sheet.get_rect()
        w = self.cellWidth = self.rect.width / cols
        h = self.cellHeight = self.rect.height / rows
        hw, hh = self.cellCenter = (w / 2, h / 2)

        self.cells = list([(index % cols * w, index // cols * h, w, h) for index in range(self.totalCellCount)])
        self.handle = list([
            (0, 0), (-hw, 0), (-w, 0),
            (0, -hh), (-hw, -hh), (-w, -hh),
            (0, -h), (-hw, -h), (-w, -h),])


    def draw(self, surface, cellIndex, x, y, handle = 0):
        surface.blit(self.sheet, (x + self.handle[handle][0], y + self.handle[handle][1]), self.cells[cellIndex])

CENTER_HANDLE = 4
index = 0

class Game:
    def __init__(self):
        pg.init()
        self.screen=pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()
        pg.key.set_repeat(500, 100)
        self.load_data()

    def load_data(self):
        game_folder = path.dirname(__file__)
        img_folder = path.join(game_folder, 'img')
        self.map= Map(path.join(game_folder, 'map.txt'))

    def new(self):
        # initialize all variables and do all the setup for a new game
        self.all_sprites = pg.sprite.Group()
        self.walls = pg.sprite.Group()
        self.player1group = pg.sprite.Group()
        self.player2group = pg.sprite.Group()
        for row, tiles in enumerate(self.map.data):
            for col, tile in enumerate(tiles):
                if tile == '1':
                    Wall(self, col, row)
                if tile =='P':
                    self.player = Civilian(self, col, row)
                if tile =='T':
                    self.player2 = Thief(self, col, row)




    def run(self):
        # game loop - set self.playing = False to end the game
        self.playing = True
        while self.playing:
            self.dt = self.clock.tick(FPS) / 1000
            self.events()
            self.update()
            self.draw()

    def quit(self):
        pg.quit()   #Calls the quit function, game window closes
        sys.exit()

    def update(self):
        # update portion of the game loop
        self.all_sprites.update()   #All sprite attributes, position etc are updated

    def draw_grid(self):
        for x in range(0, WIDTH, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (0, y), (WIDTH, y))

    def draw(self):
        self.screen.fill(BGCOLOR)
        self.draw_grid()
        self.all_sprites.draw(self.screen)
        pg.display.flip()


    def events(self):
        # catch all events here
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.quit()
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_ESCAPE:
                    self.quit()


    def show_start_screen(self):
        pass

    def show_go_screen(self):
        pass





# create the game object
g = Game()
g.show_start_screen()
while True:
    g.new()
    g.run()
    g.show_go_screen()

Here is the sprites.py file, cont. sprite classes

import pygame as pg
from settings import *
vec = pg.math.Vector2

class Civilian(pg.sprite.Sprite):
    def __init__(self, game, x, y):
        self.groups = game.all_sprites, game.player1group
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.image = pg.Surface((TILESIZE, TILESIZE))
        self.image.fill(YELLOW)
        self.rect = self.image.get_rect()
        self.vel = vec(0, 0)
        self.pos = vec(x, y) * TILESIZE

    def get_keys(self):
        self.vel= vec(0, 0)
        keys = pg.key.get_pressed()
        if keys[pg.K_a]:    # Const. subtracts player speed from velocity (E.g. Moves sprite to the left)
            self.vel.x= -PLAYER_SPEED
        if keys[pg.K_d]:    # Const. adds player speed value to velocity (E.g. Moves sprite to the right)
            self.vel.x= PLAYER_SPEED
        if keys[pg.K_w]:    # Const. subtracts player speed value from y velocity (Moves player upwards; opposite)
            self.vel.y= -PLAYER_SPEED
        if keys[pg.K_s]: # Const. adds player speed value to y velocity (Moves player downwards; opposite)
            self.vel.y= PLAYER_SPEED
        if self.vel.x != 0 and self.vel.y != 0:   # Offsetting increased vecocity when moving diagonally (Has both x and y velocity)
            self.vel *= 0.7071

    def collide_with_player2(self, dir, ifColliding):
        if dir == 'x':
            collides = pg.sprite.spritecollide(self, self.game.player2group, False)
            if collides:
                if self.vel.x > 0:
                    self.pos.x = collides[0].rect.left - self.rect.width
                if self.vel.x < 0:
                    self.pos.x = collides[0].rect.right
                self.vel.x = 0
                self.rect.x = self.pos.x
                print("collide x")
                self.ifColliding = True

        if dir == 'y':
            collides = pg.sprite.spritecollide(self, self.game.player2group, False)
            if collides:
                if self.vel.y > 0:
                    self.pos.y = collides[0].rect.top - self.rect.height
                if self.vel.y < 0:
                    self.pos.y = collides[0].rect.bottom
                self.vel.y = 0
                self.rect.y = self.pos.y
                print("collide y")
                self.ifColliding = True



    def collide_with_walls(self, dir):
        if dir == 'x':
            collides = pg.sprite.spritecollide(self, self.game.walls, False)
            if collides:
                if self.vel.x > 0:
                    self.pos.x = collides[0].rect.left - self.rect.width
                if self.vel.x < 0:
                    self.pos.x = collides[0].rect.right
                self.vel.x = 0
                self.rect.x = self.pos.x
        if dir == 'y':
            collides = pg.sprite.spritecollide(self, self.game.walls, False)
            if collides:
                if self.vel.y > 0:
                    self.pos.y = collides[0].rect.top - self.rect.height
                if self.vel.y < 0:
                    self.pos.y = collides[0].rect.bottom
                self.vel.y = 0
                self.rect.y = self.pos.y

    def update(self):
        self.ifColliding = False
        self.get_keys()
        self.pos += self.vel * self.game.dt
        self.rect.x = self.pos.x
        self.collide_with_walls('x'), self.collide_with_player2('x', self.ifColliding)
        self.rect.y = self.pos.y
        self.collide_with_walls('y'), self.collide_with_player2('y', self.ifColliding)
        if self.ifColliding == True:
            Thief.health -= COL_DAMAGE
            print(Thief.health)

class Thief(pg.sprite.Sprite):
    health = 100
    def __init__(self, game, x, y):
        self.groups = game.all_sprites, game.player2group
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.image = pg.Surface((TILESIZE, TILESIZE))
        self.image.fill(RED)
        self.rect = self.image.get_rect()
        self.vel = vec(0, 0)
        self.pos = vec(x, y) * TILESIZE
        s = Spritesheet("spritesheet_thief.png", 9, 4)




    def get_keys(self):
        self.vel = vec(0, 0)
        keys = pg.key.get_pressed()
        if keys[pg.K_LEFT]:    # Const. subtracts player speed from velocity (E.g. Moves sprite to the left)
            self.vel.x= -PLAYER_SPEED
        elif keys[pg.K_RIGHT]:    # Const. adds player speed value to velocity (E.g. Moves sprite to the right)
            self.vel.x= PLAYER_SPEED
        elif keys[pg.K_UP]:    # Const. subtracts player speed value from y velocity (Moves player upwards; opposite)
            self.vel.y= -PLAYER_SPEED
        elif keys[pg.K_DOWN]: # Const. adds player speed value to y velocity (Moves player downwards; opposite)
            self.vel.y= PLAYER_SPEED
        elif self.vel.x != 0 and self.vel.y != 0:   # Offsetting increased vecocity when moving diagonally (Has both x and y velocity)
            self.vel *= 0.7071

    def collide_with_player1(self, dir, ifColliding):
        if dir == 'x':
            collides = pg.sprite.spritecollide(self, self.game.player1group, False)
            if collides:
                if self.vel.x > 0:
                    self.pos.x = collides[0].rect.left - self.rect.width
                if self.vel.x < 0:
                    self.pos.x = collides[0].rect.right
                self.vel.x = 0
                self.rect.x = self.pos.x
                print("collide x")
                self.ifColliding = True

        if dir == 'y':
            collides = pg.sprite.spritecollide(self, self.game.player1group, False)
            if collides:
                if self.vel.y > 0:
                    self.pos.y = collides[0].rect.top - self.rect.height
                if self.vel.y < 0:
                    self.pos.y = collides[0].rect.bottom
                self.vel.y = 0
                self.rect.y = self.pos.y
                print("collide y")
                self.ifColliding = True





    def collide_with_walls(self, dir):
        if dir == 'x':
            collides = pg.sprite.spritecollide(self, self.game.walls, False)
            if collides:
                if self.vel.x > 0:
                    self.pos.x = collides[0].rect.left - self.rect.width
                if self.vel.x < 0:
                    self.pos.x = collides[0].rect.right
                self.vel.x = 0
                self.rect.x = self.pos.x
        if dir == 'y':
            collides = pg.sprite.spritecollide(self, self.game.walls, False)
            if collides:
                if self.vel.y > 0:
                    self.pos.y = collides[0].rect.top - self.rect.height
                if self.vel.y < 0:
                    self.pos.y = collides[0].rect.bottom
                self.vel.y = 0
                self.rect.y = self.pos.y


    def update(self):
        s.draw(self.game.screen, index % s.totalCellCount, HW, HH, CENTER_HANDLE)
        index += 1
        self.ifColliding = False
        self.get_keys()
        self.pos += self.vel * self.game.dt
        self.rect.x = self.pos.x
        self.collide_with_walls('x'), self.collide_with_player1('x', self.ifColliding)
        self.rect.y = self.pos.y
        self.collide_with_walls('y'), self.collide_with_player1('y', self.ifColliding)
        if Thief.health <= 0:
            self.kill()



class Wall(pg.sprite.Sprite):
    def __init__(self, game, x, y):
        self.groups = game.all_sprites, game.walls
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.image = pg.Surface((TILESIZE, TILESIZE))
        self.image.fill(GREEN)
        self.rect = self.image.get_rect()
        self.x = x
        self.y = y
        self.rect.x = x * TILESIZE
        self.rect.y = y * TILESIZE

Hoping to run through a row of cells within the sprite sheet depending on the direction the sprite is moving in and set that as the sprite image (Giving the illusion that the player sprite is animated)

Any help would be appreciated, sorry if the Question is confusing, first time using the site and not too good at coding at the current stage.


Solution

  • Load four images as

    self.image_up = pygame.image.load(...)
    self.image_down = pygame.image.load(...)
    self.image_left = pygame.image.load(...)
    self.image_right = pygame.image.load(...)
    

    and later when you need it replace

    self.image = self.image_up 
    

    or

    self.image = self.image_down
    

    etc.


    If you have all positions in one file then you can use pygame.Surface.subsurface to cut off part of image and create new one

    spritesheet = pygame.image.load(...)
    
    self.image_up = spritesheet.subsurface( Rect(0,0,10,10) )
    self.image_down = spritesheet.subsurface( Rect(...) )
    self.image_left = spritesheet.subsurface( Rect(...) )
    self.image_right = spritesheet.subsurface( Rect(...) )
    

    My simple example (with all positions in one file) on GitHub: pygame-spritesheet

    Use left/right arrows to move object and it will use different image.