image

(Остальные части туториала: первая, третья, четвёртая, пятая.)

Во второй из пяти частей туториала, посвящённого созданию игр с помощью Python 3 и Pygame, мы рассмотрим класс TextObject, используемый для рендеринга текста на экране. Мы создадим основное окно, в том числе и фоновое изображение, а затем научимся отрисовывать объекты: кирпичи, мяч и ракетку.

Класс TextObject


Класс TextObject предназначен для отображения текста на экране. Можно сделать вывод, что с точки зрения дизайна он должен быть подклассом класса GameObject, потому что тоже является визуальным объектом и его тоже иногда нужно двигать. Но я не хотел вводить глубокую иерархию классов, при которой весь отображаемый Breakout текст оставался на экране неизменным.

Класс TextObject создаёт объект шрифта. Он рендерит текст на отдельную текстовую поверхность, которая затем копируется (рендерится) на основную поверхность. Интересный аспект TextObject заключается в том, что у него нет какого-то фиксированного текста. Он получает функцию text_func(), вызываемую каждый раз, когда он рендерится.

Это позволяет нам обновлять отображение жизней и очков в Breakout, просто создав функцию, возвращающую текущие жизни и очки, а не отслеживать то, какие текстовые объекты отображают очки и жизни и обновлять их текст при каждом их изменении. Это удобный трюк из функционального программирования, и в крупных играх он позволяет поддерживать удобство и аккуратность программы.

import pygame


class TextObject:
    def __init__(self, 
                 x, 
                 y, 
                 text_func, 
                 color, 
                 font_name, 
                 font_size):
        self.pos = (x, y)
        self.text_func = text_func
        self.color = color
        self.font = pygame.font.SysFont(font_name, font_size)
        self.bounds = self.get_surface(text_func())

    def draw(self, surface, centralized=False):
        text_surface, self.bounds =             self.get_surface(self.text_func())
        if centralized:
            pos = (self.pos[0] - self.bounds.width // 2,
                   self.pos[1])
        else:
            pos = self.pos
        surface.blit(text_surface, pos)

    def get_surface(self, text):
        text_surface = self.font.render(text, 
                                        False, 
                                        self.color)
        return text_surface, text_surface.get_rect()

    def update(self):
        pass

Создание основного окна


Игры на Pygame выполняются в окнах. Можно даже сделать так, чтобы они выполнялись в полноэкранном режиме. Сейчас я расскажу, как отобразить пустое окно Pygame. Вы увидите многие элементы, которые мы обсуждали ранее. Сначала вызывается init() Pygame, а затем создаются основная поверхность рисования и таймер.

Затем выполняется основной цикл, который постоянно заполняет экран однотонным серым цветом и вызывает метод таймера tick() с частотой кадров.

import pygame

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

while True:
    screen.fill((192, 192, 192))
    pygame.display.update()
    clock.tick(60)

Использование фонового изображения


Обычно однотонный цвет фона выглядит не очень интересно. Pygame очень хорошо работает с изображениями. Для Breakout я нашёл любопытную фотографию настоящего космоса, сделанную НАСА. Код очень прост. Сначала он перед основным циклом загружает фоновое изображение с помощью функции pygame.image.load(). Затем вместо того, чтобы заливать экран цветом, он выполняет блиттинг (копирование битов) изображения на экран в позицию (0,0). В результате на экране отображается изображение.

import pygame

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
background_image = pygame.image.load('images/background.jpg')

while True:
    screen.blit(background_image, (0, 0))
    pygame.display.update()
    clock.tick(60)


Отрисовка фигур


Pygame может рисовать всё, что угодно. В модуле pygame.draw есть функции для отрисовки следующих фигур:

  • прямоугольника (rect)
  • многоугольника (polygon)
  • круга (circle)
  • эллипса (ellipse)
  • дуги (arc)
  • отрезка (line)
  • отрезков (lines)
  • сглаженного отрезка (anti-aliased line)
  • сглаженных отрезков (anti-aliased lines)

Все объекты в Breakout (за исключением текста) являются простыми фигурами. Давайте изучим метод draw() различных объектов Breakout.

Отрисовка кирпичей


Кирпичи — это просто прямоугольники. В Pygame есть функция pygame.draw.rect(), получающая поверхность, цвет и объект Rect (левую и верхнюю координату, ширину и высоту) и рендерящая прямоугольник. Если дополнительный параметр ширины больше нуля, то он отрисовывает контур. Если ширина равна нулю (значение по умолчанию), то рисует сплошной прямоугольник.

Стоит заметить, что класс Brick является подклассом GameObject и получает все его свойства, но также имеет и цвет, который обрабатывает самостоятельно (потому что могут существовать игровые объекты, имеющие несколько цветов). Поле special_effect мы пока рассматривать не будем.

import pygame

from game_object import GameObject


class Brick(GameObject):
    def __init__(self, x, y, w, h, color, special_effect=None):
        GameObject.__init__(self, x, y, w, h)
        self.color = color
        self.special_effect = special_effect

    def draw(self, surface):
        pygame.draw.rect(surface, self.color, self.bounds)

Отрисовка мяча


Мяч в Breakout — это просто круг. В Pygame есть функция pygame.draw.circle(), получающая цвет, центр, радиус и дополнительный параметр ширины, который по умолчанию равен нулю. Как и в функции pygame.draw.rect(), если ширина равна нулю, то отрисовывается сплошной круг. Ball тоже является подклассом GameObject.

Так как мяч всегда движется (в отличие от кирпичей), он также имеет скорость, которая передаётся для обработки базовому классу GameObject. Класс Ball имеет небольшое отличие — параметры x и y обозначают его центр, а параметры x и y, передаваемые базовому классу GameObject являются верхним левым углом ограничивающего прямоугольника. Чтобы преобразовать центр в верхний левый угол, достаточно вычесть радиус.

import pygame

from game_object import GameObject


class Ball(GameObject):
    def __init__(self, x, y, r, color, speed):
        GameObject.__init__(self, 
                            x - r, 
                            y - r, 
                            r * 2, 
                            r * 2, 
                            speed)
        self.radius = r
        self.diameter = r * 2
        self.color = color

    def draw(self, surface):
        pygame.draw.circle(surface, 
                           self.color, 
                           self.center, 
                           self.radius)

Отрисовка ракетки


Ракетка — это ещё один прямоугольник, двигающийся влево и вправо в ответ на нажатия игроком клавиш со стрелками. Это значит, что положение ракетки в разных кадрах может отличаться, но в процессе отрисовки это просто прямоугольник, который должен рендериться в текущей позиции, какой бы она ни была. Вот как выглядит соответствующий код:

import pygame

import config as c
from game_object import GameObject


class Paddle(GameObject):
    def __init__(self, x, y, w, h, color, offset):
        GameObject.__init__(self, x, y, w, h)
        self.color = color
        self.offset = offset
        self.moving_left = False
        self.moving_right = False

    def draw(self, surface):
        pygame.draw.rect(surface, self.color, self.bounds)

Заключение


В этой части мы узнали о классе TextObject и о том, как рендерить текст на экране. Также мы познакомились с тем, как рисовать объекты: кирпичи, мяч и ракетку.

В третьей части мы узнаем, как работает обработка событий и как Pygame позволяет нам перехватывать события и реагировать на них (нажатия клавиш, движение мыши и нажатия кнопок мыши). Также мы рассмотрим такие элементы игрового процесса, как движение мяча, задание его скорости и перемещение ракетки.

Комментарии (5)


  1. BOOTor
    23.01.2018 21:24

    Ссылку бы на первую статью добавить — было бы замечательно.


    1. PatientZero Автор
      23.01.2018 21:44

      Посмотрите пока в моих постах, позже я добавлю ссылки и оглавление.


      1. vin2809
        24.01.2018 09:12

        Спасибо за публикацию!

        А для тех, кто интересуется началом — часть 1.


  1. Terras
    24.01.2018 02:48

    А зачем? Это все же не имеет ни малейшего коммерческого смысла. На питоне из более менее адекватного — это визуальные новеллы на ren.py — остальное уровень пет-проектов.


    1. PatientZero Автор
      24.01.2018 12:22

      Во-первых, не все стремятся к тому, чтобы игра имела коммерческий успех. Во-вторых, отсутствие коммерчески успешных игр на ЯП/движке/фреймворке не означает, что на нём нельзя сделать успешную игру. В-третьих, на Python с использованием Pygame создан, например, варгейм Unity of Command. 100 тысяч проданных копий по 20 долларов — вполне себе успех, КМК.