Pygame and Sprites – Part 1

Let’s start from skratch

Let’s see how we can show sprites and move them, detect collisions.

Load a sprite

import pygame as pg
import os

sprite = pg.image.load("cat\\Idle (1).png")

print(sprite)

The sprite is a Surface object

pygame 2.0.0.dev10 (SDL 2.0.12, python 3.8.3)
Hello from the pygame community. https://www.pygame.org/contribute.html
<Surface(128x128x32 SW)>

You can see that is 128×128 as size and 32 as color.

From the documentation:

Load an image from a file source. You can pass either a filename or a Python file-like object.

Pygame will automatically determine the image type (e.g., GIF or bitmap) and create a new Surface object from the data.

Show an image / sprite

import pygame as pg
import os

screen = pg.display.set_mode((400, 400))
sprite = pg.image.load("06\\06.svg")

print(sprite)

loop = 1
while loop:
	for event in pg.event.get():
		if event.type == pg.QUIT:
			loop = 0
	screen.blit(sprite, (0, 0))
	pg.display.flip()

pg.quit()

Do something when user hit a key, any key

Now we put in the for loop inside the while loop the “event listener2” fo the user’s key press

        if event.type == pg.KEYDOWN:
            print("go")

That goes in the code like this:

import pygame as pg
import os

screen = pg.display.set_mode((400, 400))
sprite = pg.image.load("..\\06\\06.svg")

print(sprite)

loop = 1
while loop:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0
        if event.type == pg.KEYDOWN:
            print("go")
    screen.blit(sprite, (0, 0))
    pg.display.flip()

pg.quit()

Create a Sprite class and Group and rect

We need to

  • create a class that loads the image / images and create a rect attribute
  • add the sprite to a group of sprites (created with pygame.group.Group)
  • draw the group on the screen
import pygame as pg


screen = pg.display.set_mode((400, 400))


class Sprite(pg.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pg.image.load("..\\06\\06.svg")
        self.rect = self.image.get_rect()


g = pg.sprite.Group()
sprite = Sprite()
g.add(sprite)
print(sprite)

loop = 1
while loop:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0
        if event.type == pg.KEYDOWN:
            print("go")
    g.draw(screen)
    pg.display.flip()

pg.quit()

Load all the images… for an animation

import pygame as pg
from glob import glob

screen = pg.display.set_mode((400, 400))


class Sprite(pg.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.animation = [pg.image.load(f) for f in glob("..\\cat\\Idle *.png")]
        self.image = self.animation[0]
        self.rect = self.image.get_rect()


g = pg.sprite.Group()
sprite = Sprite()
g.add(sprite)
print(sprite)

loop = 1
while loop:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0
        if event.type == pg.KEYDOWN:
            print("go")
    g.draw(screen)
    pg.display.flip()

pg.quit()

The images are loaded but we see just one of them

The image is still standing still, because we see just the self.animation[0] of all the images that are store in the self.animation list of surface objects (when we load an image with pg.pygame.load we have in return a Surface object).

Blit and blits

The screen is also a Surface object and we can use blit to display one image onto another. We can also use blits to display more images onto another. I think this can make us gain framerate when we have many surfaces at once.

In the examples above we did not use blit or blits, but draw, that is a method of Group, so that we can draw many sprites (not Surfaces) on the screen at once.

Let’s update the image with a counter

If we add this into the Sprite class:

    def update(self):
        self.acount += 1
        if self.acount == len(self.animation):
            self.acount = 0
        self.image = self.animation[self.acount]

And we update the group g in the while loop:

while loop:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0
        if event.type == pg.KEYDOWN:
            print("go")
    g.draw(screen)
    g.update()
    pg.display.flip()

We will see the sprite move insanely fast

Clock

Let’s use the clock to put a limit to the frame rate

clock = pg.time.Clock()

if, at the end of the while loop we put this, the framerate will slow down and so the animation.

    clock.tick(30)

Do something when Key Right is pressed

We’ve seen how to print something on the console when we press any key. What if we want that something happens when a certain key is pressed like arrow right key?

In the while loop we make a for loop that searches into pygame.event.get() that is the list of interactions intercepted with the user; we check then the event.type if is a pygame.KEYDOWN event and then if is

While loop:

  • pg.event.get()
    • event.type()
      • event.key

After the event.type if statement that checks if you press a key, you can then check what event.key you pressed with this code:

        if event.type == pg.KEYDOWN:
            if event.key == pg.K_RIGHT:
                print("RIGHT")

Make the the sprite do something with the keys pressed

Now it walks when you press the right arrow key, stopw with the left and jumps with up.

This is the change in the while loop. When you press a keys the animation of “sprite” changes differently for each key, so that it seems that the player does a certain action.

        if event.type == pg.KEYDOWN:
            if event.key == pg.K_RIGHT:
                sprite.change_to("walk")
            if event.key == pg.K_LEFT:
                sprite.change_to("idle")
            if event.key == pg.K_UP:
                sprite.change_to("jump")

And this is the method change animation

    def change_to(self, action):
        "This makes the animation of the sprite to change when you press a key"
        self.action = action
        self.animation = [
            pg.image.load(f) for f in glob(f"..\\cat\\{self.action} *.png")]

The whole code

import pygame as pg
from glob import glob

screen = pg.display.set_mode((200, 200))
pg.display.set_caption("Game")
clock = pg.time.Clock()


class Sprite(pg.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.acount = 0

        # Coordinates for movement
        self.x = 0
        self.y = 0
        self.action = "idle"

        self.animation = [
            pg.image.load(f) for f in glob("..\\cat\\Idle *.png")]
        self.image = self.animation[0]
        self.rect = self.image.get_rect()
        print(self.image)

    def update(self):
        self.acount += 1
        if self.acount == len(self.animation):
            self.acount = 0
        self.image = self.animation[self.acount]

    def change_to(self, action):
        "This makes the animation of the sprite to change when you press a key"
        self.action = action
        self.animation = [
            pg.image.load(f) for f in glob(f"..\\cat\\{self.action} *.png")]


g = pg.sprite.Group()
sprite = Sprite()
g.add(sprite)
print(sprite)

loop = 1
while loop:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0
        if event.type == pg.KEYDOWN:
            if event.key == pg.K_RIGHT:
                sprite.change_to("walk")
            if event.key == pg.K_LEFT:
                sprite.change_to("idle")
            if event.key == pg.K_UP:
                sprite.change_to("jump")
    screen.fill((0, 0, 0))
    g.draw(screen)
    g.update()
    pg.display.flip()
    clock.tick(30)

pg.quit()

Jump just one time

I added this statement here to set to zero the self.acount when you change animation

and when you do not hold the key up, it will stop the action…

        if event.type == pg.KEYUP:
            if event.key == pg.K_UP:
                sprite.change_to("idle")

But, doing so, it will jump forever when you click or for a time too little. So I made this

import pygame as pg
from glob import glob

screen = pg.display.set_mode((200, 200))
pg.display.set_caption("Game")
clock = pg.time.Clock()


class Sprite(pg.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.acount = 0

        # Coordinates for movement
        self.x = 0
        self.y = 0
        self.action = "idle"

        self.animation = [
            pg.image.load(f) for f in glob("..\\cat\\Idle *.png")]
        self.image = self.animation[0]
        self.rect = self.image.get_rect()
        print(self.image)

    def update(self):
        self.acount += 1
        if self.acount == len(self.animation):
            self.acount = 0
        self.image = self.animation[self.acount]

    def change_to(self, action):
        self.acount = 0
        self.action = action
        self.animation = [
            pg.image.load(f) for f in glob(f"..\\cat\\{self.action} *.png")]


g = pg.sprite.Group()
sprite = Sprite()
g.add(sprite)
print(sprite)

loop = 1
jumpcount = 0
jumpstate = 0
walkstate = 0
while loop:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0
        if event.type == pg.KEYDOWN:
            if event.key == pg.K_RIGHT:
                walkstate = 1
                sprite.change_to("walk")
            if event.key == pg.K_LEFT:
                walkstate = 0
                sprite.change_to("idle")
            if event.key == pg.K_UP:
                jumpstate = 1
                sprite.change_to("jump")
    if jumpstate:
        jumpcount += 1
        print(jumpcount)
        if jumpcount > 8:
            jumpstate = 0
            jumpcount = 0
            if walkstate:
                sprite.change_to("walk")
            else:
                sprite.change_to("idle")
    screen.fill((0, 0, 0))
    g.draw(screen)
    g.update()
    pg.display.flip()
    clock.tick(30)

pg.quit()

The result is this:

Jump actually: aka “Move that sprite up and down”

Let the sprite to go up and come back down when he jumps. We will finally see how we can move the sprite using rect.x an rect.y (that is returned from pygame.image.get_rect())

    if jumpstate:
        jumpcount += 1
        if jumpcount < 4:
            sprite.rect.y -= 8
        else:
            sprite.rect.y += 4
        # print(jumpcount)
        if jumpcount > 8:
            jumpstate = 0
            jumpcount = 0
            if walkstate:
                sprite.change_to("walk")
            else:
                sprite.change_to("idle")

Video expalnation of the code

Code optimization

To make the code faster it is better to load images at the start, instead of doing it every time you press a key, as you have seen in the video.

Load before the while loop

walk = sprite.change_to("walk")
jump = sprite.change_to("jump")
idle = sprite.change_to("idle")

In the while loop

while loop:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0
        if event.type == pg.KEYDOWN:
            if event.key == pg.K_RIGHT:
                sprite.acount = 0
                sprite.animation = walk
                walkstate = 1
            if event.key == pg.K_LEFT:
                sprite.acount = 0
                sprite.animation = idle
                walkstate = 0
            if event.key == pg.K_UP:
                sprite.acount = 0
                sprite.animation = jump
                jumpstate = 1

The whole code

import pygame as pg
from glob import glob

screen = pg.display.set_mode((200, 200))
pg.display.set_caption("Game")
clock = pg.time.Clock()


class Sprite(pg.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.acount = 0

        # Coordinates for movement
        self.x = 0
        self.y = 0
        self.action = "idle"

        self.animation = [
            pg.image.load(f) for f in glob("..\\cat\\Idle *.png")]
        self.image = self.animation[0]
        self.rect = self.image.get_rect()
        print(self.image)

    def update(self):
        self.acount += 1
        if self.acount == len(self.animation):
            self.acount = 0
        self.image = self.animation[self.acount]

    def change_to(self, action):
        self.action = action
        self.animation = [
            pg.image.load(f) for f in glob(f"..\\cat\\{self.action} *.png")]
        return self.animation


g = pg.sprite.Group()
sprite = Sprite()
walk = sprite.change_to("walk")
jump = sprite.change_to("jump")
idle = sprite.change_to("idle")

g.add(sprite)
print(sprite)

loop = 1
jumpcount = 0
jumpstate = 0
walkstate = 0
while loop:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0
        if event.type == pg.KEYDOWN:
            if event.key == pg.K_RIGHT:
                sprite.acount = 0
                sprite.animation = walk
                walkstate = 1
            if event.key == pg.K_LEFT:
                sprite.acount = 0
                sprite.animation = idle
                walkstate = 0
            if event.key == pg.K_UP:
                sprite.acount = 0
                sprite.animation = jump
                jumpstate = 1
    if jumpstate:
        jumpcount += 1
        if jumpcount < 4:
            sprite.rect.y -= 8
        else:
            sprite.rect.y += 4
        # print(jumpcount)
        if jumpcount > 8:
            jumpstate = 0
            jumpcount = 0
            if walkstate:
                sprite.change_to("walk")
            else:
                sprite.change_to("idle")

    screen.fill((0, 0, 0))
    g.draw(screen)
    g.update()
    pg.display.flip()
    clock.tick(30)

pg.quit()

 

End of part 1. See ya on the next one


Subscribe to the newsletter for updates
Tkinter templates
Avatar My youtube channel

Twitter: @pythonprogrammi - python_pygame

Videos

Speech recognition game

Pygame's Platform Game

Other Pygame's posts

Published by pythonprogramming

Started with basic on the spectrum, loved javascript in the 90ies and python in the 2000, now I am back with python, still making some javascript stuff when needed.