Говорят, что на Python легко и просто создавать платформеры. Правда ли?
Предисловие
Все мы были когда-то детьми. И те, кто вырос в 90-ые, наверняка играли в приставку Денди (в США она называлась NES - Nintendo Entertainment System). Среди всех игр была одна игра, которая мне особенно запомнилась, так как была не похожа на все остальные. Это игра The Addams Family (1992 год, студия Ocean). В игре была какая-то своя атмосфера. И мне захотелось создать что-то похожее.
Придумываем сюжет и геймплей
Сюжет в игре будет очень простым: мы купили поместье викторианской эпохи, и когда туда приехали, нам очень сильно захотелось заснуть. При этом мы обратили внимание, что повсюду разбросаны детали некоторого механизма. Проблема в том, что дверь в поместье закрыта и нам надо найти ключи. Ходим ищем ключи от дверей, собирая детали, а когда открываем двери ищем кровать, чтобы поспать. Видим очень странные сны, в которых также собираем предметы, уклоняясь от врагов и шипов. И в конце находим проблему этого поместья.
Рисуем ассеты
Перед тем как приступить к созданию ассетов (разных картинок, которые потом можно будет анимировать), надо разобраться в каком разрешении будет работать игра. У всех разные мониторы с разным разрешением. И не у всех даже FullHD. Поэтому выбираем 720p - то есть разрешение 1280х720 пикселей. Для такого разрешения органично будут смотреться плитки земли размером 64х64 пикселя. Берем любой векторный графический редактор и начинаем рисовать.
Рисовал как мог, вспоминая как у всех ребят в школе по рисованию было «5», и только у меня одного «4». Вы могли заметить, что это все статические объекты, которые не имеют анимации. Ну а как же с анимированными объектами? Все просто, анимированные объекты - это те же отдельные картинки, у которых изменяется положение некоторых пикселей. Вот пример анимации на примере главного героя игры:
Собственно программирование
Инструмент для создания игр мы будем выбирать на основании того языка программирования, который более-менее знаем. В моем случае, это Python. Выбираем какую-нибудь современную версию языка - я выбрал Python 3.11.4. Для создания игр на Python существует библиотека Pygame. Причем это не графический движок типа Unity. Здесь нет графического интерфейса, а есть только набор разных классов, в которых есть свои атрибуты и методы. Эта библиотека служит для осуществления манипуляции графическими объектами на основе событий (events).
Заполняем словарики
Игру начинаем даже не с файла main.py, а с файла settings.py, куда будут прописаны основные параметры и настройки игры. Помимо названия игры, длины и ширины окна игры, мы должны прописать доступы к ассетам. В связи с тем, что данных много, а в Python есть такой тип данных как словарь (dict), в основе которого лежит хеш-таблица, мы выбираем именно словарь и заполняем. Фрагмент такого словаря:
12: {
'style': 'enemy',
'type': 'tile',
'menu': 'enemy',
'menu_surf': 'images/menu/bug.png',
'preview': 'images/preview/bug.png',
'graphics': 'images/enemies/cementry/bug'
},
С чего начинается родина - с main.py
Наконец-то поработаем с main.py. Конечно, будем использовать нашу любимую концепцию ООП. Есть такая штука как принципы SOLID, которым следует придерживаться при разработке программы. Некоторые из них я собираюсь нарушить, но вот один я никак нарушить не могу - буковка “S” - Single Resposibility Principle. Суть его в том, что каждый класс должен выполнять строго обозначенную функцию и быть ограниченным своей задачей. Не надо создавать классы, которые делают все сразу. Поэтому main.py будет класс Main, в котором есть главный цикл игры - бесконечный цикл с условием While, но все события будут обрабатываться в других отдельных классах, а в Main мы будем создавать экземпляры этих других классов.
Режим редакторы игры
Как думаете, что объединяет такие игры как Heroes of Might and Magic 3, The Elder Scrolls III: Morrowind и Max Payne. Их объединяет, что на диске с ними шел встроенный редактор уровней. Это мощнейший инструмент в разработке игры. Беда в том, что в Pygame ничего подобного нет, а значит мы его сами создадим. Здесь лучше найти какой-нибудь туториал. Я лично нашел на YouTube видео немецкого программиста Christian Koch “Creating a Mario Maker style game in Python” (канал ClearCode). На основе этого видео и создаем редактор уровней. Но нам надо как-нибудь сохранять те произведения искусства, которые мы создали. И здесь у нас есть выбор: либо сохранять как текстовый формат передачи данных (например, JSON), или же сохранять в бинарном виде, используя библиотеку pickle. Я решил, что для уровней у нас будут бинарные файлы, а для сохранений в процессе игры у нас будет JSON.
Параллельно с режимом редакторов уровней у нас есть отдельный класс Level, в экземпляр которого будут передаваться данные из редактора. И все механики будут тестироваться в именно в экземпляре класса Level.
Коллизии и анимации спрайтов
Спрайт в Pygame - это графическое изображение, которое представляет собой одну единицу анимации. Главное здесь - это коллизии и анимации. С коллизиями все просто - у классов Pygame есть встроенные методы для работы с коллизиями, например, collidepoint. Для анимаций в соответствующем классе мы создадим свой метод и используем тернарный оператор:
def animate(self, dt):
self.frame_index += ANIMATION_SPEED * dt
self.frame_index = 0 if self.frame_index >= len(
self.animation_frames) else self.frame_index
self.image = self.animation_frames[int(self.frame_index)]
Мы просто перебираем картинки в списке, и вызываем их одну за другой с помощью переменной self.frame_index. Тернарый оператор применяется, чтобы из бежать ошибки IndexError: list index out of range.
Обратите внимание на константу ANIMATION_SPEED. Дело в том, что у нас разлочен FPS в игре и скорость анимации никак не зависит от FPS.
Главное меню игры
Не помню какой обзорщик из YouTube 10-ых годов говорил, что главное меню - это как вешалка в театре. Для меня очень удобным было меню из игры The Elder Scrolls V: Skyrim - ничего лишнего. Нечто похожее и мы сделаем с учетом, что я не умею рисовать и игру мы делаем на Pygame. В главном меню мы должны сказать игроку как управлять персонажем в игре и создать возможности поменять язык, перейти в полноэкранный режим, уменьшить или увеличить громкость музыки и звуков, и перейти в режим редактора.
Музыкальная пауза
Если для звуков полно бесплатных ресурсов, где можно скачать free sounds, то музыку я решил сам написать для игры. Все рождается из импровизации. Записываем все на листочек, а потом ноты перебиваем в любой музыкальный midi-редактор, который поддерживает нотный стан.
Вот пример трека Garden из моей игры:
Как видно, самый обычный соль-мажор и нисходящие, восходящие гармонии. Остается все это записать на своем синтезаторе Casio из детства, немного обработать и саундтрек готов.
Итоги
Самое главное, что я понял, создание игры - это хорошая возможность прокачать ваши hard skills. Процесс создания игры заставляет вас решать множество задач. Библиотека Pygame - это один из самых лучших способов понять концепцию ООП в Python. Я, когда неправильно прописал условия для звуков и музыки, понял, как именно загружаются в память компьютера экземпляры классов, как в них инициализируются атрибуты и как работают методы. Также вы повторите весь базовый синтаксис Python - работу со всеми типами данных, включая файлы, и со стандартными библиотеками типа math, random, configparser, json и pickle.
Полный код игры доступен на моем GitHub
На GitHub разблочен режим редакторов уровней и имеет полный функционал, а в архиве с игрой этот функционал ограничен в плане рисования и сохранений.
И что же получилось на выходе
Комментарии (19)
Zara6502
09.10.2023 08:44у мня вопрос, я когда пишу на C# то для меня самое тяжелое это разбор ошибок и их исправление, учитывая что в питоне с этим всё еще хуже - как вы занимаетесь отладкой таких проектов? я даже скрипт на 30 строк порой пишу 3 часа как раз занимаясь отладкой.
IvanZaycev0717 Автор
09.10.2023 08:44я бы не сказал, что отладка в Python уж слишком сложна. Для интерпритируемых языков легче делать отладку, чем для компилируемых. Когда запускаешь проект с ошибкой у тебя сразу в консоль выводится, в какой строке ошибка и с чем она связано (например, AttrubuteError, когда в класс что-то не передал). Плюс у Python есть прекрасная IDE Pycharm и можно дебаггером отследить в какой именно момент произошла ошибка. Поэтому проблем особых с этим не было
Zara6502
09.10.2023 08:44в консоль выводится, в какой строке ошибка и с чем она связано
у меня в основном с этим и проблема, строку пишет, а что конкретно не понравилось - не пишет.
Andrey_Solomatin
09.10.2023 08:44В Питоне добавляются ошибки типов (хотя как там с null-safty в С#?), а так тоже самое.
Используйте собственные исключения с контекстом, вместо встроенных в С#.
Делайте в них информативыне сообщения об ошибках.
Если в мапе нет ключа, толко вы как автор приложения знаете, что это значит и что нужно делать.Zara6502
09.10.2023 08:44-1Используйте собственные исключения с контекстом
это вы про этап выполнения? с этим проблем нет, проблема на этапе компиляции или оформления, то пробел забыл, то запятую, в C# это проще.
Andrey_Solomatin
09.10.2023 08:44+1Мой выбор для Питона это IDE, аннотации типов и куча линтеров в прекоммите. Хорошо помогают ловить опечатки и прочие синтаксические ошибки.
Такие вещи как незакрытые скобки имеют тенденцию показывать ошибку где-то после того как скобка пропущенна, такие проблемы минимизируются хорошей декомпозицией или вынесением шагов в переменную.
Чем более плоский код вы пишите, тем сложнее с ним ошибиться.
Если у вас большие структуры, возможно это вообще не код, а данные.
Если взять пример автора, вот эту часть можно в отдельный тектовый файл убрать. Взять какой-нибудь популярный фомат, с возможностью провреки на базовый синтаксис и схему (yaml, json, toml?).
https://github.com/IvanZaycev0717/the_mystery_of_the_mansion/blob/main/stages/main.py#L357Zara6502
09.10.2023 08:44спасибо за пояснения, я слишком редко общаюсь с питоном и думаю просто застрял в 2005 году.
MajorMotokoKusanagi
09.10.2023 08:44+1Раскрыть бы чуть подробнее сюжет и геймплей, описать механики уровней/этапов, вписав в историю аномалии/демона/полтергейста/проклятия поместья. В общей части.
Раскрыть движок в плане функционала, анимация в котором реализуется через или массив спрайтов (удобство чего и послужило выбором типа движка), или ещё как. Обработка столкновений (коллизий) — туда же. Звуковое сопровождение игры — в функционале движка или самому довешивать коллбэки на события. То же с меню. С алгоритмически/общим описанием и внятными кусками кода. И прочие детали реализации.
А публикация в текущем варианте — это набросок черновика, который как-то вышел из песочницы. Автору прочитать Марио Цехнера, например.IvanZaycev0717 Автор
09.10.2023 08:44+1тогда получилась бы "Война и Мир". Здесь просто описание того, что можно сделать, речь о самой возможности реализации на Python с минимальным описанием как делать. Статья создавалась с тем фокусом, чтобы не забивать людям голову и не грузить излишними техническими подробностями
AcckiyGerman
09.10.2023 08:44+1Мне понравился дизайн главного героя! Костюмчик с галстуком смотрится свежо)
vassabi
09.10.2023 08:44все ожидал что будет элегантный удар\бросок тростью, но как видно - у автора не было на это времени :)
Andrey_Solomatin
09.10.2023 08:44Мы просто перебираем картинки в списке, и вызываем их одну за другой с
помощью переменной self.frame_index. Тернарый оператор применяется,
чтобы из бежать ошибки IndexError: list index out of range.
Чтобы избежать можно просто откидывать целые курги.self.frame_index = self.frame_index % len(self.animation_frames)
Разница в том, что этот код может проскочить первую анимацию.
Andrey_Solomatin
09.10.2023 08:44+1но вот один я никак нарушить не могу - буковка “S” - Single
Resposibility Principle. Суть его в том, что каждый класс должен
выполнять строго обозначенную функцию и быть ограниченным своей задачей.
А как же https://github.com/IvanZaycev0717/the_mystery_of_the_mansion/blob/main/stages/level.py ?
Andrey_Solomatin
09.10.2023 08:44+1Инструмент для переводов.
Размечаете то, что должно быть переведено в коде
Запускаете тулзу которая парсит все включения и создаёт/обновляет текстовый файл
Делаете переводы
Компилируете его в бинарный
Используете в приложении этот файл.
https://phrase.com/blog/posts/translate-python-gnu-gettext/
ignorabimus
Это ж Dangerous Dave в гриме! )))
vesper-bot
А шотган где?