Игра на питоне готовый код. Я пишу текстовую игру на Python: первый прототип

  • Перевод

Многие разработчики приходят в разработку ПО, потому что хотят создавать игры. Не все могут стать профессиональными разработчиками игр, но любой может создавать собственные игры из интереса (а может быть, и с выгодой). В этом туториале, состоящем из пяти частей, я расскажу вам, как создавать двухмерные однопользовательские игры с помощью Python 3 и замечательного фреймворка PyGame.

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

Мы создадим версию классической игры Breakout . Освоив этот туториал, вы будете чётко понимать, что необходимо для создания игры, познакомитесь с возможностями Pygame и напишете собственный пример игры.

Мы реализуем следующие функции и возможности:

  • простые стандартные GameObject и TextObject
  • простой стандартный Game object
  • простая стандартная кнопка
  • файл конфигурации
  • обработка событий клавиатуры и мыши
  • кирпичи, ракетка и мяч
  • управление движением ракетки
  • обработка коллизий мяча с объектами игры
  • фоновое изображение
  • звуковые эффекты
  • расширяемая система спецэффектов
Не стоит ожидать , что игра будет очень красива графически. Я программист, а не художник, меня больше интересует эстетика кода. Созданный мной дизайн может неприятно удивить. С другой стороны, у вас будут почти неограниченные возможности по улучшению графики этой версии Breakout. Если вы отважитесь повторять за мной, посмотрите на скриншот:

Готовый исходный код выложен .

Краткое введение в программирование игр

Главное в играх - перемещение пикселей на экране и издаваемый шум. Почти во всех видеоиграх есть эти элементы. В этой статье мы не будем рассматривать клиент-серверные и многопользовательские игры, для которых требуется много сетевого программирования.

Основной цикл

Основной цикл (main loop) игры выполняется и обновляет экран через фиксированные интервалы времени. Они называются частотой кадров и определяют плавность перемещения. Обычно игры обновляют экран 30-60 раз в секунду. Если частота будет меньше, то покажется, что объекты на экране дёргаются.

Внутри основного цикла есть три основных операции: обработка событий, обновление состояния игры и отрисовка текущего состояния на экране.

Обработка событий

События в игре состоят из всего, что происходит за пределами управления кода игры, но относится к выполнению игры. Например, если в Breakout игрок нажимает клавишу «стрелка влево», то игре нужно переместить ракетку влево. Стандартными событиями являются нажатия (и отжатия) клавиш, движение мыши, нажатия кнопок мыши (особенно в меню) и события таймера (например, действие спецэффекта может длиться 10 секунд).

Обновление состояния

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

Существует также вспомогательное состояние, позволяющее управлять игрой:

  • Отображается ли сейчас меню?
  • Закончена ли игра?
  • Победил ли игрок?

Отрисовка

Игре нужно отображать своё состояние на экране, в том числе отрисовывать геометрические фигуры, изображения и текст.

Игровая физика

В большинстве игр симулируется физическое окружение. В Breakout мяч отскакивает от объектов и имеет очень приблизительную систему физики твёрдого тела (если это можно так назвать).

В более сложных играх могут использоваться более изощрённые и реалистичные физические системы (особенно в 3D-играх). Стоит также отметить, что в некоторых играх, например, в карточных, физики почти нет, и это совершенно нормально.

ИИ (искусственный интеллект)

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

Например, враги преследуют игрока и знают о его местоположении. В Breakout нет никакого ИИ. Игрок сражается с холодными и твёрдыми кирпичами. Однако ИИ в играх часто очень прост и всего лишь следует простым (или сложным) правилам, обеспечивающим псевдоразумные результаты.

Воспроизведение звука

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

Фоновая музыка - это просто музыка, постоянно играющая на фоне. В некоторых играх она не используется, а в некоторых меняется на каждом уровне.

Жизни, очки и уровни

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

Знакомство с Pygame

Прежде чем приступить к реализации игры, давайте немного узнаем о Pygame, который возьмёт на себя большую часть работы.

Что такое Pygame?

Pygame - это фреймворк языка Python для программирования игр. Он создан поверх SDL и обладает всем необходимым:
  • зрелостью
  • хорошим сообществом
  • открытым исходным кодом
  • кроссплатформенностью
  • качественной документацией
  • множеством примеров игр
  • простотой изучения

Установка Pygame

Введите pip install pygame , чтобы установить фреймворк. Если вам нужно что-то ещё, то следуйте инструкциям из раздела Getting Started в Wiki проекта. Если у вас, как и у меня, macOS Sierra, то могут возникнуть проблемы. Мне удалось установить Pygame без сложностей, и код работает отлично, но окно игры никогда не появляется.

Это станет серьёзным препятствием при запуске игры. В конце концов мне пришлось запускать её в Windows внутри VirtualBox VM. Надеюсь, ко времени прочтения этой статьи проблема будет решена.

Архитектура игры

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

Структура папок и файлов

├── Pipfile ├── Pipfile.lock ├── README.md ├── ball.py ├── breakout.py ├── brick.py ├── button.py ├── colors.py ├── config.py ├── game.py ├── game_object.py ├── images │ └── background.jpg ├── paddle.py ├── sound_effects │ ├── brick_hit.wav │ ├── effect_done.wav │ ├── level_complete.wav │ └── paddle_hit.wav └── text_object.py
Pipfile и Pipfile.lock - это современный способ управления зависимостями в Python. Папка images содержит изображения, используемые игрой (в нашей версии будет только фоновое изображение), а в папке sound_effects directory лежат короткие звуковые клипы, используемые (как можно догадаться) в качестве звуковых эффектов.

Файлы ball.py, paddle.py и brick.py содержат код, относящийся к каждому из этих объектов Breakout. Подробнее я рассмотрю их в следующих частях туториала. Файл text_object.py содержит код отображения текста на экране, а в файле background.py содержится игровая логика Breakout.

Однако существует несколько модулей, создающих произвольный «скелет» общего назначения. Определённые в них классы можно будет использовать в других играх на основе Pygame.

Класс GameObject

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

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 == : return self.move(*self.speed)
GameObject предназначен для того, чтобы быть базовым классом для других объектов. Он непосредственно раскрывает множество свойств его прямоугольника self.bounds, а в своём методе update() он перемещает объект в соответствии с его текущей скоростью. Он ничего не делает в своём методе draw() , который должен быть переопределён подклассами.

Класс Game

Класс Game - это ядро игры. Он выполняется в основном цикле. В нём есть множество полезных возможностей. Давайте разберём его метод за методом.

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

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

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 =
Методы update() и draw() очень просты. Они обходят все управляемые игровые объекты и вызывают соответствующие им методы. Если два объекта накладываются друг на друга на экране, то порядок списка объектов определяет, какой из них будет рендериться первым, а остальные будут частично или полностью его перекрывать.

Def update(self): for o in self.objects: o.update() def draw(self): for o in self.objects: o.draw(self.surface)
Метод handle_events() слушает события, генерируемые Pygame, такие как события клавиш и мыши. Для каждого события он вызывает все функции-обработчики, которые должны обрабатывать события соответствующих типов.

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: handler(event.key) elif event.type == pygame.KEYUP: for handler in self.keydown_handlers: handler(event.key) elif event.type in (pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION): for handler in self.mouse_handlers: handler(event.type, event.pos)
Наконец, метод run() выполняет основной цикл. Он выполняется до тех пор, пока элемент game_over не принимает значение True. В каждой итерации он рендерит фоновое изображение и вызывает по порядку методы handle_events() , update() и draw() .

Затем он обновляет экран, то есть записывает на физический дисплей всё содержимое, которое было отрендерено на текущей итерации. И последнее, но не менее важное - он вызывает метод clock.tick() для управления тем, когда будет вызвана следующая итерация.

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)

Заключение

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

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

Меня уже давно интересовала конвеевская игра «Жизнь», и вот наконец-то у меня дошли руки попробовать реализовать ее наиболее простым и элегантным способом. Если кто-не знает, что это за игра — на русской Википедии есть хорошее описание. Для тех, кто знает, но забыл, я постараюсь напомнить классические правила:

  • Время дискретно.
  • Игровое поле — плоскость, состоящая из клеток, у каждой клетки восемь соседей.
  • Клетки могут находится в двух состояниях: живом или мертвом. Совокупность живых клеток называется поколением.
  • В процессе игры каждая клетка может неограниченное количество раз переходить из одного состояния в другое.

Правила перехода тоже не отличаются сложностью:

  • Каждое следующее поколение рассчитывается на основе предыдущего.
  • Мертвая клетка оживает, если рядом с ней находится ровно 3 живые клетки.
  • Живая клетка продолжает жить, если рядом с ней находится 2 или 3 живые клетки.

Все идеально подходит для программного моделирования. К сожалению, код будет представлен только для последней версии — я в творческом порыве не удосужился начать сразу все складывать в git.

Начать лучше с самого прямолинейного способа. Он выглядит примерно так:

  1. Создается двумерный массив из булевых значений, где True бы означал наличие живой клетки, а False — мертвой.
  2. Генерируется новый массив.
  3. Для каждой клетки определяется число живых соседей и на основании этого определяется, будет ли она жить в новом поколении.
  4. Новый массив записывается на место старого.
  5. Происходит возврат к пункту 2.

Обычное в этом месте появляется потребность определять соседей клетки для реализации пункта 3. Поскольку сейчас мы имеем игровое поле, представленное в виде массива — есть риск выйти за его пределы. Есть несколько способов этого избежать:

  • Отбрасывать в функции все обращения к i, j < 0 и i >= n, j >= m (где m и n — размер строки и столбца соответственно).
  • Замкнуть поле на само себя — все обращения к клеткам из предыдущего пункта считать обращением к клетке с другой стороны поля. Т.е i < 0 будет обращением к m — i, i > m будет обращением к клетке m — i. Для j формулы аналогичны.
  • Оградить игровое поле со всех сторон элементами, обозначающими его границу. Такие элементы физически не являются ни мертвым, ни живыми, но при подсчете числа живых соседей считаются мертвыми.

Алгоритм хоть и прост для понимания, но имеет ряд недостатков:

  • Имеет линейное от размеров поля время работы.
  • Двойное потребление памяти (нужно хранить предыдущее поколение + новое).
  • Требует много кода для реализации.

Все это хочется взять и оптимизировать.

Наивная оптимизация

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

Кардинальная оптимизация с переосмыслением

Если продолжить развивать идею из предыдущего пункта (хранение списка координат живых клеток и их соседей), можно уйти и от моделирования «в лоб». В конце концов, состояние поля можно выразить просто списком живых клеток. Почему это возможно? Зная только координаты живых клеток мы можем воссоздать поле в изначальном состоянии. Хранить состояние соседних с живыми клеток нам тоже не требуется — поскольку состояний у клеток всего два (жива-мертва) — все, кто не живы, мертвы, а получить координаты соседей по координатам клетки тоже достаточно просто. Плюсы этого варианта достаточно заманчивы:

  • Экономия памяти. Нам не нужно хранить все поле целиком.
  • Время такое же, как и в предыдущем разделе.
  • Динамичность игрового поля. Размеры поля можно менять на протяжении игры в зависимости от расположения живых клеток.
  • Алгоритм очень легко параллелится. Можно создать несколько потоков, которые бы принимали список живых клеток и координаты рассматриваемой клетки. Результат работы потоков склеивается и получается новый список живых клеток.
  • Он красивый:)

Я не люблю везде засовывать ООП, поэтому мой вариант основан целиком на функциях. Все начинается с функции, генерирующей новое поколение клеток на основе списка живых клеток предыдущего поколения:

defnew_step(alive_cons):board=itertools.chain(*map(get_neighbors,alive_cons))new_board=set()returnlist(new_board)

Запутанная конструкция всего лишь сохраняет в переменной board список всех живых ячеек и их соседей. Теперь настала очередь функции is_alive_con.

defis_alive_con(con,alive_cons):alive_neighbors=calculate_alive_neighbors(con,alive_cons)if(alive_neighbors==3or(alive_neighbors==2andconinalive_cons)):returnTruereturnFalse

В следующей реализованы правила перехода клетки из одного состояния в другое.

defcalculate_alive_neighbors(con,alive_cons):returnlen(filter(lambdax:xinalive_cons,get_neighbors(con)))

Осталось рассмотреть самое важное — функцию, которая возвращает список всех соседей клетки на основе ее координат.

defget_neighbors(con):x,y=conneighbors=[(x+i,y+j)foriinxrange(-1,2)forjinxrange(-1,2)ifnoti==j==0]returnneighbors

Здесь нет необходимости проверять корректность координат клеток соседей — некорректные координаты (приводящие к обращению за пределы поля) и так не должны будут встретиться в списке живых. Корректность имеет смысл проверять уже после создания нового поколения. У меня для этого реализованы две функции — одна проверяет корректность координат клетки, другая делает нечто с «неправильными» — в этой реализации просто отбрасывает все некорректные значения:

defis_correct_con(size,con):x,y=conreturnall(0<=coord<=size-1forcoordin)defcorrect_cons(size,cons):returnfilter(lambdax:is_correct_con(size,x),cons)

Заключение

Подход, при котором сохраняются только координаты клеток с определенным состоянием может применяться не только для моделирования GoF, но и для других игр, реализуемых в терминах клеточных автоматов. Например, при реализации крестиков-ноликов можно сохранять только координаты клеток, в которых стоит крестик или нолик, а не моделировать все поле сразу. Это достаточно удобно, если нужно смоделировать игру по нестандартным правилам (доска не 3*3 и требуется поставить в ряд не 3 символа). Проверять, образовалась ли на доске выигрышная комбинация можно по аналогии с определением живости клетки GoF, но чуть сложнее:

  • Получить список соседей по диагоналям, вертикалям и горизонталям.
  • Последовательно подсчитать, сколько соседей имеет такое же состояние (крестик или нолик), как и исходная клетка.

Полную реализацию можно посмотреть на github

Меня все не оставляют мысли о реализации простого веб-интерфейса для красивой визуализации всего этого добра (анализировать вывод в консоль достаточно мучительно). Может быть, о нем я напишу позже.

  1. Курс по языку;
  2. Скачать;
  3. Установка;
  4. Установка.

Прежде чем изучать библиотеку PyGame стоит ознакомиться с самим языком программирования. После прохождения базового курса вам будет намного проще понимать синтаксис языка и у вас не возникнут дополнительные вопросы в ходе этого курса.

Язык Python имеет множество различных библиотек, которые делают его намного масштабнее и дополняют его всевозможным функционалом. Большинство скучных и повторяющихся действий, которые вы делаете в коде можно было бы выполнить намного проще, зная ту или иную библиотеку, коих действительно очень и очень много!

Библиотеку PyGame это специально разработанная библиотека для создания игр с интерфейсом на языке Python .

17. Делаем первую игру на Python: Поймай шарик

В ходе этого курса мы научимся создавать простенькие 2D игры на языке Питон (3 версии). Мы создадим игру с анимацией, спрайтами (картинками), функциональностью и графическим интерфейсом.

Сама библиотека вышла уже давным давно, примерно в 2000 году . Приложения написанные на PyGame могут спокойно работать на Андроид, а также на компьютерах. Таким образом вы можете создавать игры, которые работать на нескольких устройствах, но при этом иметь один и тот же код.

На основе этой библиотеки уже было построено множество игр и приложений. Посмотреть большой список популярных игр можно на их.

Всем начинающим программистам хочется создать на новом языке программирования какую-нибудь игру, но знаний и практических навыков пока еще маловато. Не беда!

Python для начинающих

Давайте создадим простую игру с использованием логических условий !

Иногда лучший способ начать работу с новым языком программирования — посмотреть уже готовую разработку. Смысл состоит из просмотра существующего фрагмента кода, чтобы узнать, как он работает.

Давайте рассмотрим кусок кода довольно простой, но полностью работающей игры на Python «Угадай число». Вначале посмотрите на код. Посмотрите на синтаксис. Постарайтесь понять, что этот код делает, построчно. Измените код, чтобы узнать, можете ли вы адаптировать эту игру.

Код игры «Угадай число»

import random
NumberToGuess=random.randint(1,100)
userGuess=-1


userGuess=int(input(«Угадай число от 1 до 100»))
if userGuess > NumberToGuess:

else:
print(«Вы угадали, это число = » + str(NumberToGuess))
#Конец игры — выйти из цикла while
break

Давайте разберем, как это работает

import random
NumberToGuess = random.randint(1,100)

Генерируют случайное целое число от 1 до 100 и помещают его в переменную
NumberToGuess.

Конструкция

while userGuess!=NumberToGuess:

задает цикл с условием. Пока число пользователя не совпадёт с загаданным числом

Блок кода

if userGuess > NumberToGuess:
print(«Число должно быть меньше!»)
elif userGuess print(«Число должно быть больше!»)
else:

совершает три сравнения и выводит предупреждающие надписи, чтобы пользователь смог догадаться. Работают так называемые логические условия, если одно из них принимает значение True, то эта часть выполняется.

последняя строка

Пример работы программы

Python 3.5.2 (default, Dec 2015, 13:05:11)
on linux
>
Угадай число от 1 до 100 60
Число должно быть меньше!
Угадай число от 1 до 100 50
Число должно быть меньше!
Угадай число от 1 до 100 40
Число должно быть меньше!
Угадай число от 1 до 100 30
Число должно быть меньше!
Угадай число от 1 до 100 20
Число должно быть меньше!
Угадай число от 1 до 100 10
Число должно быть больше!
Угадай число от 1 до 100 12
Вы угадали, это число = 12
>

Возможная доработка программы

Не могли бы вы изменить этот код, чтобы подсчитать, сколько попыток сделал пользователь, чтобы угадать правильное число? Как только вы угадаете правильный номер, программа должна рассказать вам, сколько догадок вы использовали, и определит ваш выигрыш.

Проверка времени реакции пользователя Как создать калькулятор на Питоне?

Программирование на Python — синтаксис, методы, алгоритмы, примеры, программы, книги, справочники бесплатно

Литература ⇒ Программирование ⇒ Python

Python или питон - высокоуровневый язык программирования общего назначения, ориентированный на повышение производительности разработчика и читаемости кода. Синтаксис ядра Python минималистичен.

Какие игровые движки существуют для Python?

В то же время стандартная библиотека включает большой объём полезных функций. Программирование на Python, в том числе python 3, разработка Web-сайтов при помощи библиотеки Django, приложений, в том числе и многопоточных, на Python, PyQt., модели, контроллеры и шаблоны, синтаксис, методы, алгоритмы, примеры, программы — эти и другие книги, справочники, руководства и самоучители можно найти и скачать бесплатно в этом разделе.

Извините, данный раздел находится в разработкеYou have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near » at line 1

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

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

Обработка событий

В Breakout есть три типа событий: события нажатий клавиш, события мыши и события таймера. Основной цикл в классе Game обрабатывает нажатия клавиш и события мыши и передаёт их подписчикам (вызывая функцию-обработчик).

Хотя класс Game очень общий и не обладает знаниями о реализации Breakout, сама подписка и способы обработки событий очень специфичны.

Класс Breakout

В классе Breakout реализуется большинство знаний о том, как управляется игра Breakout. В этой серии туториалов мы несколько раз встретимся с классом Breakout. Вот строки, которые регистрируют различные обработчики событий.

Нужно учесть, что все события клавиш (и для левой, и для правой «стрелки») передаются одному методу-обработчику ракетки.

# Register the handle_mouse_event() method of a button object self.mouse_handlers.append(b.handle_mouse_event) # Register the handle() method of the paddle to handle key events self.keydown_handlers.append(paddle.handle) self.keydown_handlers.append(paddle.handle) self.keyup_handlers.append(paddle.handle) self.keyup_handlers.append(paddle.handle)

Обработка нажатий клавиш

Класс Game вызывает зарегистрированные обработчики для каждого события клавиш и передаёт клавишу. Заметьте, что это не класс Paddle. В Breakout единственный объект, который интересуют подобные события - это ракетка. При нажатии или отпускании клавиши вызывается его метод handle() .

Объекту Paddle не нужно знать, было ли это событие нажатия или отпускания клавиши, потому что он управляет текущим состоянием с помощью пары булевых переменных: moving_left и moving_right . Если moving_left равна True, то значит, была нажата клавиша «влево», и следующим событием будет отжатие клавиши, которое сбросит переменную. То же самое относится и к клавише «вправо». Логика проста и заключается в переключении состояния этих переменных в ответ на любое событие.

Def handle(self, key): if key == pygame.K_LEFT: self.moving_left = not self.moving_left else: self.moving_right = not self.moving_right

Обработка событий мыши

В Breakout есть игровое меню, с которым мы скоро встретимся. Кнопка меню управляет различными событиями мыши, такими как движение и нажатия кнопок (события mouse down и mouse up). В ответ на эти события кнопка обновляет переменную внутреннего состояния. Вот код обработки мыши:

Def handle_mouse_event(self, type, pos): if type == pygame.MOUSEMOTION: self.handle_mouse_move(pos) elif type == pygame.MOUSEBUTTONDOWN: self.handle_mouse_down(pos) elif type == pygame.MOUSEBUTTONUP: self.handle_mouse_up(pos) def handle_mouse_move(self, pos): if self.bounds.collidepoint(pos): if self.state != "pressed": self.state = "hover" else: self.state = "normal" def handle_mouse_down(self, pos): if self.bounds.collidepoint(pos): self.state = "pressed" def handle_mouse_up(self, pos): if self.state == "pressed": self.on_click(self) self.state = "hover"

Заметьте, что метод handle_mouse_event() , зарегистрированный для получения событий мыши, проверяет тип события и переадресует его к соответствующему методу, обрабатывающему этот тип события.

Обработка событий таймера

События таймера не обрабатываются в основном цикле. Однако поскольку основной цикл вызывается в каждом кадре, легко проверить, настало ли время определённого события. Вы увидите это позже, когда мы будем обсуждать временные спецэффекты.

Ещё одной ситуацией является необходимость приостановки игры. Например, при отображении сообщения, которое игрок должен прочитать и чтобы при этом ничего его не отвлекало. Метод show_message() класса Breakout использует такой подход и вызывает time.sleep() . Вот соответствующий код:

Import config as c class Breakout(Game): def show_message(self, text, color=colors.WHITE, font_name="Arial", font_size=20, centralized=False): message = TextObject(c.screen_width // 2, c.screen_height // 2, lambda: text, color, font_name, font_size) self.draw() message.draw(self.surface, centralized) pygame.display.update() time.sleep(c.message_duration)

Игровой процесс

Игровой процесс (геймплей) - это то место, в котором вступают в дело правила Breakout. Геймплей заключается в перемещении различных объектов в ответ на события и в изменении состояния игры на основании их взаимодействий.

Перемещение ракетки

Вы видели ранее, что класс Paddle реагирует на нажатия клавиш со стрелками, обновляя свои поля moving_left и moving_right . Само движение происходит в методе update() . Здесь выполняются определённые вычисления, если ракетка находится близко к левой или правой границе экрана. Мы не хотим, чтобы ракетка выходила за границы экрана (с учётом заданного смещения).

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

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 update(self): if self.moving_left: dx = -(min(self.offset, self.left)) elif self.moving_right: dx = min(self.offset, c.screen_width - self.right) else: return self.move(dx, 0)

Перемещение мяча

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

Задание исходной скорости мяча

Мяч в Breakout возникает из ниоткуда в самом начале игры каждый раз, когда игрок теряет жизнь. Он просто материализуется из эфира и начинает падать или ровно вниз, или под небольшим углом. Когда мяч создаётся в методе create_ball(), он получает скорость со случайным горизонтальным компонентом в промежутке от -2 до 2 и вертикальным компонентом, задаваемым в модуле config.py (по умолчанию задано значение 3).

Def create_ball(self): speed = (random.randint(-2, 2), c.ball_speed) self.ball = Ball(c.screen_width // 2, c.screen_height // 2, c.ball_radius, c.ball_color, speed) self.objects.append(self.ball)

Подведём итог

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

Помните также, что у нас есть для продажи и для изучения в Envato Market , если вы хотите изучить больше материала, связанного с Python.

В четвёртой части мы рассмотрим важную тему распознавания коллизий и увидим, что происходит, когда мяч ударяется об разные игровые объекты: ракетку, кирпичи, стены, потолок и пол.

Затем мы уделим внимание игровому меню. Мы создадим собственные кнопки, которые используем в качестве меню и сможем при необходимости показывать и скрывать.


В прошлый раз мы разобрались, как подготовить компьютер к работе с Python, и теперь можем перейти к написанию игры. В создании любой игры чуть ли не самое важное - это как можно скорее начать её прототипировать, чтобы сразу выявить основные уязвимости и устранить их. У нас, конечно, игра довольно простая, да и главное для нас не её качество, а упражнения с кодом. Тем не менее раннее прототипирование - это полезная привычка в разработке чего угодно, поэтому давайте ей следовать.

Для тех, кто успел подзабыть за две недели суть того, что мы делаем, напомню. LAM-40 - это текстовая игра, где вы играете за человека, которому нужно успеть за девять часов получить справку на 40-м этаже загадочного государственного учреждения, переполненного бюрократами. Каждый этаж учреждения генерируется случайным образом: игроку попадаются бюрократы разного уровня, к каждому из которых нужно искать особый подход, чтобы пройти дальше и добраться до последнего этажа.

Примечание: весь код приводится для Python версии 3.0 и старше, а потому может не работать на более старых версиях.

Для начала давайте вспомним, какой код остался у нас с прошлого раза, и разберёмся, чем мы его хотим дополнить.

У нас есть простенький класс бюрократа, умеющего здороваться с игроком и обладающего двумя характеристиками - рангом и настроением. Задача на сегодня - создать простейший, бесконечно повторяющийся цикл «сражений» с бюрократами, что, по сути, и есть основная механика нашей игры. Получившийся у меня в итоге прототип занял 95 строк - начнём его разбирать по порядку (последняя пустая строка должна быть по стандартам PEP 8, но embed"ы её обрубают) .

Строка 2: помимо random, в начале программы мы импортируем модуль sys. Из него нам понадобится функция exit() - возможность выйти из игры, пока что не указанная эксплицитно.

Строки 5–13, 16: я добавил списки положительных и отрицательных реакций бюрократов. Обратите внимание, как используются переносы. Дело в том, что, согласно PEP 8, максимальная длина строки должна быть 79 символов, иначе код будет слишком сложно читать. Именно поэтому для удобства каждое новое высказывание находится на новой строке и написано с тем же отступом, что и предыдущее. Ещё я добавил переменную ACTIONS типа данных string с основными действиями игрока (обычным вопросом, подкупом, мольбой, давлением и угрозой) , чтобы не делать слишком длинной строку, где мы будем выводить все эти действия.

Строки 25, 34–44: у класса Bureaucrat появилась характеристика negative логического типа данных (то есть истина или ложь) со значением False. Она показывает, был ли ход игрока удачным - если нет, то её значение становится True, и игра предлагает нам совершить новое действие на том же самом бюрократе, а не создаёт нового бюрократа. Для того чтобы отобразить реакцию бюрократа на действие игрока, используются функции с говорящими названиями react_positively() и react_negatively(), которые меняют значение переменной negative и выводят случайную реакцию бюрократа на случившееся.

Строки 46–85: функция act() выводит на экран все возможные действия игрока и предлагает ему выбрать одно из них, введя первую букву слова. Именно для того, чтобы не загружать эту строку, мы вывели string со всеми действиями в отдельную переменную ACTIONS. Следом за этим идёт ветвление, и мы впервые в этом блоге используем ключевое слово elif - сокращение от else if. Оно задаёт условие помимо того, что указано в if, а блок с ним - следующие за выполнением этих условий действия. В нашем случае мы проверяем, что ввёл игрок. Если он ввёл строчную букву Q, выполнение программы завершится из-за упомянутой ранее функции sys.exit(). Если игрок введёт строчную букву W (wait) , то ему выпадет новый бюрократ - это тоже пока что незадокументированная возможность. Если же игрок введёт какой-нибудь другой символ, то программа перейдёт в функцию react(), где ветвление выходит на новый уровень. На текущем этапе я сделал так, что для определённых сочетаний ранга и настроения бюрократа срабатывает лишь одно действие. Более того, есть непобедимые сочетания (например, высокий ранг и плохое настроение), в случае с которыми приходится вводить W, чтобы выпал следующий бюрократ.

Самая большая головная боль на первых порах - это использование ключевого слова self, когда вы ссылаетесь на функцию или характеристику класса внутри этого класса, и оператора сравнения == вместо оператора присваивания = рядом с условными операторами. Вероятно, и о том, и о другом вы будете забывать, так что следите за этим. Кстати, обратите внимание на ключевое слово and, которое используется рядом с операторами ветвления - оно обозначает, что должны выполняться оба условия, находящиеся рядом с ним.

Строки 87–94: наконец, в самом низу заканчивается описание класса и начинается логика основной программы. Мы создаём образец класса Bureaucrat, присваиваем его переменной bureaucrat и используем функцию с приветствием бюрократа. После этого запускаем бесконечный цикл, внутри которого запускаем функцию с выводом и вводом действий игрока, а также условное исполнение создания и приветствия нового бюрократа - в зависимости от того, каково текущее значение bureaucrat.negative. Обратите внимание на две вещи: оператор else при желании можно не использовать, а в случае с логическими типами данных в Python вместо оператора == применяют ключевое слово is.

Первый прототип LAM-40 готов. Конечно, он ещё далёк не то что от финальной, а даже от альфа-версии, поэтому нам предстоит порядочно поработать. Сейчас я вижу кучу проблем с этим прототипом, которые собираюсь устранить в следующий раз:

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

Не обойтись без элемента неожиданности в «сражениях» с бюрократами - иначе игра быстро наскучит. Вероятно, это также поможет избежать дикого ветвления, которое есть сейчас в нижней части кода.

Игре требуется больше разнообразия во фразах, характеристиках, действиях и прочем, а также в том, как всё это выводится, чтобы внести разнообразие в повествовательную часть.

Желательно описывать текстом все действия, что возможны в игре, в том числе выход из неё и ожидание.

Из возможных действий желательно сделать список, а не отдельный string, чтобы туда можно было при определённых условиях добавлять новые действия. Кроме того, на ввод нужно применить функцию upper(), чтобы не требовалось каждый раз вводить строчные буквы. Также надо сделать адекватную реакцию на неверно введённые символы.

В основной логике игры есть повторяющиеся две строчки, нарушающие правило DRY. Да и сам цикл не очень красив - пожалуй, стоит над ним подумать, когда он начнёт расширяться.

Куча мелочей вроде описания функции act(), комментариев к некоторым частям кода, использования sys.exit() и характеристики negative, которая тоже не очень изящное решение. Так что поищу ему альтернативу.

Как видите, работы предстоит полно. Надеюсь, к следующему разу игра примет уже более целостный вид. Если вам что-то непонятно, пишите комментарии под материалом и в социальных сетях - буду рад и любым другим отзывам. Если вы более опытный в программировании человек, чем я, то с удовольствием выслушаю содержательную критику. Спасибо, и до следующего раза!