Simplest Game Ever with Pygame

This time we are going to make a FULL GAME in one post. It will be built from scratch, so that you can see every detail of it, not having to guess how it works. It will be fully functional, with a game logic and a goal, but it will be the simplest as possible, just to make you aware of the process of making a game in most straight way and in the fastest time, so that you can then have a solid base to understand how to make games with Python (and maybe with every other language) on your own.

The repository with the game (and some updates).

The Screen

Let’s start with making a window

import pygame as pg

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

loop = 1
while loop:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0

pg.quit()

This is the way to make a window that you can close properly (without checking the QUIT action, you should force the closing of the window). There is nothing in the window, of course.

The Enemy

Let’s create an enemy with a simple class for position (x, y) and image (image). The sprite will have 3 attributes for now: x and y for the position (0, 0 is at the right left corner of the screen that is 800 px of width and 600 px of height… the 800 px is on the extreme right of the screen, the 600 px is at the bottom).

This is the class for the eneny

class Enemy:
    def __init__(self):
        self.x = 300
        self.y = 10
        self.go = 'right'
        self.image = pg.image.load("enemy.png")

enemy = Enemy()

This is how to show the image on the screen

loop = 1
while loop:
    screen.blit(enemy.image, (enemy.x, enemy.y))
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0

    pg.display.update()

pg.quit()

This is the whole code for the moment

import pygame as pg

pg.init()
screen = pg.display.set_mode((600, 400))
# ====================

class Enemy:
    def __init__(self):
        self.x = 300
        self.y = 10
        self.go = 'right'
        self.image = pg.image.load("enemy.png")

enemy = Enemy()


# ====================
loop = 1
while loop:
    screen.blit(enemy.image, (enemy.x, enemy.y))
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0

    pg.display.update()

pg.quit()

This is the enemy image (use another image with the same name if you want something nicer).

and a little update of the enemy …

Moving the Alien aka the enemy

Now, we do not want the movement to be very complex, right? It is just a tutorial for a very basic stuff. Let’s give the alien/enemy the simplest movement ever. It will go from left to right and reappear on the left when he disappears on the right.

We will use this simple code in the while loop:

    if enemy.x < 600:
        enemy.x += .05
    else:
        enemy.x = -100

the while loop now is this (the first 15 lines are unchanged):

loop = 1
while loop:
    if enemy.x < 600:
        enemy.x += .05
    else:
        enemy.x = -100
    screen.blit(enemy.image, (enemy.x, enemy.y))
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0

    pg.display.update()

pg.quit()

Substitute the Enemy class with a Sprite class

Now we create a more abstract class for the sprites, so that you can use it also for the player, not only for the enemy. We rename the class Enemy to Sprite and we pass to it the name of the file image and the position (x, y), that can be different for the Enemy and the Sprite, of course.

<table>

<td><td>

</table>

class Sprite:
    def __init__(self, image, x, y):
        self.x = x
        self.y = y
        self.go = 'right'
        self.image = pg.image.load(image)

# pass: image, starting x and y position
enemy = Sprite("enemy.png", 300, 10)
player = Sprite("player.png", 300, 350)

In the while loop we show the sprite like this:

    screen.blit(enemy.image, (enemy.x, enemy.y))
    screen.blit(player.image, (player.x, player.y))

The player image I ‘made’ is this (use your onw):

a “second version”

For the enemy nothing changed in the while loop. Now we have the player shown on the screen, but we cannot move it yet.

Moving the player

We need to move it with 2 keys… but we could also use the movement of the mouse?

loop = 1
while loop:
    if enemy.x < 600:
        enemy.x += .05
    else:
        enemy.x = -100
    if player.dir == "left":
        if player.x > 0:
            player.x -= .1
    if player.dir == "right":
        if player.x < 560:
            player.x += .1
    screen.blit(enemy.image, (enemy.x, enemy.y))
    screen.blit(player.image, (player.x, player.y))
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0
        if event.type == pg.KEYDOWN:
            keys = pg.key.get_pressed()
            if keys[K_LEFT]:
                player.dir = "left"
            if keys[K_RIGHT]:
                player.dir = "right"
        if event.type == pg.KEYUP:
            player.dir = "stop"
   

    pg.display.update()

pg.quit()

Let’s fire: introducing another sprite, the bullet

Now we are going to fire, but we will not harm the enemy… yet. What we did is just create another sprite with the same class Sprite with a new image, in a position that is next to the top of the player starship and then move it together the ship, so that they seem the same object. Then when we press the up arrow key, the bullet is fired and goes up until the top. In the next paragraph we will see how to destroy the enemy ship.

This tiny thing is the bullet (it’s there, on the left, it’s yellow… do you see it?)

import pygame as pg
from pygame.locals import *

pg.init()
screen = pg.display.set_mode((600, 400))
# ====================


class Sprite:
    def __init__(self, img, x, y):
        self.x = x
        self.y = y
        self.dir = 'stop'
        self.image = pg.image.load(img)


enemy = Sprite("enemy.png", 0, 10)
player = Sprite("player.png", 250, 300)
bullet = Sprite("bullet.png", 298, 295)

loop = 1
# ==================== THE WHILE LOOP ========
while loop:
    screen.fill((0, 0, 0))
    if enemy.x < 600:
        enemy.x += .1
    else:
        enemy.x = -100

    if bullet.dir == "up":
        bullet.y -= 1
        if bullet.y < 0:
            bullet.y = 295
            bullet.dir = "stop"

    if player.dir == "left":
        if player.x > 0:
            player.x -= .1
            bullet.x -= .1

    if player.dir == "right":
        if player.x < 495:
            player.x += .1
            bullet.x += .1

    screen.blit(enemy.image, (enemy.x, enemy.y))
    screen.blit(player.image, (player.x, player.y))
    screen.blit(bullet.image, (bullet.x, bullet.y))
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0
        if event.type == pg.KEYDOWN:
            keys = pg.key.get_pressed()
            if keys[K_UP]:
                print("fire")
                bullet.dir = "up"
            if keys[K_RIGHT]:
                player.dir = "right"
            if keys[K_LEFT]:
                player.dir = "left"
        if event.type == pg.KEYUP:
            player.dir = "stop"




    pg.display.update()

pg.quit()

Let’s check when the enemy is hit

Now we know when the enemy is hit. In the next paragraph we will make the enemy fall down, add sounds and increase score, making another enemy appear.

import pygame as pg
from pygame.locals import *

pg.init()
screen = pg.display.set_mode((600, 400))
# ====================


class Sprite:
    def __init__(self, img, x, y):
        self.x = x
        self.y = y
        self.dir = 'stop'
        self.image = pg.image.load(img)
        self.w, self.h = self.image.get_rect().size
        self.update()

    def update(self):
        self.rect = pg.Rect(self.x, self.y, self.w, self.h)


enemy = Sprite("enemy.png", 0, 10)
player = Sprite("player.png", 250, 300)
bullet = Sprite("bullet.png", 298, 295)


loop = 1
# ==================== THE WHILE LOOP ========
while loop:
    screen.fill((0, 0, 0))
    if enemy.x < 600:
        enemy.x += .1
    else:
        enemy.x = -100

    bullet.update()
    enemy.update()

    if bullet.dir == "up":
        bullet.y -= 1
        if bullet.rect.colliderect(enemy.rect):
            print("You hit the enemy...good job!")
            bullet.y = 295
            bullet.dir = "stop"
        if bullet.y < 0:    
            bullet.y = 295
            bullet.dir = "stop"

    if player.dir == "left":
        if player.x > 0:
            player.x -= .1
            bullet.x -= .1

    if player.dir == "right":
        if player.x < 495:
            player.x += .1
            bullet.x += .1

    screen.blit(enemy.image, (int(enemy.x), int(enemy.y)))
    screen.blit(player.image, (int(player.x), int(player.y)))
    screen.blit(bullet.image, (int(bullet.x), int(bullet.y)))
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0
        if event.type == pg.KEYDOWN:
            keys = pg.key.get_pressed()
            if keys[K_UP]:
                print("fire")
                bullet.dir = "up"
            if keys[K_RIGHT]:
                player.dir = "right"
            if keys[K_LEFT]:
                player.dir = "left"
        if event.type == pg.KEYUP:
            player.dir = "stop"




    pg.display.update()

pg.quit()

The final stage… the game is here

Now, finally, the game is over.

import pygame as pg
from pygame.locals import *
from random import random, randrange


pg.init()
screen = pg.display.set_mode((600, 400))
# ====================


class Sprite:
    def __init__(self, img, x, y):
        self.x = x
        self.y = y
        self.dir = 'stop'
        self.image = pg.image.load(img)
        self.w, self.h = self.image.get_rect().size
        self.start = y
        self.update()

    def update(self):
        self.rect = pg.Rect(int(self.x), int(self.y), int(self.w), int(self.h))


enemy = Sprite("enemy.png", 0, 10)
player = Sprite("player.png", 250, 350)
bullet = Sprite("bullet.png", 280, 345)


loop = 1
go = .1
score = 0
charged = 1
# ==================== THE WHILE LOOP ========
tragitto = 1000
count = tragitto
# Whe the enemy's hit
hit = 0
while loop:
    if hit == 1:
        enemy.y += .1
        if enemy.y > 400:
            enemy.y = 10
            enemy.x = randrange(1, 500)
            hit = 0

    screen.fill((0, 0, 0))
    count -= 1
    if count == 0:
        if random() > .5:
            go = -.1
        else:
            go = .1
        count = tragitto
    enemy.x += go
    if enemy.x > 600:
        enemy.x = -100
    if enemy.x < 0:
        enemy.x = 600

    bullet.update()
    enemy.update()

    if bullet.dir == "up":
        bullet.y -= .1
        if bullet.rect.colliderect(enemy.rect):
            score += 10
            print("Score = " + str(score))
            hit = 1
            bullet.y = bullet.start
            bullet.dir = "stop"
            charged = 1
            bullet.x = player.x + 30
        elif bullet.y < 0: 
            bullet.y = bullet.start
            bullet.x = player.x + 30
            bullet.dir = "stop"
            charged = 1


    if player.dir == "left":
        if player.x > 0:
            player.x -= .1
            if bullet.dir == "stop":
                bullet.x -= .1

    if player.dir == "right":
        if player.x < 495:
            player.x += .1
            if bullet.dir == "stop":
                bullet.x += .1


    screen.blit(enemy.image, (int(enemy.x), int(enemy.y)))
    screen.blit(player.image, (int(player.x), int(player.y)))
    screen.blit(bullet.image, (int(bullet.x), int(bullet.y)))
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0

        if charged:
            if event.type == pg.KEYDOWN:
                if event.key == K_UP:
                    bullet.dir = "up"
                    charged = 0
        if pg.key.get_pressed():
            keys = pg.key.get_pressed()
            # if keys[K_UP]:
            if keys[K_RIGHT]:
                player.dir = "right"
            if keys[K_LEFT]:
                player.dir = "left"


        # if event.type == pg.KEYUP:
        #     player.dir = "stop"




    pg.display.update()

pg.quit()

Making the ship stop… when you do not press the key

I decided to not make the ship move continuosly. In the code you can see the change is here:

            if keys[K_RIGHT]:
                player.dir = "right"
            elif keys[K_LEFT]:
                player.dir = "left"
            else:
                player.dir = "stop"

So, if you press right or left it will go, otherwise player.dir will be set to “stop”, so it is neither right or left and it will not affect the change of the position on the x axe. Notice that now I put the images in the folder imgs, so to load them you have to put them into a folder called imgs or change the code pygame.image.load(“imgs/…”) without imgs/ at the start of the name of the file.

import pygame as pg
from pygame.locals import *
from random import random, randrange




class Sprite:
    def __init__(self, img, x, y):
        self.x = x
        self.y = y
        self.dir = 'stop'
        self.image = pg.image.load(img)
        self.w, self.h = self.image.get_rect().size
        self.start = y
        self.update()

    def update(self):
        self.rect = pg.Rect(int(self.x), int(self.y), int(self.w), int(self.h))

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

# ============= SPRITES
enemy = Sprite("imgs/enemy.png", 0, 10)
player = Sprite("imgs/player.png", 250, 350)
bullet = Sprite("imgs/bullet.png", 280, 345)

# Movement speed
go = .1
# The score
score = 0
# When 1 you can fire the bullet
charged = 1
# This is the time to randomly change enemy path direction
tragitto = 1000
count = tragitto
# Whe the enemy's hit
hit = 0



# ==================== THE WHILE LOOP ========
loop = 1
while loop:
    if hit == 1:
            enemy.y += .1
    if enemy.y > 400:
        enemy.y = 10
        enemy.x = randrange(1, 500)


    screen.fill((0, 0, 0))
    count -= 1
    if count == 0:
        if random() > .5:
            go = -.1
        else:
            go = .1
        count = tragitto
    enemy.x += go
    if enemy.x > 600:
        enemy.x = -100
    if enemy.x < 0:
        enemy.x = 600

    bullet.update()
    enemy.update()

    if bullet.dir == "up":
        bullet.y -= .1
        if bullet.rect.colliderect(enemy.rect):
            score += 10
            print("Score = " + str(score))
            hit = 1
            bullet.y = bullet.start
            bullet.dir = "stop"
            charged = 1
            bullet.x = player.x + 30
        elif bullet.y < 0: 
            bullet.y = bullet.start
            bullet.x = player.x + 30
            bullet.dir = "stop"
            charged = 1


    if player.dir == "left":
        if player.x > 0:
            player.x -= .1
            if bullet.dir == "stop":
                bullet.x -= .1

    if player.dir == "right":
        if player.x < 495:
            player.x += .1
            if bullet.dir == "stop":
                bullet.x += .1


    screen.blit(enemy.image, (int(enemy.x), int(enemy.y)))
    screen.blit(player.image, (int(player.x), int(player.y)))
    screen.blit(bullet.image, (int(bullet.x), int(bullet.y)))
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0

        if charged:
            if event.type == pg.KEYDOWN:
                if event.key == K_UP:
                    bullet.dir = "up"
                    charged = 0
        if pg.key.get_pressed():
            keys = pg.key.get_pressed()
            # if keys[K_UP]:
            if keys[K_RIGHT]:
                player.dir = "right"
            elif keys[K_LEFT]:
                player.dir = "left"
            else:
                player.dir = "stop"

        # if event.type == pg.KEYUP:
        #     player.dir = "stop"


    pg.display.update()

pg.quit()

 

The repository with the game stuffs

Click here for the repository

Depository updates

31.1.2020: you can find a version with audio in the folder sg3.

The version with sounds (of folder sg3 in the repository above).

import pygame as pg
from pygame.locals import *
from random import random, randrange




class Sprite(pg.sprite.Sprite):
    def __init__(self, img, x, y):
        pg.sprite.Sprite.__init__(self)
        self.x = x
        self.y = y
        self.dir = 'stop'
        self.image = pg.image.load(img)
        self.w, self.h = self.image.get_rect().size
        self.start = y
        self.update()

    def update(self):
        self.rect = pg.Rect(int(self.x), int(self.y), int(self.w), int(self.h))

def music_init():
    "Initialize sounds avoiding delay"
    pg.mixer.pre_init(44100, -16, 1, 512)
    pg.init()
    pg.mixer.quit()
    pg.mixer.init(22050, -16, 2, 512)
    pg.mixer.set_num_channels(32)


music_init()
screen = pg.display.set_mode((600, 400), pg.RESIZABLE)
pg.display.set_caption("Game")
clock = pg.time.Clock()
# ============= SPRITES

enemy = Sprite("imgs/enemy.png", 30, 50)
player = Sprite("imgs/player.png", 250, 350)
bullet = Sprite("imgs/bullet.png", 272, 345)

# Movement speed
go = 1
# The score
score = 0
# When 1 you can fire the bullet
charged = 1
# This is the time to randomly change enemy path direction
tragitto = 100
count = tragitto
# Whe the enemy's hit
hit = 0



def game_sounds():
    "Loads the sounds for the game"
    sounds_list = "fire", "hit"
    sound_dic = {}
    for sound in sounds_list:
        sound_path = f"audio/{sound}.wav"
        print(sound_path)
        sound_dic[sound] = pg.mixer.Sound(sound_path)
    return sound_dic

sounds_dic = game_sounds()

def create_fonts(font_sizes_list):
    fonts = []
    for size in font_sizes_list:
        fonts.append(pg.font.SysFont("Arial", size))
    return fonts


def _display(fnt, what, color, where):
    text_to_show = fnt.render(what, 0, pg.Color(color))
    screen.blit(text_to_show, where)


def display_fps():
    _display(
        fonts[0],
        what=str(int(clock.get_fps())),
        color="white",
        where=(0, 0))


def display_score():
    _display(
        fonts[0],
        what="Score " + str(score),
        color="white",
        where=(300, 0))


fonts = create_fonts([32, 16, 14, 8])
sound = pg.mixer.Sound.play
# ==================== THE WHILE LOOP ========


def enemy_back():
    global hit
    enemy.y = 10
    enemy.x = randrange(1, 500)
    hit = 0


loop = 1
while loop:
    # When enemy's hit falls down and can hit the player
    if hit == 1:
        sound(sounds_dic["hit"])
        tragitto = 20
        enemy.y += 1
        # if bullet.rect.colliderect(enemy.rect):
        #     score += 100
        #     enemy.y = 10
        #     enemy.x = randrange(1, 500)
        #     hit = 0
        #     print("You killed it definitively")
        if enemy.rect.colliderect(player.rect):
            print("You died!")
            hit = 0
            enemy.y = 10
            enemy.x = randrange(1, 500)
            score -= 10
            tragitto = 100
    if enemy.y > 400:
        enemy.y = 10
        enemy.x = randrange(1, 500)
        hit = 0
        score -= 20
        print("Your score is" + str(score))
        print("Alien reached the Earth")
        tragitto = 100
    if hit == 2:
        sound(sounds_dic["hit"])
        print("You killed the alien")
        enemy.x = randrange(1, 500)
        enemy.y = 30
        score += 50
        hit = 0
        tragitto = 100


    screen.fill((0, 0, 0))
    display_fps()
    display_score()
    count -= 1
    if count == 0:
        if random() > .5:
            if enemy.x > 0:
                go = -1
        else:
            if enemy.x < 600:
                go = 1 
        count = tragitto
    enemy.x += go
    # if enemy.x > 600:
    #     enemy.x = -100
    # if enemy.x < 0:
    #     enemy.x = 600

    bullet.update()
    enemy.update()
    player.update()

    if bullet.dir == "up":
        bullet.y -= 2
        if bullet.rect.colliderect(enemy.rect):
            score += 10
            print("Score = " + str(score))
            hit += 1
            bullet.y = bullet.start
            bullet.dir = "stop"
            charged = 1
            bullet.x = player.x + 22
        elif bullet.y < 0: 
            bullet.y = bullet.start
            bullet.x = player.x + 22
            bullet.dir = "stop"
            charged = 1


    if player.dir == "left":
        if player.x > 0:
            player.x -= 1
            if bullet.dir == "stop":
                bullet.x -= 1

    if player.dir == "right":
        if player.x < 550:
            player.x += 1
            if bullet.dir == "stop":
                bullet.x += 1


    screen.blit(enemy.image, (int(enemy.x), int(enemy.y)))
    screen.blit(player.image, (int(player.x), int(player.y)))
    screen.blit(bullet.image, (int(bullet.x), int(bullet.y)))
    for event in pg.event.get():
        if event.type == pg.QUIT:
            loop = 0

        if charged:
            if event.type == pg.KEYDOWN:
                if event.key == K_UP:
                    sound(sounds_dic["fire"])
                    bullet.dir = "up"
                    charged = 0
        if pg.key.get_pressed():
            keys = pg.key.get_pressed()
            # if keys[K_UP]:
            if keys[K_RIGHT]:
                player.dir = "right"
            elif keys[K_LEFT]:
                player.dir = "left"
            else:
                player.dir = "stop"

        # if event.type == pg.KEYUP:
        #     player.dir = "stop"

    clock.tick(240)
    pg.display.update()

pg.quit()

 

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.