Hello everyone,
Last couple of times when we’ve talked about Game Development with Python or about GameDev for Android Using Python, we’ve received great responce from our and other related communities.
So we begin our small series of articles dedicated to more practical usage of Python in GameDev.
Today we will build a very simple platform game using Python game framework PyGame, and in the next article, we will try to port it to Android!
So what's a "platform game" game exactly? According to Wiki it's a video game which involves guiding an avatar to jump between suspended platforms, over obstacles, or both to advance the game. These challenges are known as jumping puzzles or freerunning. The player controls the jumps to avoid letting the avatar fall from platforms or miss necessary jumps. "
Classical example of platform game is Nintendo’s famous “Mario”, so we wil try to build something similar to it.
Let's start with the basics:
Installation
You can find installation instructions on the official PyGame page as well as prebuilt executables for whole variety of Operating Systems.
It wasn't that hard, isn't it?
Getting Started
So now we have PyGame installed, let’s start with importing it into our future project! Simply start your favourite IDE (check out previous article about IDE's for Python) and paste this code into newly created main.py, see details in comment section of the code!
Code:
# Importing PyGame library
import pygame
from pygame import *
#Declaring variables
window_width = 800 #Width of game windows
window_height = 640 # height
screen = (window_width, window_height) #Grouping W and H into a single variable
bg_color = "#FFFFFF" #setting background color
def main(): #main function start
pygame.init() # PyGame initialization (all pygame projects requires this)
window = pygame.display.set_mode(screen) #Let's create a window
pygame.display.set_caption("Platform Game") # Windows title
bg = Surface((window_width,window_height)) # Creating a visible surface to use as background
bg.fill(Color(bg_color)) #and fill it with bg_color color (white)
while True: # Main Game cycle, ‘True' means it will run without stop forever
for event in pygame.event.get(): # Handling quit event
if event.type == QUIT:
raise SystemExit("QUIT") # raise SystemExit, "QUIT" for Py 2.7
window.blit(bg, (0,0)) # We need to redraw screen each cycle iteration
pygame.display.update() # drawing everything after each iteration
if __name__ == "__main__":
main() #executing main function
Woah, it’s starting to get hot in here, isn’t it?
Keep in mind: The game will be launched in the cycle (while True), each iteration it is necessary to redraw the background, platforms, monsters, messages, etc. It is important to note that the drawing is a sequence, ie if the first draw of the hero, and then fill the background, the character will not be seen.
Once you will run above code, you will see a window filled with white color
Level construction
Let's draw a level on existing surface!
How we will do this? We’re choosing the easiest way, we will create a two-dimensional array of m by n. Each cell (m, n) will be a rectangle. Rectangle can contain something or may be empty.
Let's add some more constants:
platform_width = 32
platform_height = 32
platform_color = "#000000"
And a level structure to the 'main' function :
map = [
"-------------------------",
" -",
"- -",
"- -",
"- -",
"- -",
"- -",
"- -",
"- -",
"- -",
"- -",
"- -",
"- -",
"- -",
"- -",
"- -",
"- -",
"- -",
"- -",
"-------------------------"]
And to the main cycle we're adding a level parser, to convert above drawing into playable level
x=y=0 # coordinates
for row in map: # whole row
for col in row: # each symbol
if col == "-":
#creating a block, filling it with color and drawing it
platform = Surface((platform_width,platform_height))
platform.fill(Color(platform_color))
screen.blit(pf,(x,y))
x += platform_width #positioning blocks width
y += platform_height #same for height
x = 0 #on each row, start from 0
So what we're doing here is we sorting out the two-dimensional level array, and if there is a symbol "-", then the coordinates (x * platform_width, y * platform_height), where x, y - is the index in the array of level.
Character
Bold blocks on the background are boring. We need our character that will run and jump on the platforms we’ve just built.
So it's time for some object oriented programming! We will create a class for our hero! For convenience, we will keep our character as a separate file player.py
from pygame import *
move_speed = 7
width = 22
height = 32
color = "#111111"
class Player(sprite.Sprite):
def __init__(self, x, y):
sprite.Sprite.__init__(self)
self.xvel = 0 # movement speed, 0 is standing still position
self.startX = x # initial charecter spawn position
self.startY = y # same for y spawn position
self.image = Surface((width,height))
self.image.fill(Color(color)) # setting charecter color
self.rect = Rect(x, y, width, height) # rectangular charecter
def update(self, left, right):
if left:
self.xvel = -MOVE_SPEED # left = x- n
if right:
self.xvel = MOVE_SPEED # right = x + n
if not(left or right): # standing still, when not walking
self.xvel = 0
self.rect.x += self.xvel # moving our position on xvel
def draw(self, screen): # drawing charecter
screen.blit(self.image, (self.rect.x,self.rect.y))
What so interesting and what to look for in this code?
Let's start with the fact that we create a new class inheriting from another class pygame.sprite.Sprite, thereby we inherit all the characteristics of a sprite. Sprite, according to wiki is a moving the bitmap. It has a number of useful methods and properties.
In self.rect = Rect(x, y, width, height) line, we are creating borders of our rectangular character. Using this rectangle we will not only move hero, but also will check(later) it on a collision with other objects.
update(self, left, right)) method is used to describe the behavior of the object. It overrides the parent update(* args) → None. It may be called in groups of sprites.
draw(self, screen) method is used to display the character on the screen. We will lated remove this method to use more convinient way to display the character.
Before defining the level, let's add our hero and variables to move it.
hero = Player(55,55) # creating a hero on x and y coordinates
left = right = False # standing still by default :)
Also, let's add some code to move our charecter at the ‘event' section of our code.
if e.type == KEYDOWN and e.key == K_LEFT:
left = True
if e.type == KEYDOWN and e.key == K_RIGHT:
right = True
if e.type == KEYUP and e.key == K_RIGHT:
right = False
if e.type == KEYUP and e.key == K_LEFT:
left = False
Everything is pretty much self explanatory in this code, if not, please, ask in the comment section
Now, it's time to finally move and draw our charecter! According to drawing rules, you'll need to add this after backgroud and platform drawing
hero.update(left, right) # movement
hero.draw(screen) # charecter drawing
But, if you launch your code, you'll see that our hero moves too quickly, let's add a restriction in the number of frames per second. To do this, after determining the level add a timer
timer = pygame.time.Clock()
And to make it work, we will add it's parameters to our main cycle
timer.tick(60)
As you may see, if you launch your code, your hero is stuck in the air, to fix this, let's add some gravity and ability to jump! It's kinda boring, but result is totally worth it!
To do so, let's open our old friend player.py and add a little magic there!
What in real life makes jumps possible? Of course it's gravity!. Now converting those values into the code,:
jump_power = 10
gravity = 0.35 # Force, that will drag player down
And voi-là, we now have gravity in our game!
Now setting vertical movement speed and a check if we're standing on the floor, cause we can jump only from the floor. All this goes to init method!
self.yvel = 0
self.GroundCheck = False
Now let's add new argument to our existing method
def update(self, left, right, up):
if up:
if self.GroundCheck: # Jump, only when on floor
self.yvel = -jump_power
Now, right before self.rect.x += self.xvel string, add:
if not self.GroundCheck:
self.yvel += gravity
self.GroundCheck = False; # We don't know when we're on the ground
self.rect.y += self.yvel
Now, let's forbid our player to fly! After left = right = False line, let's add:
up = false
Again, this code is pretty much self explanatory.
To end flying question, let's add some more event checks!
if e.type == KEYDOWN and e.key == K_UP:
up = True
if e.type == KEYUP and e.key == K_UP:
up = False
And to ‘update' method, add argument named ‘up'
Final Chapter: Jumping, Movement
How do you know that we are on the ground or other hard surface? The answer is obvious - to check if rectangle(hero) crosses with platforms!
Firstly, we will need to change how platforms are generating ;)
To make our code more readable and clear, let's split each important part of the code to a different files, same as we did with player.py, but now, create platforms.py and move platform creation code there:
platform_width = 32
platform_height = 32
platform_color = "#000000"
Then, create class, that inherits from from pygame.sprite.Sprite
class Platform (sprite.Sprite):
def __init __ (self, x, y):
sprite.Sprite .__ init __ (self)
self.image = Surface ((platform_width, platform_height))
self.image.fill (Color (platform_color))
self.rect = Rect (x, y, platform_width, platform_height)
Nothing new here, i think, so let's move on
In addition, we will need to change some parts of main file. Just right befor level array let's add:
entities = pygame.sprite.Group () # All objects
platforms = [] # Platforms, that we will bounce of
entities.add(charecter)
We will will use Sprites Group entities to display all the elements of this group. An array of platforms will be used to test for intersection with the platform.
Remember this tricky part?
if col == "-":
pf = Surface((platform_width, platform_height))
pf.fill(Color (platform_color))
screen.blit(pf, (x, y))
Now, we will replace it with more readable
if col == "-":
platform = Platform(x, y)
entities.add(platform)
platforms.append(platform)
What we did is we created an instance of Platform class, and added it to the ‘entities' group of sprites and ‘platforms' array.'entities' part is to ease up blocks display logic, and ‘platforms' part is to check intersection with the player.
Next, move all of the level generation code from the cycle and replace charecter display part
charecter.draw (screen)
with
entities.draw (screen) # display everything
If you'll run the code, you will see that nothing changed, becase we're not checking for intersections with our charecter! Let's fix it!
Getting back to player.py. We can now remove draw method, wohoo! But we will need new method, boo!, let's name it ‘collide'
def collide (self, xvel, yvel, platforms):
for p in platforms:
if sprite.collide_rect (self, p): # if there's collision with player
if xvel> 0: # if moves right
self.rect.right = p.rect.left # Not move to the right
if xvel <0 # same for left
self.rect.left = p.rect.right #
if yvel> 0: # if falling down
self.rect.bottom = p.rect.top # do not fall down
self.GroundCheck = True # if standing on something solid
self.yvel = 0 # falling velocity stops
if yvel <0 # if moves up
self.rect.top = p.rect.bottom # do not move up
self.yvel = 0 # jump energy disappears
In this method, we're checking the intersection of the coordinates of the hero and platforms, if any, above described the logic activates.
Well, to make things work, we will need to call this method. Change the number of arguments for update method once again, it now should look like this:
update(self,left,right,up,platforms)
Tip: Do not forget to change method call in the main file.
And some finishing touches:
self.rect.y += self.yvel
self.rect.x += self.xvel
Replace with:
self.rect.y += self.yvel
self.collide(0, self.yvel, platforms)
self.rect.x += self.xvel # transfer their position on xvel
self.collide(self.xvel, 0, platforms)
If the hero moved vertically, we check the intersection of the vertical, moved horizontally and again, checked at the intersection of the horizontal. Let's run our game and see what we've acieved!
P.S
That’s all folks, it was a tough one, but it’s your first game!
In the next part of this article, we will try to add android support and maybe some graphics!
Write your questions and feedback in the comment section, and CheckiO team will do the best we can to help you!
