Pre-Order Kelboy 2.0 Kit

Ayuda al proyecto y aprovéchate temporalmente del código de descuento GBZEROKELBOY del 10% en pre-orders
kelboy research

Learn to develop videogames

Tutorial describes the parts you must know to make portable video games in python thanks to the pygame library

Advance
8


Introduction

Our intention is to pursue portability and the ability to program in "free" languages ​​the basics of programming a multiplatform video game that continues to function over the years.

The capacity of languages ​​as powerful as python and libraries as versatile as pygame that are currently being developed, improved and maintained, make programming a video game that works on any platform already more than a temporary lesson that will work at the time of publication but then it will cease to exist and belong to the memory.

Pygame allows us to create graphic interfaces and effects that are good enough and agile that will allow us to learn the fundamentals and programming of video games step by step, using the advantages of a programming language interpreted in our favor.

With this article what you are looking for is to progressively learn to generate your own portable video games in a high-level language such as python thanks to the pygame library


Preparing the ground

Installation and configuration

In this manual we support programming on Atom, in case you do not know it is an editor that becomes a free and very powerful alternative to face Sublime Text.

It goes without saying that you know how to install python and pip, so you can easily install pygame:

pip install pygame

The standard way to make our source code have the dependency installer that we need is to generate a requirements.txt file, with the content:

pygame

and the correct thing is to specify the version of the dependency (if you want to add == version_dependency).

Structure

Now we have to structure our project. Basically what we have to do is create a start file and a series of folders to put the logic and resources that we are going to use in our video game.

The structure we will choose will be:

  • main.py (main archive)
  • requirements.txt (dependency files to install with pip install -r requirements.txt)
  • core folder (where will the python resources go)
  • resources folder (where will static resources like textures, music, sound effects, fonts ...)

Lesson 01 - Home Screen

Work material

We are going to build an easy to understand complete example to complete this first part of how to make a video game.

At this time the structure that we are going to use will be contained in our main.py, for this we must take into account the structure that we must set up, the tasks that we must perform and in what order.

  • Estructura del programa a grandes rasgos:
    • main.py (con la lógica)
      • dependencias
      • lógica
    • resources (archivos adjuntos a nuestro programa)
      • images
        • background
        • selector
      • music
        • init

Definiendo la lógica

Nuestra clase main debe de tener la lógica necesaria para:

  1. Inicializar
  2. Manejar eventos
  3. Pintar en función de los eventos

Diferenciar funcionalidades para saber qué partes van en qué zona

Nuestro programa se basa en la librería pygame, en esta parte debemos de configurar las variables globales que vayamos a usar y arrancar los componentes sobre los que vamos a trabajar.

A grandes rasgos tendremos en cuenta lo siguiente:

Inicializar la librería pygame:

pygame.init()

Inicializar el sonido:

pygame.mixer.init()

Inicializar el reloj (que se encargará de dibujar en el ratio de FPS en el bucle de la lógica para no saturar el procesador)

pygame.time.Clock()

Inicializamos la pantalla (surface) donde vamos a pintar el programa:

screen = pygame.display.set_mode([RESOLUTION_X,RESOLUTION_Y])

Una vez tenido en cuenta que estas llamadas deben de estar en la parte de inicialización pasaremos a definir cómo se maneja el punto 2, los eventos:

while notExit:
    for event in pygame.event.get():
        logger.debug("some event")
    #some logic here related or not with events

Dentro del mismo while y después de programar la lógica debe de ir la parte que dibuja en función del resultado de la lógica anteriormente programada:

Pintar sobre un buffer:

screen.blit(buffer, graphicElement)

Refrescar el buffer (sólo los cambios):

pygame.display.flip()

Avanzar frame en función de los FPS:

clock.tick(FPS)

Con estos elementos debemos de tener clara la principal estructura para poder continuar con la programación de nuestra lógica (main.py).


main.py

We are going to build a complete example of a home screen, and for this the logic must paint everything necessary.

Dependencies

The most important of all:

import pygame

We are going to build a log to indicate what is happening at each moment. A programmer needs to know where their program stopped and whether or not it entered certain sites, so we recommend the introduction of a logger.

To do this, import the necessary dependencies:

import logging
import os

And initialize the logger:

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
handler = logging.FileHandler(os.path.join(os.getcwd(), "log.txt"))
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

We have opted for a complete logger, but if you do not want to have a logger that is too detailed or saved in a file, you can skip it from the second line.

Our calls according to the type of priority should be similar to:

logger.info("he aquí mi traza")

Logic

Python is an interpreted language, so it runs as a script, line by line.

The correct thing in this type of programming is to indicate that we are the main and run from there:

if __name__=='__main__':

    logger.info("lesson 1 logic")

Initialization

Taking this part into account we will start with the initializations, both of the variables, the resources and the pygame routines necessary for our logic, along with some trace that indicates that everything has gone well and we will program it after the previous definition (main):

logger.info("init lesson 1")
SCREENX = 800
SCREENY = 600

BACKGROUND_PATH = os.path.join('resources/images','background.png')
MUSHROOM_PATH = os.path.join('resources/images','mushroom.png')
BACKGROUND_MUSIC_PATH = os.path.join('resources/music','init.ogg')

menuX = 225
#this part will decide which coordinate (just Y axe)
lastMenuY = 405
firstMenuY = 360

fps = 25 # frame rate
clock = pygame.time.Clock()

pygame.init()
main = True # flag to control program exit without breaks or exits

screen = pygame.display.set_mode([SCREENX,SCREENY])
backdrop = pygame.image.load(BACKGROUND_PATH).convert()
mushroom = pygame.image.load(MUSHROOM_PATH).convert()
backScreen = screen.get_rect()

#init music
logger.debug("load music")
pygame.mixer.init()
pygame.mixer.music.load(BACKGROUND_MUSIC_PATH)
pygame.mixer.music.play()

#Main loop 
menuY = firstMenuY
refresh = False

While loop

The while loop indicates the logic to execute in our program while the main thread is still open.

Many programs will find them with a "while True" or a "for (;;)" that what they do is execute an infinite loop until the logic inside breaks (with a break).

To be more structured we will use a control or sentinel variable, which will change state when we want to end the program logic in an orderly manner (structured programming):

while main == True:

    #some logic here

Within this loop we will find the other two important points.

Logic and event handling

With pygame the capture of events is very simple, it will be quickly identified with the lines that contain:

for event in pygame.event.get():
    logger.debug("manage event")

This line "pygame.event.get ()" will be kept waiting until an event of any initialized input peripheral occurs, for the moment not having initialized any extra input peripheral we will have the standard ones (keyboard and mouse).

To control them we must differentiate the type of event:

if event.type == pygame.QUIT:
    main = False
elif event.type == pygame.KEYDOWN:
    logger.info("some logic related to each a pushed key")
elif event.type == pygame.KEYUP:
    logger.info("some new logic related to a released key")

Depending on which key we press, we must control within each part what logic to do, for example:

if event.key == pygame.K_LEFT or event.key == ord('a'):
    menuY = lastMenuY if menuY==firstMenuY else firstMenuY
    refresh = True

With this we will be able to define what is necessary for the next part

Paint according to defined logic

Once the sentinels or control variables that we have managed in the logic are updated, we will proceed to paint:

Parts only refreshable in case of new events that have to be repainted, as in this case the background.

#redraw background if necessary to clean screen
if refresh:
    logger.debug("refresh backdrop!")
    backdrop = pygame.image.load(BACKGROUND_PATH).convert() #refresh with background image
    #draw icon
    backdrop.blit(mushroom,(menuX,menuY))
    refresh = False

And fixed parts that are painted in each frame:

#draw part
screen.blit(backdrop, backScreen)
#refresh changes
pygame.display.flip()
#flip next frame with defined frequency (f.i. fps = 25)
clock.tick(fps)

For this explanatory example it is enough but if you want to improve performance the first two lines should be moved within the condition with refresh.

Once everything is understood we should have a main.py file similar to this:

import os,sys
import pygame

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
handler = logging.FileHandler(os.path.join(os.getcwd(), "log.txt"))
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)


if __name__=='__main__':

    logger.info("init lesson 1")
    SCREENX = 800
    SCREENY = 600

    BACKGROUND_PATH = os.path.join('resources/images','background.png')
    MUSHROOM_PATH = os.path.join('resources/images','mushroom.png')
    BACKGROUND_MUSIC_PATH = os.path.join('resources/music','init.ogg')

    menuX = 225
    #this part will decide which coordinate (just Y axe)
    lastMenuY = 405
    firstMenuY = 360

    fps = 25 # frame rate
    clock = pygame.time.Clock()

    pygame.init()
    main = True # flag to control program exit without breaks or exits

    screen = pygame.display.set_mode([SCREENX,SCREENY])
    backdrop = pygame.image.load(BACKGROUND_PATH).convert()
    mushroom = pygame.image.load(MUSHROOM_PATH).convert()
    backScreen = screen.get_rect()

    #init music
    logger.debug("load music")
    pygame.mixer.init()
    pygame.mixer.music.load(BACKGROUND_MUSIC_PATH)
    pygame.mixer.music.play()

    #Main loop
    menuY = firstMenuY
    refresh = True
    while main == True:

        #manage events
        for event in pygame.event.get():
            logger.debug("some event happened")
            if event.type == pygame.QUIT:
                main = False

            #push keyboard button event
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT or event.key == ord('a'):
                    menuY = lastMenuY if menuY==firstMenuY else firstMenuY
                    refresh = True
                elif event.key == pygame.K_RIGHT or event.key == ord('d'):
                    menuY = lastMenuY if menuY==firstMenuY else firstMenuY
                    refresh = True
                elif event.key == pygame.K_UP or event.key == ord('w'):
                    menuY = lastMenuY if menuY==firstMenuY else firstMenuY
                    refresh = True
                elif event.key == pygame.K_DOWN or event.key == ord('s'):
                    menuY = lastMenuY if menuY==firstMenuY else firstMenuY
                    refresh = True

            # release key q for exit
            elif event.type == pygame.KEYUP:
                if event.key == ord('q'): #exit
                    main = False

        #redraw background if necessary to clean screen
        if refresh:
            logger.debug("refresh backdrop!")
            backdrop = pygame.image.load(BACKGROUND_PATH).convert() #refresh with background image
            backdrop.blit(mushroom,(menuX,menuY))
            refresh = False

        #draw part
        screen.blit(backdrop, backScreen)
        pygame.display.flip()
        clock.tick(fps)

    #finish part
    logger.info("end lesson 1")
    print("bye!")
    #pygame.quit()
    #sys.exit()


Outcome

The working code launches:

Image

We already leave you in your hand to play with this example until the next lesson.