📜 ⬆️ ⬇️

Making games in Python 3 and Pygame: Part 1

Many developers come to software development because they want to create games. Not everyone can become professional game developers, but anyone can create their own games out of interest (and perhaps with a profit). In this five-part tutorial, I’ll tell you how to create two-dimensional single-player games using Python 3 and the wonderful PyGame framework.

(The remaining parts of the tutorial: the second , third , fourth , fifth .)

We will create a version of the classic Breakout game. Having mastered this tutorial, you will clearly understand what is necessary to create a game, get acquainted with the possibilities of Pygame and write your own example of the game.
')
We implement the following features and capabilities:


Do not expect that the game will be very beautiful graphically. I'm a programmer, not an artist, I'm more interested in the aesthetics of the code. The design created by me can unpleasantly surprise. On the other hand, you will have almost unlimited possibilities to improve the graphics of this version of Breakout. If you dare to repeat after me, look at the screenshot:


The ready source code is posted here .

Brief introduction to game programming


The main thing in games is the movement of pixels on the screen and the noise generated. Almost all video games have these elements. In this article we will not consider client-server and multiplayer games, which require a lot of network programming.

Main loop


The main loop of the game is executed and refreshes the screen at fixed intervals. They are called frame rates and determine the smoothness of movement. Usually games update the screen 30-60 times per second. If the frequency is less, it will seem that objects on the screen are twitching.

Inside the main loop there are three main operations: event handling, updating the game state and drawing the current state on the screen.

Event handling


Events in the game consist of everything that happens outside the control of the code of the game, but refers to the execution of the game. For example, if a player presses the left arrow key in Breakout, then the game needs to move the racket to the left. Standard events are keystrokes (and release) of keys, mouse movement, mouse button presses (especially in the menu), and timer events (for example, the effect of a special effect can last 10 seconds).

Status update


The heart of any game is its state: everything that it tracks and draws on the screen. In the case of Breakout, the state includes the position of all bricks, the position and speed of the ball, the position of the racket, as well as life and points.

There is also an auxiliary state that allows you to control the game:


Drawing


The game needs to display its state on the screen, including drawing geometric shapes, images and text.

Game physics


Most games simulate the physical environment. In Breakout, the ball rebounds from objects and has a very approximate system of solid state physics (if you can call it that).

In more complex games, more sophisticated and realistic physical systems can be used (especially in 3D games). It is also worth noting that in some games, for example, in card games, there is almost no physics, and this is completely normal.

AI (artificial intelligence)


In many games we fight computer opponents, or they have enemies trying to kill us. Often they behave in the game world as if they have a mind.

For example, enemies chase the player and know his location. There is no AI in Breakout. The player fights with cold and hard bricks. However, AI in games is often very simple and just follows simple (or complex) rules that provide pseudo-prudent results.

Sound reproduction


Sound reproduction is another important aspect of games. In general, there are two types of sound: background music and sound effects. In Breakout, I implement only sound effects that are played on various events.

Background music is just music that constantly plays in the background. In some games, it is not used, and in some it changes at each level.

Life, points and levels


In most games, the player has a certain number of lives, and when they end, the game ends. Also, there are often glasses in games that allow you to understand how well we play and which give you motivation for self-improvement or just show off your friends to your records. In many games there are levels that are either completely different, or gradually increase the difficulty.

Meet Pygame


Before you start implementing the game, let's learn a little about Pygame, which will take over most of the work.

What is Pygame?


Pygame is a Python framework for programming games. It is created on top of the SDL and has everything you need:


Install Pygame


Enter pip install pygame to install the framework. If you need something else, then follow the instructions in the Getting Started section of the project Wiki. If you, like me, macOS Sierra, you may have problems. I managed to install Pygame without difficulty, and the code works fine, but the game window never appears.

This will be a serious obstacle when you start the game. In the end I had to run it on Windows inside a VirtualBox VM. I hope that by the time of reading this article the problem will be solved.

Game architecture


Games need to manage a bunch of information and perform almost the same operations with many objects. Breakout is a small game, but trying to manage everything in one file may be too tedious. Therefore, I decided to create a file structure and architecture that is suitable for much larger games.

Folder and file structure


  P── Pipfile
 P── Pipfile.lock
 RE── README.md
 Ball── ball.py
 Break── breakout.py
 Brick── brick.py
 Button── button.py
 Colors── colors.py
 Config── config.py
 Game── game.py
 Game── game_object.py
 ├── images
 │ └── background.jpg
 Pad── paddle.py
 Sound── sound_effects
 │ ├── brick_hit.wav
 │ ├── effect_done.wav
 │ ├── level_complete.wav
 │ └── paddle_hit.wav
 Text── text_object.py 

Pipfile and Pipfile.lock is a modern way to manage dependencies in Python. The images folder contains images used by the game (in our version there will be only a background image), and in the sound_effects directory folder there are short sound clips used (as you might guess) as sound effects.

The ball.py, paddle.py, and brick.py files contain code related to each of these Breakout objects. I will discuss them in more detail in the following parts of the tutorial. The text_object.py file contains the code that displays text on the screen, and the background.py file contains the Breakout game logic.

However, there are several modules that create an arbitrary general-purpose “skeleton”. Classes defined in them can be used in other games based on Pygame.

Class GameObject


A GameObject is a visual object that knows how to render itself, maintain its boundaries, and move. In Pygame, there is a Sprite class that plays a similar role, but in this tutorial I want to show you how everything works at a low level, and not rely too actively on the finished magic. Here’s what the GameObject class looks like:

 from pygame.rect import Rect class GameObject: def __init__(self, x, y, w, h, speed=(0,0)): self.bounds = Rect(x, y, w, h) self.speed = speed @property def left(self): return self.bounds.left @property def right(self): return self.bounds.right @property def top(self): return self.bounds.top @property def bottom(self): return self.bounds.bottom @property def width(self): return self.bounds.width @property def height(self): return self.bounds.height @property def center(self): return self.bounds.center @property def centerx(self): return self.bounds.centerx @property def centery(self): return self.bounds.centery def draw(self, surface): pass def move(self, dx, dy): self.bounds = self.bounds.move(dx, dy) def update(self): if self.speed == [0, 0]: return self.move(*self.speed) 

GameObject is designed to be a base class for other objects. He directly reveals the many properties of his self.bounds rectangle, and in his update() method he moves the object according to his current speed. It does nothing in its draw() method, which must be redefined by subclasses.

Game class


The Game class is the core of the game. It is executed in the main loop. It has many useful features. Let's look at his method by method.

The __init__() method initializes Pygame itself, the font system, and the sound mixer. Three different calls are needed, because not every game on Pygame uses all the components, so you can control the subsystems that we use and initialize only the ones you need with the appropriate parameters. The method creates a background image, the main surface (on which everything is drawn) and the game timer with the correct frame rate.

The self.objects element stores all game objects that must be rendered and updated. Different handlers manage lists of handler functions that must be executed on certain events.

 import pygame import sys from collections import defaultdict class Game: def __init__(self, caption, width, height, back_image_filename, frame_rate): self.background_image = \ pygame.image.load(back_image_filename) self.frame_rate = frame_rate self.game_over = False self.objects = [] pygame.mixer.pre_init(44100, 16, 2, 4096) pygame.init() pygame.font.init() self.surface = pygame.display.set_mode((width, height)) pygame.display.set_caption(caption) self.clock = pygame.time.Clock() self.keydown_handlers = defaultdict(list) self.keyup_handlers = defaultdict(list) self.mouse_handlers = [] 

The update() and draw() methods are very simple. They bypass all managed game objects and call the methods corresponding to them. If two objects overlap each other on the screen, then the order of the list of objects determines which of them will be rendered first, and the rest will partially or completely overlap it.

  def update(self): for o in self.objects: o.update() def draw(self): for o in self.objects: o.draw(self.surface) 

The handle_events() method listens for events generated by Pygame, such as key and mouse events. For each event, it calls all the handler functions that must handle events of the appropriate types.

  def handle_events(self): for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() elif event.type == pygame.KEYDOWN: for handler in self.keydown_handlers[event.key]: handler(event.key) elif event.type == pygame.KEYUP: for handler in self.keydown_handlers[event.key]: handler(event.key) elif event.type in (pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION): for handler in self.mouse_handlers: handler(event.type, event.pos) 

Finally, the run() method executes the main loop. It runs until the game_over element is game_over to True. At each iteration, it renders the background image and calls the methods handle_events() , update() and draw() in order.

Then it updates the screen, that is, writes all the content that was rendered on the current iteration to the physical display. Last but not least, it calls the clock.tick() method to control when the next iteration is called.

  def run(self): while not self.game_over: self.surface.blit(self.background_image, (0, 0)) self.handle_events() self.update() self.draw() pygame.display.update() self.clock.tick(self.frame_rate) 

Conclusion


In this part, we learned the basics of game programming and all the components involved in creating games. We also looked at Pygame itself and learned how to install it. Finally, we plunged into the architecture of the game and studied the folder structure, the GameObject and Game classes.

In the second part, we will look at the TextObject class, which is used to render text on the screen. We will create the main window, including the background image, and then learn how to draw objects (ball and racket).

Source: https://habr.com/ru/post/347138/


All Articles