Programming: Sprites Pt 2

With the whole sprite sheet loaded, there needs to be a way to determine which frame to draw. The purpose of different animation frames is to describe different conditions: direction, walking, attacking, etc. 

On the sprite sheet, I can plot each condition that the frames correspond to:

Since the sprites loaded as a list in "self.sprites", I need a way to properly index through them. I'll start with the "self.frame" variable.  I can also create the conditions for each state: "Direction", "Walk", "Attack", & "Hit". I'll have to update the respective functions to toggle the respective "self.hit", "self.attack" variables. 
class Characters(object):
    ...
    #Animation States    
    self.frame = 0 
    self.direction = 'Left' 
    self.walk = False
    self.attack = False
    self.hit = False

I'll use the following logic to decide which frame to draw. First, find which direction the object is facing. The direction will determine which set of frames to use. Next, check if the object is getting Hit, then check if they're Attacking, etc. The order of these states are important; the object getting Hit will take priority over Attacking or Walking. If the "Hit" state is True, then it'll draw the "Hit" frames for the appropriate direction. But if all states are False, the animation will default to a "Standing" pose, in the object direction. 

To update the "self.frame" variable, this function needs to be constantly running to check for the direction and conditions. To cycle through the animation frames, the "+=" operator is used to tick up the variable value by 0.25. Thus, four "While" loops are needed to tick up to the next frame. With the script refresh rate set at 30 frames/second, this means each animation frame will last 0.133 seconds (1/30 second/frame * 4).  The animation only occurs for the "Walk" and "Attack" condition; not for the "Standing" condition. 

The frame value will continue ticking until reaching an upper limit, afterwords the frame value resets to the beginning frame to reset the animation. 

class Characters(object):
    ...
    def frames(self):
if self.direction == 'Down':
    if self.walk or self.attack:
self.frame += 0.25 
    if self.hit:
self.frame = 25 #Hit frame
    elif self.attack:
if self.frame<15 or self.frame>16:
            self.frame = 15 #Set to attack frame
elif self.walk:
             if self.frame > 2:
     self.frame = 0
    else: #Standing still 
self.frame = 0 
            if self.frame>16:
self.attack = False #Toggles "Attack" state
            if not self.invul:
self.hit = False #Once invulnerability ends, toggle "Hit" state

Rather than iterating this for every single direction, I can make things more modular. Within the class attributes, I'll create a nested dictionary that takes the input of "Direction" and the condition states to return the appropriate frame values. Since I know precisely where each frame belongs, this is easy to layout: 

self.frame_dictionary = {'Down': {'Hit': 25, 'Attack': 15, 'Walk':0},  'Down Left': {'Hit': 26, 'Attack': 17, 'Walk':3},
                                'Left': {'Hit': 27, 'Attack': 19, 'Walk':6},
                                'Up Left': {'Hit': 28, 'Attack': 21, 'Walk':9},
                                'Up': {'Hit': 29, 'Attack': 23, 'Walk':12},
                                'Down Right': {'Hit': 26, 'Attack': 17, 'Walk':3},
                                'Right': {'Hit': 27, 'Attack': 19, 'Walk':6},
                                'Up Right': {'Hit': 28, 'Attack': 21, 'Walk':9}}

Final Blit Function

Finally, rather than calling out a static frame,  I'll use the "frame" variable for the "blit" function. Note the "math.floor()" function is used. I'm ticking up the frame value by 0.25, but lists can't be indexed with decimals. The "math.floor()" function converts down the decimals into integers to make them usable. Thus, from frames 0-2; each frame will be drawn for four frames (0.133 seconds). 

Lastly, you may notice that the sprite sheet above only have frames for facing the left side. Thankfully, the Pygame "transform.flip()" can be used to flip surface horizontally or vertically. By default the "Left" direction will set the "self.flip" variable to 0, while "Right" directions will set it to 1. Thus, the final blit function will look like this: 

win.blit(pygame.transform.flip(Red_Guy.sprites[round(Red_Guy.frame)],Red_Guy.flip,0),(Red_Guy.x-11,Red_Guy.y-9))

I can copy everything above the "Enemy" class to create fully animated object sprites:
That's right, I used a nasty ol' Grimer as the enemy

Final Code

Now, rather than pasting the entire chunk of code I'll be uploading them to GitHub. I've also added the .png files used for loading the sprite sheets.

Comments