Python Forum
[PyGame] Basic animation (part 5)
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyGame] Basic animation (part 5)
#1
Back to part 4
https://python-forum.io/Thread-PyGame-Ad...cts-part-4


We are going to skip over the space shooter game on this one as our ship doesnt offer anything but 1 image making animation harder. I probably should of thought about that more in advanced...but didnt. Sorry.

Download this image
   

import pygame as pg
pg.init()
screen = pg.display.set_mode((800,600))
shipsheet = pg.image.load('shipsheet.png').convert()
 
 
def strip_from_sheet(sheet, start, size, columns, rows):
    frames =
    for j in range(rows):
        for i in range(columns):
            location = (start[0]+size[0]*i, start[1]+size[1]*j)
            frames.append(sheet.subsurface(pg.Rect(location, size)))
    return frames
 
class Ship:
    def __init__(self, sheet):
        self.frames = strip_from_sheet(sheet, (0,0), (122,164), 4, 4)
        self.frame_index = 0
        self.update()
         
    def update(self):
        self.image = self.frames[self.frame_index]
         
    def get_event(self, event):
        if event.type == pg.KEYDOWN:
            self.frame_index += 1
         
    def draw(self, screen):
        screen.blit(self.image, (0,0))
         
         
done = False
ship = Ship(shipsheet)
             
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True
        ship.get_event(event)
    ship.update()
    ship.draw(screen)
    pg.display.update()
This is animation in it's simplest form. The animation is based on key press as oppose to time. We have to tweak many things...such as make the white clear with alpha as well as assign directions for the ship but this will get us started. If you run this code it will animate through the sprite sheet until it comes with a list out of range. This is due to no clause to check for out of range.

Here we use the strip_from_sheet function we had in previous tutorials to cut out the ship spritesheet. Each individual sprites are of the same size and organized in a 4x4 pattern. This means instead of cutting them out in coordinates, we can loop through and cut them out. To do this we need to know the full spritesheet size as well as the size of each sprite. You can do this with pygame as well in a python interpreter.

Python 3.5.1+ (default, Mar 30 2016, 22:46:26) 
[GCC 5.3.1 20160330] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pygame as pg
>>> sheet = pg.image.load('shipsheet.png')
>>> sheet.get_rect().size
(490, 658)
>>> sheet.get_rect().width
490
>>> sheet.get_rect().width // 4
122
>>> sheet.get_rect().height // 4
164
So we load the spritesheet and we can see its full size. Since we know the sprites are aligned 4 by 4 in the spritesheet, if we divide the full spritesheet size by 4 we will get the size of each image. Which boils down to (122,164). So now we can use this to cut up the spritesheet into these segmants

self.frames = strip_from_sheet(sheet, (0,0), (122,164), 4, 4)
So in the Ship class we get each sprite into a frames list using the frames_from_sheet function and plugging the values in. We are sending shipsheet to the class in its dunder init method. We load the spritesheet NOT in the class because if there are more than one ship you do not want to load the spritesheet every time you create a Ship object. The second argument is the starting location, which is usually (0,0) of the spritesheet..AKA the topleft. Next is the "size". This means the size of each individual sprites. WE only know this because of using the interpreter before at (122,164). The last two arguments ask for how many images in the columns and rows, which is 4 and 4. So now the self.frames in Ship class contain each sprite in a list. You can organize this list further based on direction. For example self.frames[0] through self.frames[3] are down animations.

Ship.image is always the image that we draw. We just change which image that is based on events. In our case this event is a keypress. So every time we press a key the index gets 1 added, which in the Ship.update gets updated to the image that we draw.

So this is the bare bones of animation. Not much, but we are going to make this ship animate by direction later.


Changing the background

So the next step we are going to do is get rid of that annoying white background. Now because our current background is black and there is a black shadow touching the white. It is a good idea to change the background color. Might as well do something blue-ish for the time being. You can simply do this just by filling the background with a blue color.

Because our spritesheet has no white in the images. We can chop ALL white out for transparency. We are going to set the colorkey to white (255,255,255). We are also going to make the background blue o nthe screen so you can see the ship shadow.

So here is the updated code. Only added two lines.

import pygame as pg
pg.init()
screen = pg.display.set_mode((800,600))
shipsheet = pg.image.load('shipsheet.png').convert()
shipsheet.set_colorkey((255,255,255))
 
def strip_from_sheet(sheet, start, size, columns, rows):
    frames =
    for j in range(rows):
        for i in range(columns):
            location = (start[0]+size[0]*i, start[1]+size[1]*j)
            frames.append(sheet.subsurface(pg.Rect(location, size)))
    return frames
 
class Ship:
    def __init__(self, sheet):
        self.frames = strip_from_sheet(sheet, (0,0), (122,164), 4, 4)
        self.frame_index = 0
        self.update()
         
    def update(self):
        self.image = self.frames[self.frame_index]
         
    def get_event(self, event):
        if event.type == pg.KEYDOWN:
            self.frame_index += 1
         
    def draw(self, screen):
        screen.blit(self.image, (0,0))
         
         
done = False
ship = Ship(shipsheet)
             
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True
        ship.get_event(event)
    ship.update()
    screen.fill((0,0,220))
    ship.draw(screen)
    pg.display.update()
to be continued...

So its been over a year since i wrote this and now i am trying to add content. So forgive the hiccup of thoughts here.

   
Here is a simple script to run through each image on this spritesheet. 
import pygame as pg
pg.init()
screen = pg.display.set_mode((800,600))
batsheet = pg.image.load('silverbat.png').convert_alpha()
  
  
def strip_from_sheet(sheet, start, size, columns, rows):
    frames = []
    for j in range(rows):
        for i in range(columns):
            location = (start[0]+size[0]*i, start[1]+size[1]*j)
            frames.append(sheet.subsurface(pg.Rect(location, size)))
    return frames
  
class Bat:
    def __init__(self, sheet):
        self.frames = strip_from_sheet(sheet, (0,0), (32,48), 4, 4)
        self.frame_index = 0
        self.update()
          
    def update(self):
        self.image = self.frames[self.frame_index]
          
    def get_event(self, event):
        if event.type == pg.KEYDOWN:
            if not self.last_index():
                self.frame_index += 1
            
    def last_index(self):
        if self.frame_index == len(self.frames) - 1:
            self.frame_index = 0 #reset
            return True
          
    def draw(self, screen):
        screen.blit(self.image, (0,0))
          
          
done = False
bat = Bat(batsheet)
              
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True
        bat.get_event(event)
    bat.update()
    screen.fill((0,0,0))
    bat.draw(screen)
    pg.display.update()
In this spritesheet there is an alpha channel, so there is no need for setting the colorkey and all that. This is basically the same code as previously except to a different spritesheet, added clearing the screen by screen fills to remove the old drawn image, and there is a method to handle switch the frame index back to zero after it scrolls through them all. But that is going to be gone here soon.

We want to break away the animation to time base. As well as be able to turn our character and change the animation to the direction the player is facing.
import pygame as pg
pg.init()

screen = pg.display.set_mode((800,600))
batsheet = pg.image.load('silverbat.png').convert_alpha()
  
  
def strip_from_sheet(sheet, start, size, columns, rows):
    frames = []
    for j in range(rows):
        for i in range(columns):
            location = (start[0]+size[0]*i, start[1]+size[1]*j)
            frames.append(sheet.subsurface(pg.Rect(location, size)))
    return frames
  
class Bat:
    def __init__(self, sheet):
        self.all_frames = strip_from_sheet(sheet, (0,0), (32,48), 4, 4)
        self.setup_frames()
        self.direction = 'down'
        self.ani_delay = 100
        self.ani_timer = 0.0
        self.ani_index = 0
        self.image = self.frames[self.direction][self.ani_index]
        self.update()
        
    def setup_frames(self):
        '''organize the frames on the spritesheet to possible directions'''
        self.frames = {
            'down':self.all_frames[:4],
            'left':self.all_frames[4:8],
            'right':self.all_frames[8:12],
            'up':self.all_frames[12:16]
        }
        
    def animate(self):
        '''animate current direction'''
        if pg.time.get_ticks()-self.ani_timer > self.ani_delay:
            self.ani_timer = pg.time.get_ticks()
            self.image = self.frames[self.direction][self.ani_index]
            if self.ani_index == len(self.frames[self.direction])-1:
                self.ani_index = 0
            else:
                self.ani_index += 1
          
    def update(self):
        self.animate()
        
          
    def get_event(self, event):
        if event.type == pg.KEYDOWN:
            if event.key == pg.K_DOWN:
                self.direction ='down'
                self.ani_index = 0
            if event.key == pg.K_LEFT:
                self.direction = 'left'
                self.ani_index = 0
            if event.key == pg.K_RIGHT:
                self.direction = 'right'
                self.ani_index = 0
            if event.key == pg.K_UP:
                self.direction = 'up'
                self.ani_index = 0
          
    def draw(self, screen):
        screen.blit(self.image, (0,0))
          
          
done = False
clock = pg.time.Clock()
bat = Bat(batsheet)
              
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True
        bat.get_event(event)
    bat.update()
    screen.fill((0,0,0))
    bat.draw(screen)
    pg.display.update()
    clock.tick(60)
 
This animates constantly of the direction in which you press by the ARROW keys. However it still doesnt feel right until you add movement into play. With movement we move the animation direction out of the keypress event to constant keys since we need to move constantly if we hod the arrow key down. The following shows the same animation with the ability to move around the screen.
import pygame as pg
pg.init()

screen = pg.display.set_mode((800,600))
screen_rect = screen.get_rect()
batsheet = pg.image.load('silverbat.png').convert_alpha()
  
  
def strip_from_sheet(sheet, start, size, columns, rows):
    frames = []
    for j in range(rows):
        for i in range(columns):
            location = (start[0]+size[0]*i, start[1]+size[1]*j)
            frames.append(sheet.subsurface(pg.Rect(location, size)))
    return frames
  
class Bat:
    def __init__(self, sheet, screen_rect):
        self.screen_rect = screen_rect
        self.all_frames = strip_from_sheet(sheet, (0,0), (32,48), 4, 4)
        self.setup_frames()
        self.direction = 'down'
        self.ani_delay = 100
        self.ani_timer = 0.0
        self.ani_index = 0
        self.image = self.frames[self.direction][self.ani_index]
        
        self.speed = 5
        self.rect = self.image.get_rect(center=self.screen_rect.center)
        self.update()
        
    def setup_frames(self):
        '''organize the frames on the spritesheet to possible directions'''
        self.frames = {
            'down':self.all_frames[:4],
            'left':self.all_frames[4:8],
            'right':self.all_frames[8:12],
            'up':self.all_frames[12:16]
        }
        
    def animate(self):
        '''animate current direction'''
        if pg.time.get_ticks()-self.ani_timer > self.ani_delay:
            self.ani_timer = pg.time.get_ticks()
            self.image = self.frames[self.direction][self.ani_index]
            if self.ani_index == len(self.frames[self.direction])-1:
                self.ani_index = 0
            else:
                self.ani_index += 1
          
    def update(self):
        self.animate()
        self.rect.clamp_ip(self.screen_rect)
        keys = pg.key.get_pressed()
        if keys[pg.K_DOWN]:
            self.direction = 'down'
            self.rect.y += self.speed
        if keys[pg.K_RIGHT]:
            self.direction = 'right'
            self.rect.x += self.speed
        if keys[pg.K_LEFT]:
            self.direction = 'left'
            self.rect.x -= self.speed
        if keys[pg.K_UP]:
            self.direction = 'up'
            self.rect.y -= self.speed
          
    def get_event(self, event):
        pass
          
    def draw(self, screen):
        screen.blit(self.image, self.rect)
          
          
done = False
clock = pg.time.Clock()
bat = Bat(batsheet, screen_rect)
              
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True
        bat.get_event(event)
    bat.update()
    screen.fill((0,0,0))
    bat.draw(screen)
    pg.display.update()
    clock.tick(60)
   


Part 6
https://python-forum.io/Thread-PyGame-Enemy-AI-part-6
Recommended Tutorials:
Reply
#2
explosion animation
https://python-forum.io/Thread-PyGame-Sp...-explosion
Recommended Tutorials:
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [PyGame] Basic event handling (part 3) metulburr 0 7,494 Oct-09-2016, 03:15 PM
Last Post: metulburr

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020