The code used in this tutorial requires 4 image files. Those 4 files are attached; they must be downloaded and saved in the same directory as the game file.

Introduction

To give you an idea of what we will achieve in this tutorial, I present here a screen-dump from the game.

Here you can identify the main components (objects) that we will need for the game:

The Player (the Mario figure)

The Enemy (the black bomb-with-legs figure)

The Platforms (blue blocks – and the green 'bottom')

The Coin (yellow

circle in bottom left corner)

Actually, I will present two versions of the game.
In this Part #1 of the tutorial I'll focus mostly on Pygame issues and basic OOP (Object Oriented Programming) but keep a very simple game loop and allow some open ends in the code.

In Part #2 (separate tutorial) I'll handle some more advanced game control issues. This requires the introduction of a fifth object:

The Game (an abstract object, handling important aspects of the game)

While the first 4 objects are intuitively easy to understand, the Game object requires a little more explanation, thus a separate section.

Handling of Graphics in Pygame

Before turning to the game code, I want to introduce how graphics is handled in Pygame.
A graphic element normally has two parts: A 'Surface' and a 'Rect'.
Throughout this tutorial I'll use the names 'Surface' and 'Rect' when talking about the abstract objects. In real code any valid name can be assigned to the 'Surface' and 'Rect' variables.

The ‘Surface’ handles all about size and appearance including colorization, transparency and many specialized task (not relevant to this tutorial).
The ‘Rect’ handles all about position and collisions (and also many other tasks). The Rect has a number of position handlers like: Rect.center, Rect.midbottom, Rect.topright.
From a ‘Surface’, a ‘Rect’ can be defined using

Movement of the sprites are handled by assigning to one of the Rect handlers:

Rect.centerx += 10 # moves the sprite 10 pixels to the right

After such movement, all Rect handlers are automatically updated.
When blitting one surface onto another, the blit() function is used:

screen.blit(Surface, Rect) # blits 'Surface' onto 'screen' according to the 'Rect' position

Collisions

One of many things you can do with Rect is to check for overlap (collision) with another Rect or a list of other Rects.

To check for collision between two single Rects, you use the colliderect() method:

if rect_1.colliderect(rect_2):
# collision
else:
# no collision

The statement ”rect_1.colliderect(rect_2)” returns either True or False.
We will use it to check for collision between player, enemy and coin.

To check for collision between a single Rect and a group of Rects, you use the collidelist() method:

result = Rect.collidelist(Rect_list)

The variable result will now have the value -1 if no collisions happened or a value indicating which Rect from the list that collided with the single one.
This is used in the on_ground() method of Player and Enemy.

As usual (in my tutorials) I use 'X' and 'Y' for the display dimensions, and the name 'screen' for the actual display.
The last line defines a variable 'acc', this is the parameter for acceleration and used to make jumps look natural. Like the color constants this parameter will be globally available.

pygame.time.set_timer(pygame.USEREVENT + 1, 10000)

This line defines a timer that will appear in the event queue every 10.000 milliseconds (every 10 seconds).
You have probably seen eventID like pygame.KEYDOWN or pygame.MOUSEBUTTONUP, but may be unfamiliar with pygame.USEREVENT.
The eventID is really just small integer numbers, normally in the range from 0 to 24, but as no-one can remember which number is KEYDOWN and which is MOUSEBUTTONUP we use the named versions of the numbers. The last standard eventID is pygame.USEREVENT (== 24 on my system), and the next ones (up to 36) is free for you to use.
So, “pygame.USEREVENT + 1” simply evaluates to 25 – but DON’T use this value directly, it will make maintenance of the code a nightmare.
Here we use “pygame.USEREVENT + 1” (equal to eventID = 25) to get a signal from a timer, every 10 seconds.

In the __init__() method a number of attributes is initialized. The jump, left and right are Booleans used to control the movement of the player. The lives attribute indicates the players health – in the game this value will decrease if 'player' is hit by, or collides with, 'enemy'. The y-speed is used in jump and falls, see later.

The event() method handles all userevents and the timer mentioned earlier.

if event.key == pygame.K_SPACE and self.on_ground():
self.jump = True

We want the player to jump whenever the Space Bar is hit; but to prevent 'air-jumps' we also need to check whether the player is actually on a platform – to this end we call a specialized method, on_ground()

elif event.type == pygame.USEREVENT + 1:
enemy.timer = True

Now that we check the event queue, we also check if the timer has 'gone off'. This part of the event loop does not affect the player instance, only the enemy instance. It is therefore not a best OOP practice to have this functionality within the Player class. We will deal with this in the next version of the game.

When moving the player, we need to hold down the left/right arrow on the keyboard. We need to register this constant keypress. To this, we cannot use the event.type as it will only register the change (from up to down or vice versa) not the status of the key. Instead we can use

Each key on the keyboard has a ”pygame number” from 0 to about 250. The pygame.key.get_pressed() returns a list of same length as the number of keys. This list consists of only 0s and 1s. '0' means that the corresponding key is not pressed, while '1' indicates a pressed key.
We don't need to know the ”pygame number” for the key, as we can just use the easy-to-remember format as shown.

Now we have updated status of jump, left and right; and we are ready to move the player. The first part of move() account for the simple movements: If jump is activated, the y_speed is set to -18 (value set by me to make the jump reasonable high – you can change it). The next four lines accounts for the left/right movement within the screen dimensions.

Next we need to land on a platform or bounce if a platform is hit from below:

ok, this is tricky – please re-read the section about collisions above, and maybe even the next section explaining the on_ground() method first, and then come back here.
In the first part of the code (the major if- part) we know there is an overlap between the player and a platform.
If y_speed > 0, the player is actually landing from above on the platform.
We want to place the bottom of the player on the top of the relevant platform – but with one pixel of overlap; otherwise we are not able to detect that player is on ground. All of this we do in line 3 above.
Finally, we want the decent to stop, so: y_speed = 0
If y_speed == 0, the player is already on the platform, and the conditions are maintained.
If y_speed < 0, player hit the platform from below.
Now we need the player.rect.top to align with platform.bottom and change y_speed to downward.
If player is not on ground (the last else-clause), the velocity in y-direction is increased by the acceleration.

Running this code will show a number of platforms (ground is simply a very big platform) and a player. You can move the player around using the left/right arrows and make him jump using the space bar. Try a jump landing on a platform. Enjoy.

Back to Coding

This will soon become boring, we need an enemy and we need a collectable coin – this will make the game much more exciting. So two more objects:

The __init__() part should mostly be recognizable; we import a picture and make a corresponding Rect locating the enemy at the top of the screen. We create a random x-speed and no fall speed.
Finally we initialize a 'self.timer' to False. You might remember from the players event() method, that this variable is set to True every 10 second – it will then restart the enemy, see next paragraph.

The move() method first handles the bounce from the left and right walls; whenever a wall is hit, the x_speed parameter is negated, moving the enemy in the opposite direction.
Then the landing on platforms is handled, this is exactly as the player.
The call to hit() is covered below.
Finally we run the timer part:

If the timer has gone off, we start by resetting the 'timer' variable to False – Otherwise the code will act as if the timer is activated all the time. Then the position is reset, and so is the speed. The strange expression multiplied to the nominal speed maintains the left/right direction:
if speed>0 we multiply with (1-0) = 1
if speed<0 we get (0-1) = -1

The on_ground() method is the same as for player.

The hit() method test for collision between enemyRect and playerRect – remember, when only two Rects are checked, the output is True/False. If they hit, player’s life is decreased by one and player is reset to the starting position on ground level.

Clearly, is the number of lives falls to zero, something should happen. In this version of the game I'll leave this to you; in the next version, I'll show an example.

In the __init__() all possible positions for the coin is given in a list. In a real game this list should probably be a little longer. If the coin was allowed to spawn completely random on the screen, there is a risk of ending up in positions out of reach for the player.
The 'count ' keeps track of how many coins the player has collected.
This attribute could also be placed with the player (as a player.coins attribute), but for now I've placed it here.
The pygame.transform.scale() takes a surface and changes its size and then outputs the result in a new surface. Here we make a mini-icon of the coin, for later use in the draw() method.

The hit() method check if player hits the coin or the enemy hits the coin; in both situations the coin will spawn at random again.

Also here I have an open end: If the number of coins collected surpass a certain threshold, something should happen. In the next version I'll give an example of this.

This almost finalises the first version of the game. Before showing the full game code, I only need to comment on line 182:

p_rects = [p.rect for p in platforms]

Here a list is generated by list comprehension. List Comprehension is outside the scope of this tutorial. In this case the rect attribute is extracted from all instances of Platform, and placed in a new list, 'p_rects'.

The full code of this first version of the game is here (with only few comments or docstrings; comments are covered by this tutorial):