Программирование игр руководство

Инструкция начинающего разработчика игр

Время на прочтение
12 мин

Количество просмотров 220K

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

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

И так Вы решили сделать свою игру, о чём Вам нужно подумать…

Думаем – нужно ли это тебе

Перед тем, как за что-то взяться, необходимо всё обдумать. А перед тем, как заняться разработкой игр, необходимо обдумать всё очень хорошо. Очень часто начинающие разработчики, достигнув определённых успехов в чём-то (сделал мод для игры, небольшой фан-сайт и пр.), начинают грезить созданием своей игры. Это происходит из-за того, что они имеют слабое представление о процессе разработки игр.

Я перечислю основные ошибки в их представлении:

  • Нет романтики. Многие начинающие разработчики, наигравшись вдоволь в игры, приходят к мысли, что создавать игры также интересно, как и играть, только чуть-чуть сложнее. Это очень частая ошибка. Чем больше и сложнее игра тем, скучнее и безынтереснее процесс её разработки. Романтики совершенно нет.
  • Трудно и даже невозможно. Многие после нескольких (или даже одного) успешного проекта наполняются уверенностью, что написать игровой проект им по силам. На самом деле, игры – это одно из самых сложных направлений разработки. И чем «серьёзнее» игра, тем проект сложнее. В процессе создания игры разработчик может столкнуться с нерешаемыми проблемами, которые убивают на корню энтузиазм даже у самых упёртых.
  • Отвращение к играм. Со временем у каждого матёрого разработчика игр развивается отвращение к играм. Сначала просто они становится менее интересными. Затем начинаешь замечать, что они вовсе не интересны, а интересно только, как они работают. Чем дальше, тем хуже.
  • Конкуренция и качество продукта. Играми занимаются многие студии и независимые разработчики. Существует своеобразный «уровень вхождения» в этот бизнес. Сейчас нельзя сделать успешную игру, нарисованную акварелью (да, такие игры встречались в начале 2000-х). Она просто не выдержит конкуренции. Соответственно, абы что не сделаешь. Тут скрывается важный психологический момент – начинающий разработчик вынужден делать хороший продукт, иначе он будет испытывать постоянное чувство страха неуспешности своего продукта.
  • Время и ресурсы. И самая распространенная ошибка – это то, что ресурсов (время, деньги, знания) им хватит. Чтобы понять объём работ, читайте следующие пункты.

Концепция и ТЗ

Когда-то давно я написал довольно неплохую статью о концепции проекта. За последние пару лет мои взгляды слегка поменялись, но суть осталась та же.

  • Что же такое концепция? Концепция игрового проекта — это документ, описывающий цели, задачи, основные особенности проекта, исследование рынка и целевой аудитории, условия его выполнения. Также, так как проект игровой, обязательно описание игровой механики, игровых понятий, примерный сценарий и концепт-арт. Если Вы ещё рассчитываете делать проект не в одиночку (что весьма вероятно), то понадобится ещё техническое задание (ТЗ) – документ, содержащий описание необходимых работ, сроки и условия.
  • Зачем нужна концепция? Очень хороший вопрос. Зачем заниматься какой-то «писаниной», когда нужно собирать команду и писать код?
    В первую очередь концепция нужна, чтобы самому получить полноценное представление о конечном результате и оценить объём работ. Без чёткой и продуманной концепции у Вас в итоге получится, соответственно, противоречивая и непродуманная игра. Без концепции существует большая вероятность возникновения ошибок организации игровой механики или ошибок реализации.
    Во вторую очередь концепция нужна для того, что бы другие поняли то, что Вы хотите сделать. Все члены команды должны работать над одним общим проектом. Об этом общем проекте члены команды узнают из документа концепции проекта. Это нужно, чтобы не было расхождений в представлениях о конечном результате. Если Вы решили создать игру и для этого собираете команду, то первые вопросы от будущих членов команды будут: «А что предстоит мне сделать? Что в итоге мы должны получить?». Вы должны будете им предоставить концепцию проекта и ТЗ. Без концепции и ТЗ Вы не привлечёте ни одного нормального специалиста.
  • Объёмы. Весьма интересен вопрос объёма концепции. Тут необходимо отталкиваться от сложности игры и её разработки. Если у Вас простая игра, Вы работаете один и Вы способны удержать идею игры в своей голове, то можно вообще не писать концепцию. Если удержать в памяти все моменты нельзя, то необходимо перенести их на бумагу (или другой носитель). Если Вы будете работать в команде или использовать помощь других людей (инвесторы, художники и прочие), то Вам просто необходима развёрнутая концепция и ТЗ для каждого человека. Критерий один – понятность. Нужно чтобы любой человек, ознакомившись с концепцией и ТЗ, представил конечный результат, так же как и Вы.
    Обратите внимание на то, что, если Вы понимаете свою концепцию, то это не значит, что её поймут другие. Написание концепции выполняет роль «лакмусовой бумажки». Если Вы не можете написать понятную концепцию (примерно, 5 страниц для небольшой игры, несколько десятков страниц для большой), то вряд ли Вы закончите в итоге проект.
  • Детальность. В концепции должны быть ответы на все вопросы. После прочтения концепции должно сформироваться полное представление о проекте. Специалисты первым делом смотрят на концепцию, если концепция окажется для них не полной и непонятной, то они не будут с Вами работать.
    Отдельно стоит упомянуть концепт-арт. Он должен быть, хотя бы в простейшем виде. Он является доказательством решения проблемы с контентом, содержимым игры (смотрите следующий раздел).
  • Русский язык. Для многих это серьёзная проблема. Если документ концепции содержит множество грамматических, орфографических и синтаксических ошибок, то ни один специалист не воспримет его всерьёз. Помните: незнание русского языка очень вредит бизнесу.
  • Время. Желательно в сразу в ТЗ указать сроки выполнения работ. Проблема в том, как оценить это время, если никогда подобным не занимался. Точно ответа на этот вопрос Вы никогда не получите, всё придёт с опытом. Я только дам один совет: учитывайте не только время разработки, но и время на исправление ошибок (примерно 50% от времени на разработку).

Контент

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

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

Разберём основные моменты этого раздела.

  • Сложность. Это самая главная проблема в вопросе контента. Оказывается, в большинстве случаев подготовка контента является самой сложной задачей, сложнее написания программного кода, сложнее тестирования и отладки, и сложнее реализации игры. Естественно, если игра маленькая, то это не так заметно, а если большая, то на долю создателя контента выпадает до 80% работы по проекту.
  • Объёмы. Часто из-за того, что разработчики никогда не выполняли задачи создателей контента, им очень сложно оценить объёмы. Кажется, что там такого ¬– пара десятков картинок и 3-4 звука. Но если посчитать, то получается 40 крупноразмерных изображений, 400 мелких изображений, два десятка звуков (я привел пример среднестатистической BMMORPG). Хорошо, что это всё можно подсчитать при подготовке концепции.
  • Качество. Во-первых, надо понимать, что игроки работают непосредственно с контентом. Во-вторых, надо помнить, что существует огромное количество игр-конкурентов с хорошим контентом. Можно сделать вывод: игра с плохим контентом будет не конкурентоспособна, т.е. контент в игре должен быть высокого качества.
  • Время. Вполне логично, что на подготовку больших объёмов качественного контента уходит очень много времени. Времени уходит больше, чем на все остальные направления вместе взятые (маленькие игры не в счёт). Учитывайте это, когда будете планировать и рассчитывать сроки.
  • Стоимость контента. Хороший контент стоит «хороших» денег. Очень «хороших» денег, которых у начинающих разработчиков игр обычно нет. Многие разработчики питают иллюзорную надежду найти «бесплатного» создателя контента (или дешёвого). Найти можно, но он либо будет создавать низкопробный контент, либо создаст для Вас немного контента, а затем переметнётся к тем, кто будет ему платить. Короче говоря, «бесплатного» создателя хорошего контента Вы никогда не найдёте. Именно по этой причине нет хороших «OpenSource» игр.
  • Воровство. Из-за существования проблемы дорогого контента, иногда появляются разработчики игр, которые его воруют. Мол, что такого?.. возьму-ка я из этой игры десяток картинок, а фоновые изображения найду на DA, а в качестве фоновой музыки поставлю пару любимых песенок Rammstein. Проблема в том, что авторское право контента достаточно легко подтвердить. У «жертвы» воровства есть либо исходные файлы контента, либо документ о передаче контента от его создателя. Для воров контента очень велик шанс нарваться на статью 146 УК РФ.
  • Единый стиль. Ещё один важный момент, о котором часто забывают. Что бы игра смотрелась цельной и продуманной, ей нужно иметь единый стиль. Создатели контента не роботы, поэтому делают работы в своём индивидуальном стиле. Отсюда можно сделать вывод: желательно чтобы содержимое игры создавало как можно меньше человек.
  • Дилемма. После прочтения описанных выше моментов можно построить следующую цепочку: Конкурентоспособная игра требует использование качественного контента. Качественный контент может сделать только профессиональный создатель контента. Профессиональный создатель контента стоит недёшево. Решений данной проблемы всего лишь три:
    1. Не делать игру, то есть отказаться от направления разработки игр.
    2. Найти инвестора. Но тут сразу нас поджидает проблема концепт-арта. Кто будет подготавливать концепт-арт, если нет денег на художника? А без концепт-арта никто не будет инвестировать в Вас.
    3. Решать проблему собственными силами. То есть кто-то из членов команды должен быть создателем контента и должен получать зарплату, даже если все остальные сосут палец.

Программирование

Как ни странно, создание программного кода для игр не является самой сложной задачей, но в тоже время и не является простой.

  • Команда. В отличие от создателей контента программистов для своей команды найти легко. Это объясняется тем, что при определённом уровне подготовки написание программного кода игры не такая уж сложная задача. Можно найти «бесплатных» программистов, готовых работать ради интереса. А за плату и «имя» (упоминание, как разработчика игры) можно найти программиста, который будет писать хороший годный код. Но… сейчас не начало 2000-х и программисты поумнели. Первым делом адекватный программист попросит разработчика продемонстрировать концепцию и ТЗ. Затем спросит про создание контента или финансирование, которое пойдёт на его создание. Специалисты прекрасно понимают, что незачем вкладывать силы в изначально провальный проект. Если у Вас нет концепции и не решен вопрос с контентом, то нормального программиста Вы не найдёте.
  • Сначала делаем большое, потом маленькое. Достаточно простой совет, но ему чаще всего не следуют. Игровой проект в большинстве случаев сложен и имеет множество зависимостей. Все это сложно просчитать на уровне составления концепции, частенько приходиться что-то менять в планах. Поэтому, чтобы не выполнять двойную работу, сначала необходимо сделать общую работающую конструкцию (прототип), а затем углубляться в детали.
  • Что сначала контент или код? Прочитав раздел про контент, Вы уже, наверное, поняли, что современные игры основаны на контенте, а не на программировании. Отсюда дилемма – код подгонять под контент или контент подгонять под код. Оба подхода имеют место в современном процессе разработки. Если подгонять код под контент, то нагрузка падает на программиста, время разработки увеличивается. Это дешёвый способ. Если контент подгонять под код, то нагрузка с программиста спадает, и при учёте, что контент подготавливают наёмные работники, время разработки сокращается. Это дорогой способ, так как нагрузка падает на наёмных создателей контента. Заранее оцените ситуацию и придерживайтесь одного из подходов.
  • Нерешаемые ошибки. Это даже не проблема, а предубеждение. Разработка игры весьма сложный процесс. Бывает, что разработчик сталкивается с нерешаемыми проблемами (либо решаемыми крайне тяжело) и ему приходиться пересматривать чуть ли не весь проект. Психологически это очень трудно. Многие, даже самые упёртые разработчики, попав в такой «тупик», теряют энтузиазм и закрывают проект. Предубеждение в том, что все считают, что с ними такого не случиться. Совет один: будьте психологически готовы пересмотреть весь проект и выполнить работу заново.
  • Журналы. Совет лично от меня. Ведите три журнала:
    1. Журнал выполненной работы по проекту для отслеживания динамики разработки;
    2. Журнал идей по проекту, чтобы не забыть их и, если возможно, включить в концепцию;
    3. Журнал найденных багов и ошибок, которые необходимо исправить в будущем.

    Эти три журнала помогут избежать ошибок и двойной работы в процессе разработки.

  • Время. При определении времени, которое планируется на написание кода, часто делают одну ошибку – не учитывают затраты времени на исправление багов и отладку кода.
  • Авторство программного кода. Определённый «маразм» наблюдается, у некоторых программистов. Они считают, что обладают абсолютными правами на код, который ими написан, что даже после релиза игры они могут предъявить права на «часть кода» игры. Что бы защитить это «священное» право они могут пойти на всякие низости (программные бомбы, бакдоры, отказ от передачи исходных кодов и прочие). Мой совет прост – остерегайтесь таких неадекватных программистов. Программный код не контент. Доказать его авторство очень сложно. Поэтому нормальный программист сначала договаривается, что он получит за код; затем его пишет; потом передаёт разработчику и получает вознаграждение; после чего уже ни на что не претендует. Также должно быть организовано и в команде.

Тестирование

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

  • Тестирование сторонними людьми. В процессе разработки тестирование проводиться в основном своими средствами. Со временем глаза привыкают к имеющимся игровым моментам, вырабатываются умение работать в данной системе, короче, ошибки становятся менее заметными. Поэтому устраивайте периодические тесты продукта сторонними людьми, которые никогда не видели ваш продукт. Следите за их реакцией на различные игровые моменты, как они воспринимают меню игры и, вообще, расспросите их общее впечатление. Поверьте мне, один такой тест даст Вам очень большой объём полезной информации. Проводите их чаще, прислушивайтесь к обратной связи, и Вы получите на выходе хорошую игру.
  • Женщины. Когда-то я написал неплохую статью про женщин и игры. Смысл в том, что женщины видят всё по другому. Поэтому, желательно, чтобы в тесте игры участвовала хотя бы одна женщина (девушка), даже если игра не рассчитана на женскую аудиторию. Их обратная связь будет невероятно полезна.

Организационные моменты

  • Команда. Как Вы могли догадаться, созданием таких проектов, как игры, лучше заниматься командой. Это обосновано тем, что создание контента совершенно другой вид деятельности отличный от программирования и организации проекта и одному заниматься такими разными видами деятельности сложно. Минимальная команда – это два человека, создатель контента и разработчик. Чтобы не было непонимания, уточню – наёмный работник, по-моему, тоже в какой-то мере член команды.
    Конечно, можно заниматься разработкой и в одиночку. Есть такие «сумасшедшие», которые и пишут код, и рисуют графику, и сочиняют музыку, но это их проблемы.
    Собрать команду при наличии финансирования легко – форумы программистов и создателей контента, биржи фрилансеров Вам в помощь. При отсутствии финансирования можно найти только программиста, а вот нормального создателя контента не найдёте – здесь надо надеяться либо на себя, либо на удачу.
  • Юридическое оформление. Здесь всё просто. Хотите иметь с игры деньги и обезопасить себя от рейдерского захвата (когда кто-то внаглую ворует вашу игру), то вам нужно юридическое оформление на уровне индивидуального предпринимателя. Если Вы ещё собираетесь распределять проект по долям, то нужно оформление на уровне ООО. Поэтому если при поиске членов команды Вы обещаете долю в проекте, то не удивляйтесь, что Вас будут просить предъявить реквизиты вашей организации.
  • Контакты. Никогда не пренебрегайте знакомствами. Знакомьтесь с другими разработчиками, общайтесь и обменивайтесь контактами. В будущем, возможно, знакомство с ними принесёт Вам пользу.
  • Реклама. Так как игр на рынке много, то чтобы пользователи выбрали именно вашу игру, нужно привлечь к себе внимание. Делается это при помощи рекламы на различных ресурсах. Логично, что реклама эта требует: во-первых денег, а во-вторых, рекламный контент (банеры, видеоролики, статьи). Возможны и другие способы ¬– связи, спам, рекламные акции и прочие, но они не всегда эффективны.
    Без рекламы игра, точно также как и без контента, является провальным проектом. Обратите на это внимание. Но тут ситуация может достаточно легко исправлена инвестированием и сторонней помощью.
  • Инвестирование. Понятное дело, что с деньгами игру делать гораздо легче, но без развёрнутой концепции (с концепт-артом), команды и рабочего прототипа никто не будет финансировать ваш проект. То есть на начальных этапах на финансирование не надейтесь. А вот на последних этапах разработки ситуация может в корне поменяться – могут появиться инвесторы и Вам всё равно будут нужны деньги для организации рекламной компании.
    Чтобы найти инвесторов, собирайте контакты.
  • Сторонняя помощь. Вместо инвесторов можно найти стороннюю помощь (реклама за рекламу, помощь в распространении за процент и прочие). Тут надо ориентироваться по ситуации. Чтобы найти стороннюю помощь, так же собирайте контакты.

Послесловие

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

UPD: Статья получилась успешной, даже очень. Но в комментариях прослеживаются замечания по поводу отсуствия романтики и отвращения к играм. Поэтому я прокомментирую эти моменты.
Многие опускают тот факт, что статья для

начинающих

разработчиков игр, претендующих на роль

лидера

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

#Руководства


  • 0

Как программировать игры: языки, движки и все, что нужно знать начинающему разработчику

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

 vlada_maestro / shutterstock

Евгений Кучерявый

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

Главное — в самом начале узнать, что нас ждёт, чтобы потом не свернуть на полпути, пройти все этапы и выпустить релиз. Подробно всем тонкостям, навыкам и хитростям мы обучаем на курсе «Профессия разработчик игр на Unity». Здесь же рассмотрим первые шаги, которые ждут разработчика.

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

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

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

Подойдут любые, от Python и C до Pascal и Java. От выбора зависит то, сколько времени уйдёт на игру и для какой платформы будет релиз. Также язык влияет на производительность.

На C++, например, пишут для любой платформы, а вот PHP или JavaScript лучше подходят для браузерных игр. Если же вы используете один из движков, то лучше вдобавок изучать C# — на нём прописывают скрипты. Главное — не недооценивать языки. Движок Unity дружит и с JavaScript, а MineCraft был написан на Java.

Среди современных выделим:

CryEngine

Crysis, Far Cry, Sniper II: Ghost Warrior.

Unreal Engine

Gears of War 4, Dead Pool, Mortal Kombat X, Tekken 7

Unity

Outlast, Assassin’s Creed: Identity, Temple Run, Deus Ex: The Fall.

Большой популярностью пользуется Unity, он рассчитан как на 2D-, так и на 3D-игры. Он подходит под разные платформы и языки. На нём создается большинство мобильных и инди-игр. Он бесплатный, но если вы зарабатываете на своих играх больше 100 тысяч долларов в год, то придётся делиться ими с разработчиками Unity.

Допустим, вы выбрали язык и движок, составили план. Что дальше? Продумайте всё от и до. В зависимости от выбранного вами пути (чистый язык или использование движка) будет отличаться и то, что вас ждёт на разных этапах разработки.

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

Физика — это то, как мир игры реагирует на действия игрока или объектов внутри мира. Вот какие могут быть физические действия:

  • ходьба;
  • езда;
  • прыжки;
  • удары;
  • выстрелы;
  • падение предметов и так далее.

Если пишете сами, то для обычного прыжка придется:

  • проверить, находится ли игрок на земле;
  • менять координату Y, чтобы игрок поднимался вверх;
  • закончить подъём через какое-то время;
  • начинать и продолжать падение до тех пор, пока под ногами игрока не окажется земля.

Не говоря уже о том, что нужно работать над анимацией всего этого.

function jump() {
If(gamer.jumpTimer >= 1) {
   gamer.y++; 
   gamer.jumpTimer --;
   } else {
      If(gamer.y >= ground.y) {
         gamer.y--;
      }
   }
}

В движках уже прописана физика, и нужно лишь подогнать её под свои нужды. Для примера:

  • Вы поместили объект в рабочую область Unity.
  • Указали, что для него нужно использовать физику.
  • При запуске игры объект упадёт.

И для этого не придётся писать код вообще — всё уже предусмотрено.

Игровая механика — это то, какими способами игрок взаимодействует с миром. Совокупность игровых механик составляет игровой процесс. Например, вы уже реализовали возможность ходьбы и прыжков. Эта игра, скорее, платформер.

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

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

  • если добавить стрельбу, то будет экшн;
  • если игрок будет безоружен, — стелс;
  • если ещё и замки нужно взламывать, то это уже головоломка или пазл.

Будучи программистом, придётся уделять много времени механике.

Раньше графика создавалась с помощью программного кода, потом придумали текстуры и спрайты, а для 3D-игр используются модели. Подготовив все текстуры и модели, нужно добавить их в игру.

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

Пример анимации двумерного персонажа

Для анимации 2D-объектов создаётся текстура по типу той, что на изображении выше. Она разбивается на равные части, которые сменяют друг друга. То есть игрок сначала видит первый кадр, который потом сменяется на второй, а затем на третий — это создает иллюзию движения.

Анимация в действии

Если брать 3D-модель, то используется скелетная анимация — модель как бы нанизывается на специальный каркас (скелет) с подвижными частями. Движение этих частей прописывается в коде.

На скриншоте видно, как персонаж сгибает руку в местах с точками (вершинами). Таких точек может быть очень много, если требуется сложная анимация — жесты, мимика и так далее.

Создаётся анимация так: прописываются точки координат или захватываются движения реального актера.

Первый способ сложный, но дешёвый, потому что от программиста требуется только прописать движения — сдвинуть точку A1 на координаты (50,240).

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

Чтобы играть было интересно, нужен баланс. Это значит, что у каждого противника должны быть сильные и слабые стороны. Так геймплей не превратится в убийство одуванчиков или десятичасовые перестрелки с боссом.

Например, если в шутере есть оружие с большим уроном, у него должна быть долгая перезарядка. Если урон маленький, перезарядка может быть быстрой. У бронированных врагов малая подвижность, а шустрые противники умирают с нескольких ударов или выстрелов.

Если геймплей предусматривает взаимодействие с NPC, то им нужно прописать модели поведения: реакцию на действия игрока, агрессивность, возможность вести диалоги или торговать.

Работа с ИИ — одна из самых сложных, потому что стоит учитывать множество ситуаций, для которых задумана реакция. Например, когда вы пытаетесь пройти в дверь, ваш компаньон обязательно должен преградить вам путь, чтобы жизнь малиной не казалась.

Разобравшись с тем, как всё будет устроено в игре, можно приступать к разработке. Но чтобы проект был коммерчески успешен, выбирайте популярные платформы. Всего можно выделить четыре:

  • Персональные компьютеры.
  • Приставки.
  • Мобильные устройства.
  • Браузер.

У каждой из этих платформ своя аудитория с вполне конкретными предпочтениями. На мобильных устройствах предпочитают головоломки (2048, 94%, Cut the Rope), аркады (Subway Surf, Temple Run, Angry Birds) и казуалы (Talking Cat Tom, Kitty Kate Baby Care, Hair Stylist Fashion Salon).

На компьютерах играют в MMORPG (Lineage II, World of Warcraft, Skyrim) или шутеры (Battlefield, Call of Duty, Counter-Strike).

Приставки подходят для гонок (Need for Speed, Blur, Burnout Paradise), приключенческих игр (Assassin’s Creed, Portal, The Walking Dead) и так далее.

В браузерах собирают пазлы и строят фермы.

Конечно, можно сделать и головоломку для PS4, и гонку для браузера — никто никого не ограничивает.

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

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

Как зарабатывать больше с помощью нейросетей?
Бесплатный вебинар: 15 экспертов, 7 топ-нейросетей. Научитесь использовать ИИ в своей работе и увеличьте доход.

Узнать больше

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

К концу этой статьи вы сможете:

  • Рисовать предметы на экране
  • Воспроизводить звуковые эффекты и музыку
  • Обрабатывать пользовательский ввод
  • Реализовать циклы событий
  • Описать, чем программирование игр отличается от стандартного процедурного программирования на Python.

Предыстория и установка

pygame представляет собой оболочку Python для библиотеки SDL, что означает Simple DirectMedia Layer. SDL обеспечивает межплатформенный доступ к базовым мультимедийным аппаратным компонентам вашей системы, таким как звук, видео, мышь, клавиатура и джойстик. pygame начал жизнь как замена застопорившемуся проекту PySDL . Кроссплатформенный характер SDL pygame означает, что вы можете писать игры и многофункциональные мультимедийные программы Python для любой платформы, которая их поддерживает!

Для установки pygame на вашей платформе используйте соответствующую команду pip:

$ pip install pygame

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

$ python3 -m pygame.examples.aliens

Если появится окно с игрой, значит pygame установлен правильно! Если у вас возникнут проблемы, в руководстве по началу работы описаны некоторые известные проблемы и предостережения для всех платформ.

Базовая программа PyGame

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

# Simple pygame program

# Import and initialize the pygame library
import pygame
pygame.init()

# Set up the drawing window
screen = pygame.display.set_mode([500, 500])

# Run until the user asks to quit
running = True
while running:

# Did the user click the window close button?
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False

# Fill the background with white
screen.fill((255, 255, 255))

# Draw a solid blue circle in the center
pygame.draw.circle(screen, (0, 0, 255), (250, 250), 75)

# Flip the display
pygame.display.flip()

# Done! Time to quit.
pygame.quit()

Когда вы запустите эту программу, вы увидите окно, похожее на это:

Разберем этот код по частям:

  • Строки 4 и 5 импортируют и инициализируют библиотеку pygame. Без этих строк не будет pygame.
  • Строка 8 настраивает окно отображения вашей программы. Вы предоставляете либо список, либо кортеж, определяющий ширину и высоту создаваемого окна. Эта программа использует список для создания квадратного окна с 500 пикселями с каждой стороны.
  • Строки 11 и 12 настраивают игровой цикл для управления завершением программы. Позже в этом руководстве вы познакомитесь с игровыми циклами.
  • Строки с 15 по 17 сканируют и обрабатывают события внутри игрового цикла. Вы также доберетесь до событий немного позже. В этом случае обрабатывается только одно событие pygame.QUIT, которое происходит, когда пользователь нажимает кнопку закрытия окна.
  • Строка 20 заполняет окно сплошным цветом. screen.fill() принимает либо список, либо кортеж, определяющий значения RGB для цвета.
  • Строка 23 рисует круг в окне, используя следующие параметры: screen: окно, в котором будет происходить процесс(0, 0, 255): кортеж, содержащий значения цвета RGB.(250, 250): кортеж, определяющий координаты центра круга75: радиус круга для рисования в пикселях
  • Строка 26 обновляет содержимое дисплея на экране. Без этого вызова в окне ничего не появляется!
  • Строка 29 выходит из pygame. Это происходит только после завершения цикла.

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

Концепции PyGame

Поскольку библиотеки pygame и SDL переносимы на разные платформы и устройства, обе они должны определять абстракции и работать с ними для различных реалий аппаратного обеспечения. Понимание этих концепций и абстракций поможет вам проектировать и разрабатывать собственные игры.

Инициализация и модули

Библиотека pygame состоит из ряда конструкций Python, которые включают в себя несколько различных модулей . Эти модули обеспечивают абстрактный доступ к определенному оборудованию в вашей системе, а также унифицированные методы работы с этим оборудованием. Например, display обеспечивает единый доступ к вашему видеодисплею, а joystick позволяет абстрактно управлять вашим джойстиком.

После импорта библиотеки pygame из приведенного выше примера первое, что вы сделали, — это инициализировали PyGame с помощью pygame.init(). Эта функция вызывает отдельные init() функции всех включенных модулей pygame. Поскольку эти модули являются абстракциями для конкретного оборудования, этот шаг инициализации необходим, чтобы вы могли работать с одним и тем же кодом в Linux, Windows и Mac.

Дисплеи и поверхности

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

В pygame всё просматривается в одном созданном пользователем display, который может быть окном или полным экраном. Дисплей создается с помощью .set_mode(), который возвращает представление Surface видимой части окна. Surface — это то, что вы передаете в функции рисования, такие как pygame.draw.circle(), и содержимое этого Surface выталкивается на дисплей, когда вы вызываете pygame.display.flip().

Изображения и прямоугольники

Ваша базовая программа pygame рисовала фигуру прямо на экране Surface, но вы также можете работать с изображениями на диске. Модуль позволяет загружать и сохранять изображения image в различных популярных форматах. Изображения загружаются в объекты, которыми затем можно манипулировать и отображать различными способами.

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

Ладно, хватит теории. Давайте придумаем и напишем игру!

Базовый дизайн игры

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

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

Один мой бывший коллега , описывая программные проекты, говорил: «Вы не знаете, что делаете, пока не узнаете, чего не делаете». Имея это в виду, вот некоторые вещи, которые не будут рассмотрены в этом руководстве:

  • Нет нескольких жизней
  • Нет ведения счета
  • Нет возможности атаки игрока
  • Нет продвинутых уровней
  • Нет боссов

Вы можете попробовать свои силы в добавлении этих и других функций в свою программу.

Давайте начнем!

Импорт и инициализация PyGame

После импорта pygame вам также потребуется инициализировать его. Это позволяет подключить абстракции pygame к вашему конкретному оборудованию:

# Import the pygame module
import pygame

# Import pygame.locals for easier access to key coordinates
# Updated to conform to flake8 and black standards
from pygame.locals import (
K_UP,
K_DOWN,
K_LEFT,
K_RIGHT,
K_ESCAPE,
KEYDOWN,
QUIT,
)

# Initialize pygame
pygame.init()

Библиотека pygame определяет множество вещей помимо модулей и классов. Она также определяет некоторые локальные константы для таких вещей, как нажатия клавиш, движения мыши и атрибуты отображения. Вы ссылаетесь на эти константы, используя синтаксис pygame.<CONSTANT>. Импортируя определенные константы из pygame.locals, вы можете вместо этого использовать синтаксис <CONSTANT>. Это сэкономит вам несколько нажатий клавиш и улучшит общую читаемость.

Настройка дисплея

Теперь вам нужно то, на чём вы будете рисовать! Создайте экран , который будет общим холстом:

# Import the pygame module
import pygame

# Import pygame.locals for easier access to key coordinates
# Updated to conform to flake8 and black standards
from pygame.locals import (
K_UP,
K_DOWN,
K_LEFT,
K_RIGHT,
K_ESCAPE,
KEYDOWN,
QUIT,
)

# Initialize pygame
pygame.init()

# Define constants for the screen width and height
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

# Create the screen object
# The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))

Вы создаете экран для использования, вызывая pygame.display.set_mode() и передавая кортеж или список с желаемой шириной и высотой. В данном случае окно имеет размер 800×600, как определено константами SCREEN_WIDTH и SCREEN_HEIGHT в строках 20 и 21. Это часть окна, которой вы можете управлять, в то время как ОС управляет границами окна и строкой заголовка.

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

Настройка игрового цикла

Каждая игра от Pong до Fortnite использует игровой цикл для управления игровым процессом. Игровой цикл делает четыре очень важные вещи:

  • Обрабатывает пользовательский ввод
  • Обновляет состояние всех игровых объектов
  • Обновляет дисплей и аудиовыход
  • Поддерживает скорость игры

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

  • Игрок сталкивается с препятствием.
  • Игрок закрывает окно.

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

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

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

Каждое событие в pygame имеет связанный с ним тип события. В вашей игре типы событий, на которых вы сосредоточитесь, — это нажатия клавиш и закрытие окна. События нажатия клавиш имеют тип события KEYDOWN, а событие закрытия окна имеет тип QUIT. Различные типы событий также могут иметь другие связанные с ними данные. Например, у типа события KEYDOWN также есть переменная , вызываемая key для указания того, какая клавиша была нажата.

Вы получаете доступ к списку всех активных событий в очереди, вызывая pygame.event.get(). Затем вы просматриваете этот список, проверяете каждый тип события и отвечаете соответствующим образом:

# Variable to keep the main loop running
running = True

# Main loop
while running:
# Look at every event in the queue
for event in pygame.event.get():
# Did the user hit a key?
if event.type == KEYDOWN:
# Was it the Escape key? If so, stop the loop.
if event.key == K_ESCAPE:
running = False

# Did the user click the window close button? If so, stop the loop.
elif event.type == QUIT:
running = False

Давайте подробнее рассмотрим этот игровой цикл:

  • В строке 28 задается управляющая переменная для игрового цикла. Для выхода из цикла и игры вы устанавливаете running = False. Игровой цикл начинается в строке 29.
  • Строка 31 запускает обработчик событий, просматривая каждое событие, находящееся в данный момент в очереди. Если событий нет, то список пуст, и обработчик ничего не делает.
  • Строки с 35 по 38 проверяют, является ли текущее KEYDOWN событием. Если да, то программа проверяет, какая клавиша была нажата, по атрибуту event.key. Если ключом является Esc, обозначенный K_ESCAPE, то он выходит из игрового цикла, устанавливая running = False.
  • Строки 41 и 42 выполняют аналогичную проверку для типа события с именем QUIT. Это событие происходит только тогда, когда пользователь нажимает кнопку закрытия окна. Пользователь также может использовать любое другое действие операционной системы, чтобы закрыть окно.

Когда вы добавите эти строки к предыдущему коду и запустите его, вы увидите окно с пустым или черным экраном:

Окно не исчезнет, пока вы не нажмете клавишу Esc или иным образом не вызовете событие QUIT, закрыв окно.

Рисование на экране

В примере программы вы рисовали на экране с помощью двух команд:

  • screen.fill() — заполнить фон
  • pygame.draw.circle() — нарисовать круг

Теперь вы узнаете о третьем способе рисования на экране: использовании файла Surface.

Напомним, что Surface— это прямоугольный объект, на котором можно рисовать как, например, на чистом листе бумаги. Объект screen представляет собой Surface, и вы можете создавать свои собственные Surface — объекты отдельно от экрана дисплея. Давайте посмотрим, как это работает:

# Fill the screen with white
screen.fill((255, 255, 255))

# Create a surface and pass in a tuple containing its length and width
surf = pygame.Surface((50, 50))

# Give the surface a color to separate it from the background
surf.fill((0, 0, 0))
rect = surf.get_rect()

После того, как экран заполнен белым цветом в строке 45, в строке 48 создается новый Surface. Он имеет ширину 50 пикселей, высоту 50 пикселей и назначается surf. На этом этапе вы относитесь к нему так же, как к файлу screen. Итак, в строке 51 вы заполняете его черным цветом. Вы также можете получить доступ к его основе, используя .get_rect(). Это сохраняет rect для последующего использования.

Использование .blit() и .flip()

Просто создать новое Surface недостаточно, чтобы увидеть его на экране. Для этого вам нужно скопировать на Surface другой Surface. Термин blit означает Block Transfer, а .blit() означает, как вы копируете содержимое одного Surface в другое. Вот как происходит процесс рисования surf на экране:

# This line says «Draw surf onto the screen at the center»
screen.blit(surf, (SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
pygame.display.flip()

Вызов .blit() в строке 55 принимает два аргумента:

  • Рисование Surface_
  • Место, в котором нужно нарисовать его

Координаты (SCREEN_WIDTH/2, SCREEN_HEIGHT/2) говорят вашей программе разместить surf точно в центре экрана, но это не совсем так:

Причина, по которой изображение выглядит не по центру, заключается в том, что верхний левый угол не помещает .blit() в указанное место. Если вы хотите быть в центре, то вам придется сделать некоторые математические расчеты, чтобы сместить его вверх и влево. Вы можете сделать это, вычитая ширину и высоту из ширины и высоты экрана, разделив каждую на 2, чтобы найти центр, а затем передав эти числа в качестве аргументов :surfsurfsurfscreen.blit()

# Put the center of surf at the center of the display
surf_center = (
(SCREEN_WIDTH-surf.get_width())/2,
(SCREEN_HEIGHT-surf.get_height())/2
)

# Draw surf at the new coordinates
screen.blit(surf, surf_center)
pygame.display.flip()

Обратите внимание на вызов pygame.display.flip() после вызова blit(). Это обновляет весь экран с момента последнего перелистывания. Без вызова .flip() ничего не будет отображаться.

Спрайты

В вашем игровом дизайне игрок начинает слева, а препятствия появляются справа. Вы можете изобразить все препятствия Surface объектами, чтобы упростить рисование, но как узнать, где их рисовать? Как узнать, столкнулось ли препятствие с игроком? Что происходит, когда препятствие улетает за пределы экрана? Что делать, если вы хотите рисовать фоновые изображения, которые также движутся? Что делать, если вы хотите, чтобы ваши изображения были анимированными? Вы можете справиться со всеми этими и другими ситуациями с помощью спрайтов .

В терминах программирования спрайт — это двухмерное представление чего-либо на экране. По сути, это картинка. pygame предоставляет класс Sprite , предназначенный для хранения одного или нескольких графических представлений любого игрового объекта, который вы хотите отобразить на экране. Чтобы использовать его, вы создаёте новый класс, который расширяет Sprite. Это позволяет использовать его встроенные методы.

Игроки

Вот как вы используете объекты Sprite в текущей игре для определения игрока. Вставьте этот код после строки 18:

# Define a Player object by extending pygame.sprite.Sprite
# The surface drawn on the screen is now an attribute of ‘player’
class Player(pygame.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
self.surf = pygame.Surface((75, 25))
self.surf.fill((255, 255, 255))
self.rect = self.surf.get_rect()

Сначала вы определяете Player, расширяя строку 22 pygame.sprite.Sprite. Затем используете .__init__() .super()для вызова метода.

Далее вы определяете и инициализируете сохранение изображения .surf для отображения, которое в настоящее время является белым прямоугольником. Вы также определяете и инициализируете .rect, которые позже будете использовать для рисования игрока. Чтобы использовать этот новый класс, вам нужно создать новый объект и изменить код рисования. В блоке ниже представлено всё, что у нас есть на данном этапе:

# Import the pygame module
import pygame

# Import pygame.locals for easier access to key coordinates
# Updated to conform to flake8 and black standards
from pygame.locals import (
K_UP,
K_DOWN,
K_LEFT,
K_RIGHT,
K_ESCAPE,
KEYDOWN,
QUIT,
)

# Define constants for the screen width and height
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

# Define a player object by extending pygame.sprite.Sprite
# The surface drawn on the screen is now an attribute of ‘player’
class Player(pygame.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
self.surf = pygame.Surface((75, 25))
self.surf.fill((255, 255, 255))
self.rect = self.surf.get_rect()

# Initialize pygame
pygame.init()

# Create the screen object
# The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))

# Instantiate player. Right now, this is just a rectangle.
player = Player()

# Variable to keep the main loop running
running = True

# Main loop
while running:
# for loop through the event queue
for event in pygame.event.get():
# Check for KEYDOWN event
if event.type == KEYDOWN:
# If the Esc key is pressed, then exit the main loop
if event.key == K_ESCAPE:
running = False
# Check for QUIT event. If QUIT, then set running to false.
elif event.type == QUIT:
running = False

# Fill the screen with black
screen.fill((0, 0, 0))

# Draw the player on the screen
screen.blit(player.surf, (SCREEN_WIDTH/2, SCREEN_HEIGHT/2))

# Update the display
pygame.display.flip()

Запустите этот код. Вы увидите белый прямоугольник примерно посередине экрана:

Как вы думаете, что произойдет, если вы замените строку 59 на screen.blit(player.surf, player.rect)? Попробуйте и посмотрите:

# Fill the screen with black
screen.fill((0, 0, 0))

# Draw the player on the screen
screen.blit(player.surf, player.rect)

# Update the display
pygame.display.flip()

Когда вы передаете Rectto .blit(), он использует координаты верхнего левого угла для рисования поверхности. Вы будете использовать это позже, чтобы заставить вашего игрока двигаться!

Пользовательский ввод

Итак, вы узнали, как создавать pygame и рисовать объекты на экране. Теперь начинается настоящее веселье! Вы сделаете плеер управляемым с помощью клавиатуры.

Ранее вы видели, что pygame.event.get() возвращает список событий в очереди, которую вы сканируете на наличие типов событий KEYDOWN. Ну, это не единственный способ читать нажатия клавиш. pygame также предоставляет pygame.event.get_pressed(), который возвращает словарь , содержащий все текущие события в очереди KEYDOWN.

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

# Get the set of keys pressed and check for user input
pressed_keys = pygame.key.get_pressed()

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

# Move the sprite based on user keypresses
def update(self, pressed_keys):
if pressed_keys[K_UP]:
self.rect.move_ip(0, -5)
if pressed_keys[K_DOWN]:
self.rect.move_ip(0, 5)
if pressed_keys[K_LEFT]:
self.rect.move_ip(-5, 0)
if pressed_keys[K_RIGHT]:
self.rect.move_ip(5, 0)

K_UP, K_DOWN, K_LEFT и K_RIGHT соответствуют клавишам со стрелками на клавиатуре. Если словарная статья для этой клавиши — True, то эта клавиша нажата, и вы перемещаете игрока в правильном направлении. Здесь вы используете .move_ip(), что означает перемещение на месте , чтобы переместить текущий Rect.

Затем вы можете вызвать каждый кадр .update() для перемещения спрайта игрока в ответ на нажатия клавиш. Добавьте этот вызов сразу после вызова .get_pressed():

# Main loop
while running:
# for loop through the event queue
for event in pygame.event.get():
# Check for KEYDOWN event
if event.type == KEYDOWN:
# If the Esc key is pressed, then exit the main loop
if event.key == K_ESCAPE:
running = False
# Check for QUIT event. If QUIT, then set running to false.
elif event.type == QUIT:
running = False

# Get all the keys currently pressed
pressed_keys = pygame.key.get_pressed()

# Update the player sprite based on user keypresses
player.update(pressed_keys)

# Fill the screen with black
screen.fill((0, 0, 0))

Теперь вы можете перемещать прямоугольник вашего плеера по экрану с помощью клавиш со стрелками:

Далее необходимо решить 2 проблемы:

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

Чтобы игрок оставался на экране, вам нужно добавить некоторую логику, чтобы определить, собирается ли rect уйти за пределы экрана. Для этого вы проверяете, не вышли ли координаты за пределы экрана. Если это так, то вы указываете программе переместить его обратно к краю:

# Move the sprite based on user keypresses
def update(self, pressed_keys):
if pressed_keys[K_UP]:
self.rect.move_ip(0, -5)
if pressed_keys[K_DOWN]:
self.rect.move_ip(0, 5)
if pressed_keys[K_LEFT]:
self.rect.move_ip(-5, 0)
if pressed_keys[K_RIGHT]:
self.rect.move_ip(5, 0)

# Keep player on the screen
if self.rect.left < 0:
self.rect.left = 0
if self.rect.right > SCREEN_WIDTH:
self.rect.right = SCREEN_WIDTH
if self.rect.top <= 0:
self.rect.top = 0
if self.rect.bottom >= SCREEN_HEIGHT:
self.rect.bottom = SCREEN_HEIGHT

Здесь вместо использования .move() вы просто меняете соответствующие координаты .top, .bottom, .leftили .right напрямую. Проверьте это, и вы обнаружите, что прямоугольник игрока больше не может перемещаться за пределы экрана.

Теперь давайте добавим врагов!

Враги

Какая игра без врагов? Вы будете использовать методы, которые вы уже изучили, чтобы создать базовый класс врагов, а затем создать множество из них, чтобы ваш игрок их избегал. Сначала импортируйте библиотеку random:

# Import random for random numbers
import random

Затем создайте новый класс спрайтов с именем Enemy, следуя тому же шаблону, который вы использовали для Player:

# Define the enemy object by extending pygame.sprite.Sprite
# The surface you draw on the screen is now an attribute of ‘enemy’
class Enemy(pygame.sprite.Sprite):
def __init__(self):
super(Enemy, self).__init__()
self.surf = pygame.Surface((20, 10))
self.surf.fill((255, 255, 255))
self.rect = self.surf.get_rect(
center=(
random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
random.randint(0, SCREEN_HEIGHT),
)
)
self.speed = random.randint(5, 20)

# Move the sprite based on speed
# Remove the sprite when it passes the left edge of the screen
def update(self):
self.rect.move_ip(-self.speed, 0)
if self.rect.right < 0:
self.kill()

Есть четыре заметных различия между Enemy и Player:

  • В строках с 62 по 67 вы обновляете случайное место вдоль правого края экрана. Центр прямоугольника находится за пределами экрана. Он расположен в некотором положении между 20 и 100 пикселями от правого края и где-то между верхним и нижним краями.
  • В строке 68 вы определяете .speed как случайное число от 5 до 20. Это указывает, как быстро враг движется к игроку.
  • В строках с 73 по 76 вы определяете .update(). Этот метод не требует аргументов, так как враги двигаются автоматически. Вместо этого .update() перемещает врага к левой стороне экрана.
  • В строке 74 вы проверяете, ушел ли враг за пределы экрана. Чтобы убедиться, что значок Enemy полностью исчез с экрана и не исчезнет, пока он все еще виден, убедитесь, что правая часть экрана .rect вышла за левую часть экрана. Как только враг исчезает с экрана, вы вызываете .kill(), чтобы предотвратить его дальнейшую обработку.

Итак, что делает .kill()? Чтобы понять это, вы должны знать о группах спрайтов .

Группы спрайтов

Еще один очень полезный класс, предоставляющий возможности — Sprite Group. Это объект, который содержит группу объектов Sprite. Так зачем его использовать? Разве вы не можете вместо этого просто отслеживать свои объекты в списке? Ну, можете, но преимущество использования заключается в методах Group, которые он раскрывает. Эти методы помогают определить, не столкнулся ли кто-либо с файлом Player, что значительно упрощает обновление.

Давайте посмотрим, как создавать группы спрайтов. Вы создадите два разных объекта Group:

  • Первый Group проведет каждый Sprite в игре.
  • Второй Group будет содержать только объекты Enemy.

Вот как это выглядит в коде:

# Create the ‘player’
player = Player()

# Create groups to hold enemy sprites and all sprites
# — enemies is used for collision detection and position updates
# — all_sprites is used for rendering
enemies = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
all_sprites.add(player)

# Variable to keep the main loop running
running = True

Когда вы вызываете .kill(), Sprite удаляется из всех Group, которым он принадлежит. Это также удаляет ссылки на, что позволяет сборщику мусора Python освобождать память по мере необходимости.

Теперь, когда у вас есть группа all_sprites, вы можете изменить способ рисования объектов. Вместо того, чтобы вызывать .blit(), вы можете перебрать всё в all_sprites:

# Fill the screen with black
screen.fill((0, 0, 0))

# Draw all sprites
for entity in all_sprites:
screen.blit(entity.surf, entity.rect)

# Flip everything to the display
pygame.display.flip()

Теперь all_sprites с каждым кадром будет прорисовываться что угодно, будь то враг или игрок.

Есть только одна проблема… У тебя нет врагов! Вы можете создать кучу врагов в начале игры, но игра быстро станет скучной, когда все они покинут экран через несколько секунд. Вместо этого давайте рассмотрим, как поддерживать постоянный приток врагов по ходу игры.

Пользовательские события

Дизайн требует, чтобы враги появлялись через равные промежутки времени. Это означает, что через определенные промежутки времени вам нужно делать две вещи:

  • Создавать новый Enemy.
  • Добавить его в all_sprites и enemies.

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

Давайте посмотрим, как создать пользовательское событие, которое генерируется каждые несколько секунд:

# Create the screen object
# The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))

# Create a custom event for adding a new enemy
ADDENEMY = pygame.USEREVENT + 1
pygame.time.set_timer(ADDENEMY, 250)

# Instantiate player. Right now, this is just a rectangle.
player = Player()

pygame внутренне определяет события как целые числа, поэтому вам необходимо определить новое событие с уникальным целым числом. Последнее резервное событие pygame называется USEREVENT, поэтому определение ADDENEMY = pygame.USEREVENT + 1 в строке 83 гарантирует его уникальность.

Затем вам нужно вставить это новое событие в очередь через равные промежутки времени на протяжении всей игры. Вот тут-то и появляется модуль time. Строка 84 запускает новое событие ADDENEMY каждые 250 миллисекунд, или четыре раза в секунду. Вы вызываете .set_timer() вне игрового цикла, так как вам нужен только один таймер, но он будет срабатывать на протяжении всей игры.

Добавьте код для обработки вашего нового события:

# Main loop
while running:
# Look at every event in the queue
for event in pygame.event.get():
# Did the user hit a key?
if event.type == KEYDOWN:
# Was it the Escape key? If so, stop the loop.
if event.key == K_ESCAPE:
running = False

# Did the user click the window close button? If so, stop the loop.
elif event.type == QUIT:
running = False

# Add a new enemy?
elif event.type == ADDENEMY:
# Create the new enemy and add it to sprite groups
new_enemy = Enemy()
enemies.add(new_enemy)
all_sprites.add(new_enemy)

# Get the set of keys pressed and check for user input
pressed_keys = pygame.key.get_pressed()
player.update(pressed_keys)

# Update enemy position
enemies.update()

Всякий раз, когда обработчик событий видит новое событие ADDENEMY в строке 115, он создает Enemy и добавляет его в enemies и all_sprites. Поскольку Enemy находится в all_sprites, он будет отображаться в каждом кадре. Вам также нужно вызвать строку 126 enemies.update(), которая обновляет все в enemies, чтобы убедиться, что они перемещаются правильно:

Однако это не единственная причина, по которой существует группа только для enemies.

Обнаружение столкновений

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

В этом уроке вы будете использовать метод под названием .spritecollideany(), который читается как «столкновение спрайтов». Этот метод принимает Sprite и Group в качестве параметров. Он просматривает каждый объект в Group и проверяет, пересекается ли .rect с объектом .rect в Sprite. Если да, то возвращается True. В противном случае возвращается False. Это идеально подходит для этой игры, так как вам нужно проверить, не сталкивается ли сингл player с одним из Group enemies.

Вот как это выглядит в коде:

# Draw all sprites
for entity in all_sprites:
screen.blit(entity.surf, entity.rect)

# Check if any enemies have collided with the player
if pygame.sprite.spritecollideany(player, enemies):
# If so, then remove the player and stop the loop
player.kill()
running = False

Строка 135 проверяет, не столкнулся ли player с каким-либо из объектов в enemies. Если это так, то player.kill() вызывается удалить его из каждой группы, к которой он принадлежит. Поскольку рендерятся только объекты в all_sprites, они больше не будут этого делать. Как только игрок был убит, вам также нужно выйти из игры, поэтому вы устанавливаете running = False в строке 138.

На данный момент у вас есть основные элементы игры:

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

Изображения спрайтов

Хорошо, у вас есть игра, но давайте будем честными… Она немного уродлива. Игрок и враги — это просто белые блоки на черном фоне. Это было ультрасовременно, когда Pong был новым, но сейчас он просто не подходит. Давайте заменим все эти скучные белые прямоугольники более крутыми изображениями, которые сделают игру похожей на настоящую.

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

Изменение конструкторов объектов

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

# Import pygame.locals for easier access to key coordinates
# Updated to conform to flake8 and black standards
# from pygame.locals import *
from pygame.locals import (
RLEACCEL,
K_UP,
K_DOWN,
K_LEFT,
K_RIGHT,
K_ESCAPE,
KEYDOWN,
QUIT,
)

# Define constants for the screen width and height
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

# Define the Player object by extending pygame.sprite.Sprite
# Instead of a surface, use an image for a better-looking sprite
class Player(pygame.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
self.surf = pygame.image.load(«jet.png»).convert()
self.surf.set_colorkey((255, 255, 255), RLEACCEL)
self.rect = self.surf.get_rect()

Давайте немного распакуем строку 31 pygame.image.load(), которая загружает образ с диска. Вы передаете её путь к файлу. Она возвращает Surface, а вызов .convert() оптимизирует Surface, ускоряя будущие вызовы .blit().

Строка 32 .set_colorkey() используется для указания того, что цвет pygame будет отображаться как прозрачный. В этом случае вы выбираете белый, потому что это цвет фона изображения. Константа RLEACCEL — это необязательный параметр, который помогает pygame ускорить отрисовку на неускоренных дисплеях. Это добавляется в pygame.locals оператор импорта в строке 11.

Больше ничего не нужно менять. Изображение по-прежнему Surface, за исключением того, что теперь на нем что-то нарисовано. Вы все еще используете его таким же образом.

Вот как выглядят похожие изменения Enemy:

# Define the enemy object by extending pygame.sprite.Sprite
# Instead of a surface, use an image for a better-looking sprite
class Enemy(pygame.sprite.Sprite):
def __init__(self):
super(Enemy, self).__init__()
self.surf = pygame.image.load(«missile.png»).convert()
self.surf.set_colorkey((255, 255, 255), RLEACCEL)
# The starting position is randomly generated, as is the speed
self.rect = self.surf.get_rect(
center=(
random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
random.randint(0, SCREEN_HEIGHT),
)
)
self.speed = random.randint(5, 20)

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

Добавление фоновых изображений

Для фоновых облаков используются те же принципы, что и для Player и Enemy:

  • Создайте класс Cloud.
  • Добавьте к нему изображение облака.
  • Создайте метод .update(), который перемещает cloud к левой стороне экрана.
  • Создайте пользовательское событие и обработчик для создания новых объектов cloud с заданным интервалом времени.
  • Добавьте вновь созданные объекты cloud в новый файл Group с именем clouds.
  • Обновите и нарисуйте clouds в своем игровом цикле.

Вот как это выглядит:

# Define the cloud object by extending pygame.sprite.Sprite
# Use an image for a better-looking sprite
class Cloud(pygame.sprite.Sprite):
def __init__(self):
super(Cloud, self).__init__()
self.surf = pygame.image.load(«cloud.png»).convert()
self.surf.set_colorkey((0, 0, 0), RLEACCEL)
# The starting position is randomly generated
self.rect = self.surf.get_rect(
center=(
random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
random.randint(0, SCREEN_HEIGHT),
)
)

# Move the cloud based on a constant speed
# Remove the cloud when it passes the left edge of the screen
def update(self):
self.rect.move_ip(-5, 0)
if self.rect.right < 0:
self.kill()

Это должно выглядеть очень знакомо. Это почти то же самое, что и Enemy.

Чтобы облака появлялись через определенные промежутки времени, вы будете использовать код создания событий, аналогичный тому, который вы использовали для создания новых врагов. Поместите его прямо под событием создания врага:

# Create custom events for adding a new enemy and a cloud
ADDENEMY = pygame.USEREVENT + 1
pygame.time.set_timer(ADDENEMY, 250)
ADDCLOUD = pygame.USEREVENT + 2
pygame.time.set_timer(ADDCLOUD, 1000)

Это говорит о том, что нужно подождать 1000 миллисекунд или одну секунду, прежде чем создавать следующий файл cloud.

Затем создайте новый Group для хранения каждого вновь созданного cloud:

# Create groups to hold enemy sprites, cloud sprites, and all sprites
# — enemies is used for collision detection and position updates
# — clouds is used for position updates
# — all_sprites is used for rendering
enemies = pygame.sprite.Group()
clouds = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
all_sprites.add(player)

Затем добавьте обработчик нового события ADDCLOUD в обработчик событий:

# Main loop
while running:
# Look at every event in the queue
for event in pygame.event.get():
# Did the user hit a key?
if event.type == KEYDOWN:
# Was it the Escape key? If so, then stop the loop.
if event.key == K_ESCAPE:
running = False

# Did the user click the window close button? If so, stop the loop.
elif event.type == QUIT:
running = False

# Add a new enemy?
elif event.type == ADDENEMY:
# Create the new enemy and add it to sprite groups
new_enemy = Enemy()
enemies.add(new_enemy)
all_sprites.add(new_enemy)

# Add a new cloud?
elif event.type == ADDCLOUD:
# Create the new cloud and add it to sprite groups
new_cloud = Cloud()
clouds.add(new_cloud)
all_sprites.add(new_cloud)

Наконец, убедитесь, что clouds обновляются каждый кадр:

# Update the position of enemies and clouds
enemies.update()
clouds.update()

# Fill the screen with sky blue
screen.fill((135, 206, 250))

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

Обратите внимание, что каждый новый Cloud и Enemy добавляется all_sprites так же, как clouds и enemies. Это сделано потому, что каждая группа используется для отдельной цели:

  • Рендеринг выполняется с помощью all_sprites.
  • Обновление позиции выполняется с помощью clouds и enemies.
  • Обнаружение столкновений выполняется с помощью enemies.

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

Скорость игры

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

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

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

Использование Clock для установки воспроизводимой частоты кадров требует всего две строки кода. Первый создает новый Clock перед началом игрового цикла:

# Setup the clock for a decent framerate
clock = pygame.time.Clock()

Второй вызывает .tick(), чтобы сообщить pygame, что программа достигла конца кадра:

# Flip everything to the display
pygame.display.flip()

# Ensure program maintains a rate of 30 frames per second
clock.tick(30)

Переданный аргумент .tick() устанавливает желаемую частоту кадров. Для этого .tick() вычисляется количество миллисекунд, которое должен занимать каждый кадр, исходя из желаемой частоты кадров. Затем он сравнивает это число с количеством миллисекунд, прошедших с момента последнего вызова .tick(). Если прошло недостаточно времени, .tick()задерживает обработку, чтобы гарантировать, что она никогда не превысит указанную частоту кадров.

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

Поэкспериментируйте с этим числом, чтобы понять, что вам больше подходит!

Звуковые эффекты

До сих пор вы были сосредоточены на игровом процессе и визуальных аспектах вашей игры. Теперь давайте рассмотрим, как придать вашей игре слуховой оттенок. pygame обеспечивает обработку всех действий mixer, связанных со звуком. Вы будете использовать классы и методы этого модуля для предоставления фоновой музыки и звуковых эффектов для различных действий.

Название mixer относится к тому факту, что модуль смешивает различные звуки в единое целое. С помощью подмодуля music вы можете транслировать отдельные звуковые файлы в различных форматах, таких как MP3 , Ogg и Mod . Вы также можете использовать Sound для хранения одного звукового эффекта, который будет воспроизводиться в формате Ogg или несжатом формате WAV . Все воспроизведение происходит в фоновом режиме, поэтому при воспроизведении Sound метод возвращает значение сразу после воспроизведения звука.

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

Как и в большинстве случаев pygame, использование mixer начинается с шага инициализации. К счастью, этим уже занимается pygame.init(). Вам нужно только вызвать pygame.mixer.init(), если вы хотите изменить значения по умолчанию:

# Setup for sounds. Defaults are good.
pygame.mixer.init()

# Initialize pygame
pygame.init()

# Set up the clock for a decent framerate
clock = pygame.time.Clock()

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

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

# Load and play background music
# Sound source: http://ccmixter.org/files/Apoxode/59262
# License: https://creativecommons.org/licenses/by/3.0/
pygame.mixer.music.load(«Apoxode_-_Electric_1.mp3»)
pygame.mixer.music.play(loops=-1)

# Load all sound files
# Sound sources: Jon Fincher
move_up_sound = pygame.mixer.Sound(«Rising_putter.ogg»)
move_down_sound = pygame.mixer.Sound(«Falling_putter.ogg»)
collision_sound = pygame.mixer.Sound(«Collision.ogg»)

Строки 138 и 139 загружают фоновый звуковой клип и начинают его воспроизведение. Вы можете указать звуковому клипу зацикливаться и никогда не заканчиваться, установив именованный параметр loops=-1.

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

Итак, как вы используете звуковые эффекты? Вы хотите воспроизводить каждый звук, когда происходит определенное событие. Например, когда корабль движется вверх, вы хотите сыграть move_up_sound. Поэтому вы добавляете вызов .play() всякий раз, когда обрабатываете это событие. В дизайне это означает добавление следующих вызовов .update() для Player:

# Define the Player object by extending pygame.sprite.Sprite
# Instead of a surface, use an image for a better-looking sprite
class Player(pygame.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
self.surf = pygame.image.load(«jet.png»).convert()
self.surf.set_colorkey((255, 255, 255), RLEACCEL)
self.rect = self.surf.get_rect()

# Move the sprite based on keypresses
def update(self, pressed_keys):
if pressed_keys[K_UP]:
self.rect.move_ip(0, -5)
move_up_sound.play()
if pressed_keys[K_DOWN]:
self.rect.move_ip(0, 5)
move_down_sound.play()

При столкновении между игроком и врагом вы воспроизводите звук, вызываемый при обнаружении столкновений:

# Check if any enemies have collided with the player
if pygame.sprite.spritecollideany(player, enemies):
# If so, then remove the player
player.kill()

# Stop any moving sounds and play the collision sound
move_up_sound.stop()
move_down_sound.stop()
collision_sound.play()

# Stop the loop
running = False

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

Наконец, когда игра закончится, все звуки должны прекратиться. Это верно независимо от того, заканчивается ли игра из-за столкновения или пользователь выходит вручную. Для этого в конце программы после цикла добавьте следующие строки:

# All done! Stop and quit the mixer.
pygame.mixer.music.stop()
pygame.mixer.quit()

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

Вот и все! Проверьте это снова, и вы должны увидеть что-то вроде этого:

Примечание об источниках

Вы могли заметить комментарий к строкам 136-137 при загрузке фоновой музыки, в котором указан источник музыки и ссылка на лицензию Creative Commons. Это было сделано потому, что этого требовал создатель этого звука. В лицензионных требованиях говорилось, что для использования звука необходимо указать как правильное указание авторства, так и ссылку на лицензию.

Вот несколько источников музыки, звука и изображений, в которых вы можете найти полезный контент:

  • OpenGameArt.org: звуки, звуковые эффекты, спрайты и другие изображения.
  • Kenney.nl: звуки, звуковые эффекты, спрайты и другие иллюстрации.
  • Gamer Art 2D: спрайты и другие арты
  • CC Mixter: звуки и звуковые эффекты
  • Freesound: звуки и звуковые эффекты

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

Заключение

Из этого руководства вы узнали, чем программирование игр pygame отличается от стандартного процедурного программирования. Вы также узнали, как:

  • Реализовать циклы событий
  • Рисовать предметы на экране
  • Воспроизводить звуковые эффекты и музыку
  • Обрабатывать пользовательский ввод

Для этого вы использовали подмножество модулей pygame, включая модули display, mixer и music, time, image, event, и key. Вы также использовали несколько классов pygame, в том числе Rect, Surface, Sound и Sprite. Но это только царапает поверхность того, что в pygame можно сделать! Ознакомьтесь с официальной документацией pygame для получения полного списка доступных модулей и классов.

Вы можете найти весь код, графические и звуковые файлы для этой статьи, нажав на ссылку ниже:

Спасибо за прочтение данной статьи!

Как программировать игры

Оглавление:

Какой язык нужен для игр
Движки и языки, на которых они написаны
Как выбрать платформу 
Основные компоненты
Как строится код
Этапы создания
С чего начать разработку
Что полезного посмотреть
Коротко о главном

Главный вопрос в следующем: вы хотите создать одну игру и забыть о ней или сделать карьеру в геймдеве?

Если вас интересует только создание простой игры, то вам не обязательно изучать десятки языков, движков и технологий: существует множество готовых способов создания игр для начинающих разработчиков, например, на JavaScript и Pygame. Если это как раз ваш сценарий — нет никакой необходимости изучать C++.

Хочу стать профессионалом в геймдеве

Если вы хотите сделать карьеру в геймдеве, то учить C++ (например) — точно придётся, это лишь вопрос времени. Знание этого языка поможет вам, даже если вы не будете работать на нем регулярно. Как только вы освоите C++, вы сможете легко перейти к другим языкам, которые как раз и используются в программировании игр. И это не только языки группы С.

Какой язык программирования нужен

Языков — очень много. Но профессионалам геймдева, в конце концов, требуется 2-3 языка, не больше. В любом случае, понадобится C++ — это самый важный язык программирования в индустрии игр. Другие языки, которые вы должны знать: Python — для сред сборки, C# — для написания инструментов и Lua — для написания сценариев. 

Пример скриптинга на Lua

Пример скриптинга на Lua

Но если вам нужно сконцентрироваться на чём-то одном и то, что 100% пригодится в дальнейшем — это C++. Единственное исключение из этого правила: если вы хотите писать игры для устройств iOS (iPhone и iPad) — учите Objective-C.

Вот самые популярные инструменты, которые сегодня применяются в геймдеве:

  • Unity.
  • CryEngine.
  • RPG Maker.
  • Unreal.
  • LibGDX.
  • Godot.
  • Game Maker (только 2D)

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

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

Пример элементарной игры на C++

Пример элементарной игры на C++

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

Остались вопросы?

Укажите ваши данные, и мы вам перезвоним

Игровые движки

Для программирования игр пригодны далеко не все языки. Например, тот же Python — предназначен для удобства чтения. И на самом деле он используется в геймдеве, но для второстепенных задач (например, для создания прототипов игровых идей). Для разработки конечного продукта — Python не подходит. Ниже мы разобрали пять языков, которые точно пригодятся разработчику игр.

Lua

Легкий скриптовый язык, который используется во многих отраслях, включая гейм-разработку. Lua прост в изучении и использовании, а также быстр (что, кстати, является необычным сочетанием).

Скрипты Lua часто используются с мощными движками, например, с Unreal Engine

Скрипты Lua часто используются с мощными движками, например, с Unreal Engine

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

Хотя некоторые игровые движки поддерживают Lua, он не так широко используется, как другие языки, но все же — это отличный выбор для создания 2D-игр.

Популярные игровые движки, использующие Lua:

  • Solar2D.
  • LÖVE.
  • Gideros.

Недостатки Lua:

  • Используется не повсеместно.
  • Ограничен индустрией.

C++

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

В чем разница между С++ и C#

C++ — это реально очень сложный язык для обучения, поэтому начните свои первые шаги с хорошего игрового движка. Создавать игровые движки — также очень сложно, но вот писать игры поверх готовых движков — гораздо проще. Существует множество бесплатных движков, хотя большинство из них — это просто рендеры 3D-графики. 

C++ априори будет быстрее, чем C#, но для запуска игры он требует больше времени. С++ также может показаться более сложным (например, из-за указателей).

И в С++ вообще очень много вещей, которые могут сбить с толку, если вы не разбираетесь в программных компонентах компьютера. Если вы не разбираетесь в аппаратной части и не хотите разбираться — лучше выбрать C#.

Почему С++ быстрее

Будучи компилируемым языком, C++ преобразуется в машинный код перед запуском на компьютере. Благодаря этому программы на C++ выполняются быстрее, чем на интерпретируемых языках, таких как Python. 

Пример простой игры на С++

Пример простой игры на С++

C++ также предлагает различные парадигмы программирования (объектно-ориентированное и на основе данных). Многие из самых популярных игровых движков поддерживают C++, а это значит, что существует множество инструментов и готовых библиотек, которые разработчики могут использовать для создания своих игр.

Популярные движки, использующие C++:

  • Unreal Engine.
  • Unity.
  • CRYENGINE.
  • Amazon Lumberyard.

Минусы C++:

  • Сложно изучать.
  • Небезопасное управление памятью.
  • Не кроссплатформенный. Да, вы можете написать код на C++, который будет компилироваться на разных платформах, однако, скомпилированные двоичные файлы не будут кроссплатформенными. Кроме того, многие функции и библиотеки зависят от платформы. Так что, написание действительно кроссплатформенного кода на C++ — будет ограничено.

Язык C#

Обладает многими теми же преимуществами, что и C++. Особенно хорошо подойдет для разработки 2D- и 3D.

Одним из плюсов C# перед C++ является фактор простоты: его легче изучить. C# также является кроссплатформенным языком, что означает, что игры, разработанные на C#, можно запускать на Windows, macOS и Linux.

Пример игры на Unity, созданной на основе языка C#

Пример игры на Unity, созданной на основе языка C#

Интеграция между C# и Visual Studio (также созданной Microsoft) превосходна, как и все инструменты отладки / тестирования.

Остались вопросы?

Укажите ваши данные, и мы вам перезвоним

Популярные движки, использующие C#:

  • Unity.
  • Unreal Engine.
  • Godot.

Недостатки C#:

  • Широко не используется.
  • Ограничен экосистемой Microsoft.

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

Что выбрать для игр: C# или C++

  • Если вы собираетесь использовать C# — выбирайте Unity.
  • C++ поддерживается большим количеством движков. Оптимальные варианты — Unreal или Godot.

Java

Универсальный и мощный язык программирования, который широко используется во многих отраслях. Java также хороший выбор для разработки игр, поскольку обладает многими достоинствами C++, а также работает на различных платформах.

Будучи платформо-независимым языком, благодаря виртуальной машине JVM, игры на Java можно запускать на любой операционной системе, включая Windows, macOS и Linux. 

Игра на языке Java

Игра на языке Java

Java популярный выбор для разработки и мобильных игр, поскольку его можно запускать на устройствах iOS и Android. Кроме того, Java — язык для разработки нативных приложений в Android Studio. 

Некоторые игровые движки поддерживают Java, хотя он не так быстр, как C++, что может быть существенным недостатком для некоторых типов игр.

Движки, использующие Java:

  • jMonkeyEngine.
  • libGDX.
  • Java 3D.

Недостатки:

  • Сложный для изучения.
  • Может быть медленным, особенно в устаревших устройствах.
  • Не так популярен как C++.

JavaScript

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

JS не так быстр, как C++ или Java, но он подходит для разработки двухмерных игр.

Эта игра на JavaScript занимает около 13 килобайт

Эта игра на JavaScript занимает около 13 килобайт

Игры на JavaScript можно запускать в любом браузере и это существенно облегчает их распространение. Но, все равно, игры на JavaScript — гораздо менее популярны и востребованы, чем игры на других языках. Все же JS — для веба.

Популярные движки, использующие JavaScript:

  • Phaser.
  • PixiJS.
  • Cocos2d-x.

Недостатки JavaScript:

  • Не такой быстрый, как другие языки.
  • Не так распространен в gamedev.
  • Ограничен браузером.

Остались вопросы?

Укажите ваши данные, и мы вам перезвоним

Как выбрать платформу 

Какой движок лучше всего подходит для геймдева? Все зависит от того, какой продукт вы хотите разработать и сколько у вас ресурсов. Есть большая разница, когда вы single-разработчик и разработчик, которому помогает команда. Выбор платформы зависит и от масштаба игры.

Пример игры на Godot

Пример игры на Godot

Вот лучшие движки для различных целей:

  • 2D: Buildbox и Godot.
  • 3D: Unity и Unreal Engine.
  • Разработка для iOS: SpriteKit (только 2D).
  • Создание для Android: Unity.
  • Мобильный движок, удобный для новичков: Buildbox и Unity (судя по огромному количеству обучающих материалов).
  • Кроссплатформенный движок (мобильный, ПК и игровые консоли): Unity и Unreal Engine.
  • Движки без кода: Buildbox и Gamemaker.
  • Разработка гиперказуальных продуктов: Buildbox и Unity.

Остались вопросы?

Укажите ваши данные, и мы вам перезвоним

Основные компоненты игры

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

Механике нужно уделять колоссальное количество времени. Разработчик определяет: как именно персонаж будет коммуницировать и взаимодействовать с внутриигровым миром и его объектами. Примеры механики: прокачка героя, прохождение квеста, настройка оружия.

Прокачка уровня героя является одним из элементов игровой механики

Прокачка уровня героя является одним из элементов игровой механики

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

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


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

Место действия Far Cry 4 — Кират, вымышленная страна в Гималаях. Описание игрового мира и есть сеттинг

Место действия Far Cry 4 — Кират, вымышленная страна в Гималаях. Описание игрового мира и есть сеттинг

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

Серия GTA — пример проработанного и открытого мира с которым можно взаимодействовать

Серия GTA — пример проработанного и открытого мира с которым можно взаимодействовать 

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

Многие тайтлы хочется проходить, не в последнюю очередь, из-за интересного сюжета

Многие тайтлы хочется проходить, не в последнюю очередь, из-за интересного сюжета

При этом важно, чтобы сценарий представлен в последовательной и понятной форме, чтобы персонаж мог с ним взаимодействовать. Если сюжет / повествование / история беспорядочны, игра может развалиться.

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

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

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

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

Остались вопросы?

Укажите ваши данные, и мы вам перезвоним

Как строится игровой код

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

ИИ

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

NPC в Counter Strike: Source

NPC в Counter Strike: Source

Физические законы

Эта часть кода регламентирует физические законы внутри игры. Например, как падает предмет с высоты на землю или как двигается стрела, выпущенная из лука (и куда она отлетает, в случае столкновения с твердым препятствием).

Физика не может быть одинаковой для каждого объекта

Физика не может быть одинаковой для каждого объекта

Внутриигровой баланс

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

Без баланса геймплей просто развалиться

Без баланса геймплей просто развалиться

Графическая оболочка

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

Графика, все еще, отличима от реального мира, но в некоторых случаях — она максимально приближена к нему

Графика, все еще, отличима от реального мира, но в некоторых случаях — она максимально приближена к нему

Остались вопросы?

Укажите ваши данные, и мы вам перезвоним

Этапы создания

Этапов создания, конечно, гораздо больше, чем три. Но мы рассмотрим основные стадии разработки.

Pre-Production

На пре-продакшене решается множество вопросов:

  • О чем будет игра.
  • На какую аудиторию она рассчитана.
  • На какой рынок она нацелена.
  • Какие игры будут конкурентами.
  • Где и когда она будет опубликована.
  • Как будет продаваться.
  • Сколько времени потребуется на разработку.
  • Какие ресурсы и какой бюджет потребуются для производства.

Из всех этапов разработки, пре-продакшн является наиболее изменчивым, занимая от нескольких недель до нескольких месяцев (а иногда — около года и даже дольше), в зависимости от масштаба проекта. 

В идеале предпроизводство, обычно, занимает около 20% времени, отведенного на все этапы процесса разработки продукта.

Производство

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

Пост-продакшн

Существует распространенное заблуждение: после завершения разработки игры процесс производства останавливается. На самом деле, после того как игра отправлена в пост-продакшн, работа может стать еще более интенсивной. В крупных студиях отделы перераспределяются на техническое обслуживание, исправление ошибок и разработку патчей для улучшения игры. QA находит 90% ошибок, которые разработчики должны устранить, но все баги поймать невозможно. И здесь на помощь приходят первые игроки — они всегда найдут дополнительные ошибки.

Остались вопросы?

Укажите ваши данные, и мы вам перезвоним

Первый проект — с чего начать

Прежде всего — необходимо определиться с платформой для которой будет создаваться игра. Это может быть десктоп, браузер, мобильное устройство, консоль. От платформы вы и будете отталкиваться при выборе конкретных инструментов и технологий для разработки. 

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

Создать игру — это как построить дом (начиная с фундамента и заканчивая чистовой отделкой внутри). И ни один разработчик не способен в одиночку создать тайтл уровня Far Cry 5, например. Для создания игры такого масштаба необходимо сразу несколько специалистов: гейм-дизайнер, тестировщик, художник, композитор, звукорежиссёр, программист, дизайнер уровня, дизайнер баланса, сценарист, продюсер, переводчик, аниматор. В одиночку программист может создать инди-тайтл, но не коммерчески успешный тайтл уровня GTA VI.

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

Остались вопросы?

Укажите ваши данные, и мы вам перезвоним

Что посмотреть: видеоуроки, каналы ютуб

Наконец — несколько видео, которые помогут сформировать правильное представление о том, как это: быть геймдевом и программировать в этой отрасли.

One Year of Game Development with Unreal Engine: My Journey. Одиночный независимый разработчик, пытается делать игры. Самоучка в области 3D-моделирования и разработки с базовыми знаниями Java, полученными в университете, расскажет о своем опыте использования Unreal Engine 4 и 5, а также Unity.

ЧТО Я УЗНАЛ ЗА 9 ЛЕТ В ГЕЙМДЕВЕ | Game dev by Artalasky. Действующий геймдев делится своим опытом за 9 лет. Колоссальный багаж знаний, реальный опыт, неприятные ошибки, несколько инсайтов. Все это ждет вас в видео.

8 Months of Game Dev in 13 Minutes. Разработчик делится опытом создания игры в течение восьми месяцев за 13 минут. Концепция, сложности, движки, механика, и всё это — в одном видео.

IT профессии: РАЗРАБОТЧИК ИГР или GAME DEVELOPER. Как попасть в ГЕЙМДЕВ c нуля? Видео о профессии Game Developer. Всё, что нужно знать будущему разработчику в одном видео.

Game development is HARD. Gamedev — это всегда сложно. И в этом видео — вы узнаете, почему. Обязательно посмотрите, чтобы понять основные вызовы, с которыми сталкиваются game-разработчики.

Should You Be a Game Developer?

Если вы когда-нибудь задумывались о том, стоит ли становится Game Developer — это видео для вас. Разработчик с 20-летним стажем в играх расскажет о том, каково это на самом деле — быть геймдевом. Интересные подробности о Unity3D и Unreal. А также — плюсы и минусы профессии. 

Коротко о главном

Ставить слишком большие задачи для своего первого проекта — типичная ошибка начинающих программистов. Начинайте с малого. Представьте свою будущую игру как долгосрочную цель, а не как краткосрочную. Вот подсказки, как добиться успеха:

  1. Тщательно документируйте дизайн в том виде, в котором вы видите его в данный момент. Не пытайтесь расширить его, просто зафиксируйте то, что у вас есть сейчас.
  2. Выберите инструмент и язык. Вариантов много, C++, Java, Unity, UE — лучшие из них.
  3. Изучите инструменты, движки и язык досконально. Сделайте несколько простых проектов с изучаемыми инструментами. Например, создайте локальную игру по типу танчиков для Dendy, а затем — расширьте ее до многопользовательской. После — придумайте еще более сложный вариант.
  4. После каждого небольшого подпроекта — пересматривайте свой проект и подумайте: чему вы научились и как это повлияло на ваш стек.

Остались вопросы?

Укажите ваши данные, и мы вам перезвоним

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

  • Pygame
  • PyKyra
  • Pyglet
  • Panda3D
  • Kivy
  • PyopenGL

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

Pygame не входит в стандартную поставку Python, для установки библиотеки выполните:

        pip install pygame
    

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

Окно и главный цикл приложения

Создание любого приложения на базе Pygame начинается с импорта и инициализации библиотеки. Затем нужно определить параметры окна, и по желанию – задать цвет (или изображение) фона:

        import pygame

# инициализируем библиотеку Pygame
pygame.init()

# определяем размеры окна
window_size = (300, 300)

# задаем название окна
pygame.display.set_caption("Синий фон")

# создаем окно
screen = pygame.display.set_mode(window_size)

# задаем цвет фона
background_color = (0, 0, 255)  # синий

# заполняем фон заданным цветом
screen.fill(background_color)

# обновляем экран для отображения изменений
pygame.display.flip()

# показываем окно, пока пользователь не нажмет кнопку "Закрыть"
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()

    

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Цикл while True
играет роль главного цикла программы – в нем происходит отслеживание событий
приложения и действий пользователя. Функция pygame.quit() завершает работу
приложения, и ее можно назвать противоположностью функции pygame.init(). Для
завершения Python-процесса
используется exit(), с
той же целью можно использовать sys.exit(), но ее нужно
импортировать в начале программы: import sys.

В качестве фона можно использовать изображение:

        import pygame

pygame.init()

window_size = (400, 400)
screen = pygame.display.set_mode(window_size)
pygame.display.set_caption("Peter the Piglet")

# загружаем изображение
background_image = pygame.image.load("background.png")

# подгоняем масштаб под размер окна
background_image = pygame.transform.scale(background_image, window_size)

# накладываем изображение на поверхность
screen.blit(background_image, (0, 0))

pygame.display.flip()

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()

    

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Обработку событий (нажатий клавиш и кликов) в Pygame реализовать
очень просто – благодаря встроенным функциям. Приведенный ниже код изменяет
цвет фона после клика по кнопке. Обратите внимание, что в Pygame можно
задавать цвет несколькими способами:

        import pygame

pygame.init()
pygame.display.set_caption('Измени цвет фона')
window_surface = pygame.display.set_mode((300, 300))
background = pygame.Surface((300, 300))
background.fill(pygame.Color('#000000'))

color_list = [
    pygame.Color('#FF0000'),  # красный
    pygame.Color('#00FF00'),  # зеленый
    pygame.Color('#0000FF'),  # синий
    pygame.Color('#FFFF00'),  # желтый
    pygame.Color('#00FFFF'),  # бирюзовый
    pygame.Color('#FF00FF'),  # пурпурный
    pygame.Color('#FFFFFF')   # белый
]

current_color_index = 0

button_font = pygame.font.SysFont('Verdana', 15) # используем шрифт Verdana
button_text_color = pygame.Color("black")
button_color = pygame.Color("gray")
button_rect = pygame.Rect(100, 115, 100, 50)
button_text = button_font.render('Нажми!', True, button_text_color)

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()
        elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            if button_rect.collidepoint(event.pos):
                current_color_index = (current_color_index + 1) % len(color_list)
                background.fill(color_list[current_color_index])

        window_surface.blit(background, (0, 0))
        pygame.draw.rect(window_surface, button_color, button_rect)
        button_rect_center = button_text.get_rect(center=button_rect.center)
        window_surface.blit(button_text, button_rect_center)
        pygame.display.update()

    

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Как очевидно из приведенного выше примера, основной цикл Pygame приложения
состоит из трех повторяющихся действий:

  • Обработка событий (нажатий клавиш или кнопок).
  • Обновление состояния.
  • Отрисовка состояния на экране.

GUI для PyGame

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

  • Pygame GUI
  • Thorpy
  • PGU

Вот простой пример использования Pygame GUI – зеленые нули и
единицы падают вниз в стиле «Матрицы»:

        import pygame
import pygame_gui
import random

window_size = (800, 600)
window = pygame.display.set_mode(window_size)
pygame.display.set_caption('Матрица Lite')
pygame.init()
gui_manager = pygame_gui.UIManager(window_size)

font = pygame.font.SysFont('Consolas', 20)
text_color = pygame.Color('green')
text_symbols = ['0', '1']
text_pos = [(random.randint(0, window_size[0]), 0) for i in range(50)]
text_speed = [(0, random.randint(1, 5)) for i in range(50)]
text_surface_list = []

button_size = (100, 50)
button_pos = (350, 250)
button_text = 'Матрица!'

button = pygame_gui.elements.UIButton(
    relative_rect=pygame.Rect(button_pos, button_size),
    text=button_text,
    manager=gui_manager
)

while True:
    time_delta = pygame.time.Clock().tick(60) 

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()

        if event.type == pygame_gui.UI_BUTTON_PRESSED:
            text_surface_list = []
            for i in range(50):
                text_symbol = random.choice(text_symbols)
                text_surface = font.render(text_symbol, True, text_color)
                text_surface_list.append(text_surface)

        gui_manager.process_events(event)

    gui_manager.update(time_delta)

    window.fill(pygame.Color('black'))

    for i in range(50):
        text_pos[i] = (text_pos[i][0], text_pos[i][1] + text_speed[i][1])
        if text_pos[i][1] > window_size[1]:
            text_pos[i] = (random.randint(0, window_size[0]), -20)
        if len(text_surface_list) > i:
            window.blit(text_surface_list[i], text_pos[i])

    gui_manager.draw_ui(window)
    pygame.display.update()

    

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Рисование

В Pygame есть функции для простого рисования геометрических фигур –
прямоугольников, окружностей, эллипсов, линий, многоугольников. Вот пример:

        import pygame
import math

pygame.init()
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Геометрические фигуры")

black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
yellow = (255, 255, 0)
pink = (255, 192, 203)

# рисуем прямоугольник
rect_x = 50
rect_y = 50
rect_width = 100
rect_height = 50
pygame.draw.rect(screen, red, (rect_x, rect_y, rect_width, rect_height))

# рисуем круг
circle_x = 200
circle_y = 75
circle_radius = 30
pygame.draw.circle(screen, green, (circle_x, circle_y), circle_radius)

# рисуем треугольник
triangle_x = 350
triangle_y = 50
triangle_width = 100
triangle_height = 100
triangle_points = [(triangle_x, triangle_y), (triangle_x + triangle_width, triangle_y),
                   (triangle_x + triangle_width / 2, triangle_y + triangle_height)]
pygame.draw.polygon(screen, blue, triangle_points)

# рисуем пятиугольник
pent_x = 500
pent_y = 100
radius = 40
sides = 5
pent_points = []
for i in range(sides):
    angle_deg = 360 * i / sides
    angle_rad = math.radians(angle_deg)
    x = pent_x + radius * math.sin(angle_rad)
    y = pent_y - radius * math.cos(angle_rad)
    pent_points.append((x, y))
pygame.draw.polygon(screen, white, pent_points)

# рисуем эллипс
ellipse_x = 100
ellipse_y = 275
ellipse_width = 150
ellipse_height = 60
pygame.draw.ellipse(screen, red, (ellipse_x, ellipse_y, ellipse_width, ellipse_height))

# горизонтальная линия
horiz_line_y = 400
pygame.draw.line(screen, blue, (50, horiz_line_y), (590, horiz_line_y), 5)

# вертикальная линия
vert_line_x = 320
pygame.draw.line(screen, green, (vert_line_x, 50), (vert_line_x, 430), 5)

# рисуем желтую звезду
yellow_star_points = [(260 - 50, 250 - 70), (310 - 50, 250 - 70), (325 - 50, 200 - 70),
                      (340 - 50, 250 - 70), (390 - 50, 250 - 70), (350 - 50, 290 - 70),
                      (365 - 50, 340 - 70), (325 - 50, 305 - 70), (285 - 50, 340 - 70),
                      (300 - 50, 290 - 70)]
pygame.draw.polygon(screen, yellow, yellow_star_points)

# рисуем окружность с квадратом внутри
circle2_x = 490
circle2_y = 350
circle2_radius = 80
pygame.draw.circle(screen, white, (circle2_x, circle2_y), circle2_radius)
square_side = 60
square_x = circle2_x - square_side / 2
square_y = circle2_y - square_side / 2
pygame.draw.rect(screen, pink, (square_x, square_y, square_side, square_side))

pygame.display.update()

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()

    

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Анимация и обработка событий

Выше, в примере с падающими символами в «Матрице», уже был
показан принцип простейшей имитации движения, который заключается в
последовательном изменении координат объекта и обновлении экрана с
установленной частотой кадра pygame.time.Clock().tick(60).
Усложним задачу – сделаем простую анимацию с падающими розовыми «звездами».
Приложение будет поддерживать обработку двух событий:

  • При клике мышью по экрану анимация останавливается.
  • При нажатии клавиши Enter – возобновляется.

Кроме того, добавим счетчик упавших звезд. Готовый код
выглядит так:

        import pygame
import random

pygame.init()

screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Звезды падают вниз")

black = (0, 0, 0)
white = (255, 255, 255)
pink = (255, 192, 203)

font = pygame.font.SysFont("Verdana", 15)

star_list = []
for i in range(50):
    x = random.randrange(screen_width)
    y = random.randrange(-200, -50)
    speed = random.randrange(1, 5)
    star_list.append([x, y, speed])
score = 0

freeze = False # флаг для определения момента остановки

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()
        if event.type == pygame.MOUSEBUTTONDOWN: # останавливаем падение звезд по клику
            freeze = True
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RETURN: # возобновляем движение вниз, если нажат Enter
                freeze = False

    if not freeze: # если флаг не активен,
        # звезды падают вниз
        for star in star_list:
            star[1] += star[2]
            if star[1] > screen_height:
                star[0] = random.randrange(screen_width)
                star[1] = random.randrange(-200, -50)
                score += 1

    # рисуем звезды, выводим результаты подсчета
    screen.fill(black)
    for star in star_list:
        pygame.draw.circle(screen, pink, (star[0], star[1]), 3)
    score_text = font.render("Упало звезд: " + str(score), True, white)
    screen.blit(score_text, (10, 10))

    pygame.display.update()

    # устанавливаем частоту обновления экрана
    pygame.time.Clock().tick(60)

    

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Покадровая анимация

Если у вас есть в запасе картинки с раскадровкой движения,
превратить их в анимацию не составит труда:

        import pygame

pygame.init()

window_size = (640, 480)
screen = pygame.display.set_mode(window_size)
color = (216, 233, 243)
screen.fill(color)
pygame.display.flip()
pygame.display.set_caption("Покадровая анимация")
clock = pygame.time.Clock()

# загружаем кадры
frame_images = []
for i in range(1, 9):
    frame_images.append(pygame.image.load(f"frame{i}.png"))

# параметры анимации
animation_length = len(frame_images)
animation_speed = 15  # кадры в секунду
current_frame_index = 0
animation_timer = 0
frame_position = [0, 0]

# вычисляем позицию для вывода кадров в зависимости от высоты окна
window_height = screen.get_height()
frame_height = frame_images[0].get_height()
frame_position[1] = int(window_height * 0.45) - int(frame_height / 2)

# запускаем основной цикл
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # обновление состояния
    time_delta = clock.tick(60) / 1000.0
    animation_timer += time_delta
    if animation_timer >= 1.0 / animation_speed:
        current_frame_index = (current_frame_index + 1) % animation_length
        animation_timer -= 1.0 / animation_speed

    frame_position[0] += 1  # сдвигаем кадр вправо

    # выводим кадры, обновляем экран
    current_frame = frame_images[current_frame_index]
    screen.blit(current_frame, frame_position)
    pygame.display.flip()

pygame.quit()

    

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Столкновение объектов

В этом примере расстояние между объектами проверяется до тех
пор, пока объекты не столкнутся. В момент столкновение движение прекращается, а
цвет объектов – изменяется:

        import pygame
import math

pygame.init()
screen_width = 400
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Драматическое столкновение")

# размеры и позиция окружности
circle_pos = [screen_width/2, 50]
circle_radius = 20

# размеры и позиция прямоугольника
rect_pos = [screen_width/2, screen_height-50]
rect_width = 100
rect_height = 50

# цвета окружности и прямоугольника
white = (255, 255, 255)
black = (0, 0, 0)
green = (0, 255, 0)
red = (255, 0, 0)

# скорость движения окружности
speed = 5

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()

    # окружность движется вниз
    circle_pos[1] += speed

    # проверяем (используя формулу расстояния),
    # столкнулась ли окружность с прямоугольником
    circle_x = circle_pos[0]
    circle_y = circle_pos[1]
    rect_x = rect_pos[0]
    rect_y = rect_pos[1]
    distance_x = abs(circle_x - rect_x)
    distance_y = abs(circle_y - rect_y)
    if distance_x <= (rect_width/2 + circle_radius) and distance_y <= (rect_height/2 + circle_radius):
        circle_color = red # изменяем цвет фигур
        rect_color = green # в момент столкновения
    else:
        circle_color = green
        rect_color = black

    # рисуем окружность и прямоугольник на экране
    screen.fill(white)
    pygame.draw.circle(screen, circle_color, circle_pos, circle_radius)
    pygame.draw.rect(screen, rect_color, (rect_pos[0]-rect_width/2, rect_pos[1]-rect_height/2, rect_width, rect_height))

    pygame.display.update()

    # останавливаем движение окружности, если она
    # столкнулась с прямоугольником
    if circle_pos[1] + circle_radius >= rect_pos[1] - rect_height/2:
        speed = 0

    # задаем частоту обновления экрана
    pygame.time.Clock().tick(60)

    

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Управление движением объекта

Для управления движением (в нашем случае – с помощью клавиш ← и →) используются pygame.K_RIGHT (вправо) и pygame.K_LEFT (влево). В
приведенном ниже примере передвижение падающих окружностей возможно только до
момента приземления на дно игрового поля, или на предыдущие фигуры. Для
упрощения вычисления факта столкновения фигур окружности вписываются в прямоугольники:

        import pygame
import random

pygame.init()
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))

# цвета окружностей
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
black = (0, 0, 0)
yellow = (255, 255, 0)

# цвет, скорость, начальная позиция окружности
circle_radius = 30
circle_speed = 3
circle_color = random.choice([red, green, blue, yellow, white])
circle_pos = [screen_width//2, -circle_radius]
circle_landed = False

# список приземлившихся окружностей и их позиций
landed_circles = []

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()

    # если окружность не приземлилась
    if not circle_landed:
        # меняем направление по нажатию клавиши
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            circle_pos[0] -= circle_speed
        if keys[pygame.K_RIGHT]:
            circle_pos[0] += circle_speed

        # проверяем, столкнулась ли окружность с другой приземлившейся окружностью
        for landed_circle in landed_circles:
            landed_rect = pygame.Rect(landed_circle[0]-circle_radius, landed_circle[1]-circle_radius, circle_radius*2, circle_radius*2)
            falling_rect = pygame.Rect(circle_pos[0]-circle_radius, circle_pos[1]-circle_radius, circle_radius*2, circle_radius*2)
            if landed_rect.colliderect(falling_rect):
                circle_landed = True
                collision_x = circle_pos[0]
                collision_y = landed_circle[1] - circle_radius*2
                landed_circles.append((collision_x, collision_y, circle_color))
                break

        # если окружность не столкнулась с другой приземлившейся окружностью
        if not circle_landed:
            # окружность движется вниз
            circle_pos[1] += circle_speed

            # проверяем, достигла ли окружность дна
            if circle_pos[1] + circle_radius > screen_height:
                circle_pos[1] = screen_height - circle_radius
                circle_landed = True
                # добавляем окружность и ее позицию в список приземлившихся окружностей
                landed_circles.append((circle_pos[0], circle_pos[1], circle_color))

    if circle_landed:
        # если окружность приземлилась, задаем параметры новой
        circle_pos = [screen_width//2, -circle_radius]
        circle_color = random.choice([red, green, blue, yellow, white])
        circle_landed = False

    # рисуем окружности
    screen.fill(black)
    for landed_circle in landed_circles:
        pygame.draw.circle(screen, landed_circle[2], (landed_circle[0], landed_circle[1]), circle_radius)
    pygame.draw.circle(screen, circle_color, circle_pos, circle_radius)
    pygame.display.update()

    # частота обновления экрана
    pygame.time.Clock().tick(60)

    

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Практика

Задание 1 – Лестница

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

Пример:

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Решение:

Код решения.

        import pygame

pygame.init()
WINDOW_WIDTH = 500
WINDOW_HEIGHT = 500
game_display = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption('Вверх по лестнице')

# параметры ступеней
STEP_WIDTH = 20
STEP_HEIGHT = STEP_WIDTH
STEP_COLOR = (255, 255, 255)

# параметры игрока, размеры и цвет шрифта
PLAYER_RADIUS = 10
PLAYER_COLOR = (255, 0, 0)
FONT_SIZE = 20
FONT_COLOR = (255, 255, 255)
clock = pygame.time.Clock()

def game_loop():
    game_exit = False
    # стартовая позиция игрока
    player_x = PLAYER_RADIUS
    player_y = WINDOW_HEIGHT - PLAYER_RADIUS * 3
    # начальные координаты ступеней
    step_x = 0
    step_y = WINDOW_HEIGHT - STEP_HEIGHT
    # создаем счетчик шагов
    step_count = 0

    while not game_exit:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                game_exit = True
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RIGHT:
                    # удаляем игрока из предыдущей позиции
                    pygame.draw.circle(game_display, (0, 0, 0), (player_x, player_y), PLAYER_RADIUS)
                    # обновляем позицию и счет
                    player_x += 20
                    player_y -= 20
                    step_count += 1

        # рисуем лестницу
        while step_x < WINDOW_WIDTH and step_y >= 0:
            pygame.draw.rect(game_display, STEP_COLOR, [step_x, step_y, STEP_WIDTH, STEP_HEIGHT])
            step_x += STEP_WIDTH
            step_y -= STEP_HEIGHT

        # перемещаем игрока на ступень выше
        pygame.draw.circle(game_display, PLAYER_COLOR, (player_x, player_y), PLAYER_RADIUS)

        # подсчитываем сделанные шаги
        pygame.draw.rect(game_display, (0, 0, 0), (10, 10, 100, FONT_SIZE))
        font = pygame.font.SysFont('Arial', FONT_SIZE)
        text = font.render(f'Шаг: {str(step_count)}', True, FONT_COLOR)
        game_display.blit(text, (10, 10))
        pygame.display.update()
        clock.tick(60)
game_loop()
pygame.quit()
    

Задание 2 – Лабиринт

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

Пример:

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Решение:

Код решения.

        import pygame
import random

pygame.init()
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('Лабиринт')

black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
blue = (0,0,255)
green = (0,255,0)

# параметры стен и дверей
line_width = 10
line_gap = 40
line_offset = 20
door_width = 20
door_gap = 40
max_openings_per_line = 5

# параметры и стартовая позиция игрока
player_radius = 10
player_speed = 5
player_x = screen_width - 12
player_y = screen_height - line_offset

# рисуем стены и двери
lines = []
for i in range(0, screen_width, line_gap):
    rect = pygame.Rect(i, 0, line_width, screen_height)
    num_openings = random.randint(1, max_openings_per_line)
    if num_openings == 1:
        # одна дверь посередине стены
        door_pos = random.randint(line_offset + door_width, screen_height - line_offset - door_width)
        lines.append(pygame.Rect(i, 0, line_width, door_pos - door_width))
        lines.append(pygame.Rect(i, door_pos + door_width, line_width, screen_height - door_pos - door_width))
    else:
        # несколько дверей
        opening_positions = [0] + sorted([random.randint(line_offset + door_width, screen_height - line_offset - door_width) for _ in range(num_openings-1)]) + [screen_height]
        for j in range(num_openings):
            lines.append(pygame.Rect(i, opening_positions[j], line_width, opening_positions[j+1]-opening_positions[j]-door_width))

clock = pygame.time.Clock()

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()

    # передвижение игрока
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT] and player_x > player_radius:
        player_x -= player_speed
    elif keys[pygame.K_RIGHT] and player_x < screen_width - player_radius:
        player_x += player_speed
    elif keys[pygame.K_UP] and player_y > player_radius:
        player_y -= player_speed
    elif keys[pygame.K_DOWN] and player_y < screen_height - player_radius:
        player_y += player_speed

    # проверка столкновений игрока со стенами
    player_rect = pygame.Rect(player_x - player_radius, player_y - player_radius, player_radius * 2, player_radius * 2)
    for line in lines:
        if line.colliderect(player_rect):
            # в случае столкновения возвращаем игрока назад
            if player_x > line.left and player_x < line.right:
                if player_y < line.top:
                    player_y = line.top - player_radius
                else:
                    player_y = line.bottom + player_radius
            elif player_y > line.top and player_y < line.bottom:
                if player_x < line.left:
                    player_x = line.left - player_radius
                else:
                    player_x = line.right + player_radius
    screen.fill(black)
    for line in lines:
        pygame.draw.rect(screen, green, line)
    pygame.draw.circle(screen, red, (player_x, player_y), player_radius)
    pygame.display.update()
    clock.tick(60)
    

Задание 3 – Дождь

Используя Pygame,
напишите симулятор дождя: падение каждой сотни капель приводит к подъему уровня
воды на 1 пиксель.

Пример:

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Решение:

Код решения.

        import pygame
import random

class RainSimulator:
    def __init__(self):
        pygame.init()
        self.screen_width = 500
        self.screen_height = 700
        self.screen = pygame.display.set_mode((self.screen_width, self.screen_height))
        pygame.display.set_caption("Дождь")
        self.font = pygame.font.SysFont("Consolas", 20)
        self.background_color = (0, 0, 0)
        self.blue = (173, 216, 230)

        # параметры дождевых капель
        self.drops = []
        self.drops_landed = 0
        self.drops_per_pixel = 100
        self.level_height = 0

        self.clock = pygame.time.Clock()

    # добавляем капли дождя
    def add_drop(self):
        self.drops.append([random.randint(0, self.screen_width), 0])

    # рисуем дождь
    def draw_drops(self):
        for drop in self.drops:
            pygame.draw.line(self.screen, self.blue, (drop[0], drop[1]), (drop[0], drop[1] + 5), 2)

    # подсчитываем капли, поднимаем уровень воды
    def update_drops(self):
        for drop in self.drops:
            drop[1] += 5
            if drop[1] >= self.screen_height:
                self.drops.remove(drop)
                self.drops_landed += 1
                if self.drops_landed % self.drops_per_pixel == 0:
                    self.level_height += 1

    # выводим количество капель
    def draw_score(self):
        score_text = self.font.render(f"Капель дождя: {str(self.drops_landed)}", True, (255, 255, 255))
        self.screen.blit(score_text, (10, 10))
        pygame.draw.rect(self.screen, self.blue, (0, self.screen_height-self.level_height, self.screen_width, self.level_height))

    def run_rain(self):
        running = True
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
            self.add_drop()
            self.update_drops()
            self.screen.fill(self.background_color)
            self.draw_drops()
            self.draw_score()
            pygame.display.update()
            self.clock.tick(60)

        pygame.quit()

if __name__ == "__main__":
    app = RainSimulator()
    app.run_rain()

    

Задание 4 – Мерцающие звезды

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

Пример:

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Решение:

Код решения.

        import pygame
import random

pygame.init()
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("Мерцающие звезды")

# параметры звезд
MAX_STARS = 200
stars = []
for i in range(MAX_STARS):
    star_radius = random.randint(1, 3)
    star_color = (255, 255, 237)
    star_position = (random.randint(0, WINDOW_WIDTH), random.randint(0, WINDOW_HEIGHT))
    star_expand = True
    star_expand_speed = random.uniform(0.1, 0.5)
    stars.append((star_radius, star_color, star_position, star_expand, star_expand_speed))

clock = pygame.time.Clock()
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()

    for i in range(MAX_STARS):
        star_radius, star_color, star_position, star_expand, star_expand_speed = stars[i]

        # вычисление радиуса расширения
        if star_expand:
            star_radius += star_expand_speed
            if star_radius >= 5:
                star_expand = False
        else:
            star_radius -= star_expand_speed
            if star_radius <= 1:
                star_expand = True

        # изменяем позиции звезд для создания эффекта мерцания
        star_position = (
            star_position[0] + random.randint(-1, 1),
            star_position[1] + random.randint(-1, 1)
        )

        stars[i] = (star_radius, star_color, star_position, star_expand, star_expand_speed)

    # рисуем звезды
    screen.fill((0, 0, 0))
    for star in stars:
        star_radius, star_color, star_position, _, _ = star
        pygame.draw.circle(screen, star_color, star_position, star_radius)

    pygame.display.update()
    clock.tick(60)

    

Задание 5 – Колобок

Используя Pygame,
создайте анимацию, в которой лиса (состоящая из этих фреймов) преследует
Колобка. Колобок вращается вокруг своей оси.

Пример:

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Решение:

Код решения и необходимые изображения.

        import pygame

pygame.init()
background = (24, 113, 147)
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 300
game_display = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption('Колобок')

# загружаем изображение Колобка
kolobok = pygame.image.load('kolobok.png')
# стартовый угол вращения и скорость
kolobok_angle = 0
kolobok_rotation_speed = 2

# загружаем фреймы лисы
fox = []
for i in range(8):
    fox.append(pygame.image.load(f'fox{i+1}.png'))

# частота обновления фреймов лисы
fox_frame = 0
fox_frame_rate = 8
fox_frame_timer = 0

# стартовые позиции и скорость движения лисы и Колобка
kolobok_x = 0
kolobok_y = WINDOW_HEIGHT // 2 + kolobok.get_height() // 4 
fox_x = -fox[0].get_width()
fox_y = WINDOW_HEIGHT // 2 - fox[0].get_height() // 2
movement_speed = 3
clock = pygame.time.Clock()

# главный цикл
game_exit = False
while not game_exit:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game_exit = True

    # вращаем изображение Колобка вокруг своей оси
    kolobok_angle += kolobok_rotation_speed
    if kolobok_angle >= 360:
        kolobok_angle = 0
    rotated_kolobok = pygame.transform.rotate(kolobok, kolobok_angle)

    # движение Колобка и лисы слева направо
    kolobok_x += movement_speed
    if kolobok_x > WINDOW_WIDTH:
        kolobok_x = 0 - fox[0].get_width()
    fox_x += movement_speed
    if fox_x > WINDOW_WIDTH:
        fox_x = 0 - fox[0].get_width() 

    # приводим скорость анимации лисы в соответствие с частотой обновления экрана
    fox_frame_timer += clock.tick(60)
    if fox_frame_timer >= 1000 / fox_frame_rate:
        fox_frame_timer -= 1000 / fox_frame_rate
        fox_frame = (fox_frame + 1) % len(fox)

    # рисуем фон, выводим фигуры Колобка и лисы
    game_display.fill(background)
    game_display.blit(rotated_kolobok, (kolobok_x, kolobok_y))
    game_display.blit(fox[fox_frame], (fox_x, fox_y))
    pygame.display.update()

pygame.quit()
    

Задание 6 – Светофор

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

Пример:

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Решение:

Код решения.

        import pygame
import random

pygame.init()
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
game_display = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption('Светофор')
clock = pygame.time.Clock()

BLACK = (0, 0, 0)
DARK_GRAY = (64, 64, 64)
GRAY = (128, 128, 128)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)


def game_loop():
    game_exit = False

    # определяем цвета для светофора
    colors = [RED, YELLOW, GREEN]
    active_index = 0
    last_switch = pygame.time.get_ticks()
    interval = 2000

    # параметры автомобилей
    car_width = 40
    car_height = 60
    car_speed = 2
    horizontal_spacing = 12
    vertical_spacing = 20
    car_rects = []
    for i in range(2):
        left_rect = pygame.Rect(100, random.randint(50, WINDOW_HEIGHT - car_height), car_width, car_height)
        right_rect = pygame.Rect(WINDOW_WIDTH - 300 - car_width, random.randint(50, WINDOW_HEIGHT - car_height), car_width, car_height)
        car_rects.append(left_rect)
        car_rects.append(right_rect)

    # вертикальная и горизонтальная дистанция между автомобилями
    for i in range(1, len(car_rects)):
        if car_rects[i].left - car_rects[i-1].right < horizontal_spacing:
            car_rects[i].left = car_rects[i-1].right + horizontal_spacing
        if car_rects[i].top - car_rects[i-1].bottom < vertical_spacing:
            car_rects[i].top = car_rects[i-1].bottom + vertical_spacing

    while not game_exit:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                game_exit = True

        # определяем нужный цвет светофора
        now = pygame.time.get_ticks()
        if now - last_switch >= interval:
            active_index = (active_index + 1) % len(colors)
            last_switch = now

        # временной интервал для зеленого цвета - 6 секунд, для остальных - 2
        interval = 6000 if active_index == 2 else 2000

        # движение машин
        if active_index == 0 or active_index == 1:
            car_speed = 0
        else:
            car_speed = 2
        for car_rect in car_rects:
            car_rect.move_ip(0, -car_speed)
            if car_rect.bottom <= 0:
                car_rect.top = WINDOW_HEIGHT
                car_rect.left = 100 if car_rect.left == WINDOW_WIDTH - 100 - car_width else WINDOW_WIDTH - 100 - car_width

        # рисуем светофор
        game_display.fill(GRAY)
        light_rect = pygame.Rect((WINDOW_WIDTH - 200) // 2, (WINDOW_HEIGHT - 300) // 2, 100, 300)
        pygame.draw.rect(game_display, DARK_GRAY, light_rect, 5)
        light_width = light_rect.width
        light_height = light_rect.height // 3
        light_y = light_rect.top
        for i in range(3):
            circle_rect = pygame.Rect(light_rect.left + 10, light_y + i * light_height + 10, light_width - 20, light_height - 20)
            circle_color = colors[i] if i == active_index else BLACK
            pygame.draw.circle(game_display, circle_color, circle_rect.center, circle_rect.width // 2)

        # рисуем автомобили
        for car_rect in car_rects:
            pygame.draw.rect(game_display, BLUE, car_rect)

        pygame.display.update()
        clock.tick(60)
    pygame.quit()
game_loop()
    

Задание 7 – Визуальная память

Напишите лайт-версию игры Memory game, используя возможности Pygame. Сначала приложение выводит (в случайном порядке) цветные
окружности и дает возможность пользователю запомнить их расположение в течение
нескольких секунд. Затем приложение закрывает цветные окружности серыми:
пользователь должен по памяти сопоставить цветные пары. Каждая угаданная пара
приносит пользователю 1 балл.

Пример:

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Решение:

Код решения.

        import pygame
from random import shuffle

pygame.init()
# определяем цвета игры
black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
blue = (0, 0, 255)
green = (0, 255, 0)
yellow = (255, 255, 0)
purple = (128, 0, 128)
grey = (192, 192, 192)

screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Тренируем визуальную память")

# задаем параметры окружностей и перемешиваем пары
circle_radius = 50
circle_colors = [red, blue, green, yellow, purple, white]
circle_pairs = circle_colors * 2
shuffle(circle_pairs)

# формируем список окружностей
circle_positions = []
for i in range(6):
    for j in range(2):
        center_x = ((screen_width / 6) * (i + 1)) - (screen_width / 12)
        center_y = ((screen_height / 3) * (j + 1)) - (screen_height / 6)
        circle_positions.append([center_x, center_y])

# запоминаем позиции и цвета окружностей
original_circle_positions = circle_positions.copy()
original_circle_colors = circle_pairs.copy()

# рисуем цветные окружности
for i in range(len(circle_pairs)):
    position = circle_positions[i]
    color = circle_pairs[i]
    pygame.draw.circle(screen, color, position, circle_radius)

font = pygame.font.SysFont('Arial', 20)
pygame.display.update()

# ждем 5 секунд
pygame.time.wait(5000)

# закрываем цветные окружности серыми
for i in range(len(circle_pairs)):
    position = circle_positions[i]
    pygame.draw.circle(screen, grey, position, circle_radius)

pygame.display.update()
uncovered_circles = []
last_uncovered_circle = None
score = 0

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()

        if event.type == pygame.MOUSEBUTTONDOWN:
            mouse_pos = event.pos
            for i in range(len(circle_positions)):
                position = circle_positions[i]
                if ((position[0] - mouse_pos[0]) ** 2 + (position[1] - mouse_pos[1]) ** 2) ** 0.5 < circle_radius:
                    if i not in uncovered_circles:
                        uncovered_circles.append(i)
                        color = original_circle_colors[i]
                        pygame.draw.circle(screen, color, position, circle_radius)
                        pygame.display.update()
                        if last_uncovered_circle is not None and original_circle_colors[last_uncovered_circle] == original_circle_colors[i]:
                            score += 1
                        last_uncovered_circle = i

            if len(uncovered_circles) == len(circle_pairs):
                # вывод результата
                final_score_text = font.render(f"Уровень памяти: {str(score)} из 6", True, white)
                screen.blit(final_score_text, (screen_width // 2, screen_height // 2 + 125))
                pygame.display.update()
                pygame.time.wait(3000)
                pygame.quit()
                exit()

    

Задание 8 – Подсчет фигур

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

Пример:

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Решение:

Код решения.

        import pygame
import random

pygame.init()
width = 800
height = 600
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Подсчет фигур")
clock = pygame.time.Clock()

white = (255, 255, 255)
black = (0, 0, 0)
colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255), (0, 255, 255)]

# параметры фигур
class Circle:
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color
        self.radius = 30

    def draw(self):
        pygame.draw.circle(screen, self.color, (self.x, self.y), self.radius)

class Triangle:
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color
        self.width = 60
        self.height = 60

    def draw(self):
        pygame.draw.polygon(screen, self.color, [(self.x, self.y), (self.x + self.width, self.y), (self.x + self.width/2, self.y - self.height)])

class Square:
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color
        self.width = 60
        self.height = 60

    def draw(self):
        pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height))

# создаем список фигур
shapes = []
x = random.randint(0, width - 60)
y = random.randint(-500, -50)
color = random.choice(colors)
shape_type = random.choice(["circle", "triangle", "square"])
if shape_type == "circle":
    shape = Circle(x, y, color)
elif shape_type == "triangle":
    shape = Triangle(x, y, color)
else:
    shape = Square(x, y, color)
shapes.append(shape)

# счетчики фигур
circle_count = 0
triangle_count = 0
square_count = 0

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()

    screen.fill(black)

    # рисуем и подсчитываем фигуры
    for shape in shapes:
        shape.draw()
        shape.y += 5
        if shape.y > height:
            shapes.remove(shape)
            if isinstance(shape, Circle):
                circle_count += 1
            elif isinstance(shape, Triangle):
                triangle_count += 1
            else:
                square_count += 1
            x = random.randint(0, width - 60)
            y = random.randint(-500, -50)
            color = random.choice(colors)
            shape_type = random.choice(["circle", "triangle", "square"])
            if shape_type == "circle":
                shape = Circle(x, y, color)
            elif shape_type == "triangle":
                shape = Triangle(x, y, color)
            else:
                shape = Square(x, y, color)
            shapes.append(shape)

    # выводим счетчики
    font = pygame.font.SysFont("Verdana", 25)
    circle_text = font.render(f"Окружности: {circle_count}", True, white)
    triangle_text = font.render(f"Треугольники: {triangle_count}", True, white)
    square_text = font.render(f"Квадраты: {square_count}", True, white)
    screen.blit(circle_text, (10, 10))
    screen.blit(triangle_text, (10, 40))
    screen.blit(square_text, (10, 70))

    pygame.display.update()
    clock.tick(60)

    

Задание 9 – Призы и бомбы

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

Пример:

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Решение:

Код решения.

        import pygame
import random

class RewardsBombs():
    def __init__(self):
        pygame.init()
        self.screen_width = 600
        self.screen_height = 600
        self.screen = pygame.display.set_mode((self.screen_width, self.screen_height))
        pygame.display.set_caption("Призы и бомбы")
        self.clock = pygame.time.Clock()
        self.green_pos = [self.screen_width // 2, self.screen_height - 30]
        self.red_positions = []
        self.red_speed = 2
        self.score = 0
        self.font = pygame.font.SysFont("Arial", 24)
        self.run()

    def run(self):
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    exit()

                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_LEFT:
                        if self.green_pos[0] - 20 >= 0:
                            self.green_pos[0] -= 20
                    elif event.key == pygame.K_RIGHT:
                        if self.green_pos[0] + 20 <= self.screen_width:
                            self.green_pos[0] += 20
                    elif event.key == pygame.K_UP:
                        if self.green_pos[1] - 20 >= 0:
                            self.green_pos[1] -= 20
                    elif event.key == pygame.K_DOWN:
                        if self.green_pos[1] + 20 <= self.screen_height:
                            self.green_pos[1] += 20

            # движение красных бомб
            for i in range(len(self.red_positions)):
                self.red_positions[i][1] += self.red_speed

            # создание бомб и призов
            if random.random() < 0.02:
                x = random.randint(0, self.screen_width)
                num = random.randint(1, 10)
                if num % 2 == 0:
                    self.red_positions.append([x, 0, False])
                else:
                    self.red_positions.append([x, 0, True])

            # проверка столкновений с игроком
            for pos in self.red_positions:
                if pos[2]:
                    if abs(pos[0] - self.green_pos[0]) <= 20 and abs(pos[1] - self.green_pos[1]) <= 20:
                        self.score += 1
                        self.red_positions.remove(pos)
                else:
                    if (pos[0] - self.green_pos[0]) ** 2 + (pos[1] - self.green_pos[1]) ** 2 < 400:
                        self.game_over()

            # убираем бомбы за пределами окна
            self.red_positions = [pos for pos in self.red_positions if pos[1] < self.screen_height]
            self.screen.fill((0, 0, 0))

            for pos in self.red_positions:
                if pos[2]:
                    pygame.draw.polygon(self.screen, (0, 0, 255), [[pos[0], pos[1]-10], [pos[0]+10, pos[1]+10], [pos[0]-10, pos[1]+10]])
                else:
                    pygame.draw.circle(self.screen, (255, 0, 0), pos[:2], 10)

            pygame.draw.circle(self.screen, (0, 255, 0), self.green_pos, 10)

            self.draw_score()
            pygame.display.update()
            self.clock.tick(60)

    def draw_score(self):
        score_surface = self.font.render(f"Призы: {self.score}", True, (255, 255, 255))
        self.screen.blit(score_surface, (10, 10))

    def game_over(self):
        message_surface = self.font.render(f"Игра закончена! Призы: {self.score}", True, (255, 0, 0))
        self.screen.blit(message_surface, (self.screen_width // 2 - message_surface.get_width() // 2, self.screen_height // 2 - message_surface.get_height() // 2))
        pygame.display.update()
        pygame.time.wait(3000)
        pygame.quit()
        exit()

if __name__ == "__main__":
    RewardsBombs()
    

Задание 10 – Змейка

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

Пример:

🐍 Самоучитель по Python для начинающих. Часть 21: Основы разработки игр на Pygame

Решение:

Код решения.

        import pygame
import random

pygame.init()
screen_width = 600
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Змейка")
green = (0, 255, 0)
red = (255, 0, 0)
font = pygame.font.SysFont("Arial", 20)
clock = pygame.time.Clock()

# основные параметры игры
cell_size = 20
snake_speed = 5
snake_length = 3
snake_body = []

for i in range(snake_length):
    snake_body.append(pygame.Rect((screen_width / 2) - (cell_size * i), screen_height / 2, cell_size, cell_size))
snake_direction = "right"
new_direction = "right"
apple_position = pygame.Rect(random.randint(0, screen_width - cell_size), random.randint(0, screen_height - cell_size), cell_size, cell_size)

game_over = False
while not game_over:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game_over = True
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_UP and snake_direction != "down":
                new_direction = "up"
            elif event.key == pygame.K_DOWN and snake_direction != "up":
                new_direction = "down"
            elif event.key == pygame.K_LEFT and snake_direction != "right":
                new_direction = "left"
            elif event.key == pygame.K_RIGHT and snake_direction != "left":
                new_direction = "right"

    # новое направление движения
    snake_direction = new_direction
    # управление змейкой
    if snake_direction == "up":
        snake_body.insert(0, pygame.Rect(snake_body[0].left, snake_body[0].top - cell_size, cell_size, cell_size))
    elif snake_direction == "down":
        snake_body.insert(0, pygame.Rect(snake_body[0].left, snake_body[0].top + cell_size, cell_size, cell_size))
    elif snake_direction == "left":
        snake_body.insert(0, pygame.Rect(snake_body[0].left - cell_size, snake_body[0].top, cell_size, cell_size))
    elif snake_direction == "right":
        snake_body.insert(0, pygame.Rect(snake_body[0].left + cell_size, snake_body[0].top, cell_size, cell_size))

    # проверяем, съела ли змея яблоко
    if snake_body[0].colliderect(apple_position):
        apple_position = pygame.Rect(random.randint(0, screen_width - cell_size), random.randint(0, screen_height-cell_size), cell_size, cell_size)
        snake_length += 1

    if len(snake_body) > snake_length:
        snake_body.pop()

    # проверка столкновения со стенами
    if snake_body[0].left < 0 or snake_body[0].right > screen_width or snake_body[0].top < 0 or snake_body[0].bottom > screen_height:
        game_over = True

    # проверка столкновения с собственным телом
    for i in range(1, len(snake_body)):
        if snake_body[0].colliderect(snake_body[i]):
            game_over = True

    screen.fill((0, 0, 0))
    # рисуем змейку
    for i in range(len(snake_body)):
        if i == 0:
            pygame.draw.circle(screen, green, snake_body[i].center, cell_size / 2)
        else:
            pygame.draw.circle(screen, green, snake_body[i].center, cell_size / 2)
            pygame.draw.circle(screen, (0, 200, 0), snake_body[i].center, cell_size / 4)

    # рисуем яблоко
    pygame.draw.circle(screen, red, apple_position.center, cell_size / 2)

    # выводим количество яблок
    score_text = font.render(f"Съедено яблок: {snake_length - 3}", True, (255, 255, 255))
    screen.blit(score_text, (10, 10))
    pygame.display.update()

    clock.tick(snake_speed)

pygame.quit()

    

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

Мы рассмотрели самые простые приемы разработки игр в Pygame
– возможности этой библиотеки намного обширнее. К примеру, для быстрой
разработки в Pygame используются спрайты – объекты для определения свойств и
поведения игровых элементов. Встроенные классы Group, GroupSingle и RenderUpdates позволяют быстро,
просто и эффективно группировать, обновлять и отрисовывать игровые элементы.

В
следующей главе будем изучать работу с
SQL и базами данных.

***

Содержание самоучителя

  1. Особенности, сферы применения, установка, онлайн IDE
  2. Все, что нужно для изучения Python с нуля – книги, сайты, каналы и курсы
  3. Типы данных: преобразование и базовые операции
  4. Методы работы со строками
  5. Методы работы со списками и списковыми включениями
  6. Методы работы со словарями и генераторами словарей
  7. Методы работы с кортежами
  8. Методы работы со множествами
  9. Особенности цикла for
  10. Условный цикл while
  11. Функции с позиционными и именованными аргументами
  12. Анонимные функции
  13. Рекурсивные функции
  14. Функции высшего порядка, замыкания и декораторы
  15. Методы работы с файлами и файловой системой
  16. Регулярные выражения
  17. Основы скрапинга и парсинга
  18. Основы ООП: инкапсуляция и наследование
  19. Основы ООП: абстракция и полиморфизм
  20. Графический интерфейс на Tkinter
  21. Основы разработки игр на Pygame
  22. Основы работы с SQLite
  23. Основы веб-разработки на Flask
  24. Основы работы с NumPy
  25. Основы анализа данных с Pandas

***

Материалы по теме

  • 🐍🕹️ Как написать игру на Python: 5 игровых движков
  • 🐍 Пишем Тетрис на Python с помощью библиотеки Pygame

Понравилась статья? Поделить с друзьями:
  • Диван мальта угол инструкция по сборке
  • Гентамициновая мазь инструкция по применению цена от чего помогает
  • Рефлектометр ри 407 руководство по эксплуатации
  • Мануал от цистита инструкция по применению цена
  • Тауфон глазные капли инструкция по применению для профилактики взрослым отзывы