- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF110:Python
Материал из Linuxformat.
- Программирование Стрелялка в стиле «убей-их-всех» на PyGame для ретро-аркадных приколовр
Python |
---|
Python для профессионалов |
---|
Python + PyGame |
---|
Python + Web |
---|
Python + Clutter |
---|
Содержание |
Создаем свою игру
- Руки чешутся пострелять? А также попрограммировать? Присоединяйтесь к Майку Сондерсу: возродим классическую аркаду 1978 года всего 100 строками кода.
Программирование – великая вещь. Можно создавать нечто абсолютно новое, стимулировать свой мозг и получать удовольствие в процессе – особенно если вы пишете игру. В последние месяцы вы просили нас опубликовать более проектно-ориентированный учебник, и сегодня мы попытаемся написать наше видение игры Space Invaders [Космические захватчики] под названием PyInvaders. Кто устал от сухой теории программирования, не паникуйте: мы займемся созданием Клевых Штук™ и сделаем игру рабочей, а не станем трещать об алгоритмах, структурах данных и объектно-ориентированном инкапсулированном полиморфизме. Или о чем-то типа этого.
Однако следование данному руководству возможно, только если у вас есть опыт программирования. Мы не собираемся все разжевывать; если вы баловались написанием кода и раньше и знаете ваши массивы как свои пять пальцев, проблем у вас не будет. Для полных нулей в программировании некоторые термины будут загадочными, но вы не обязаны понимать их все. Почерпните из статьи то, что сможете, возьмите исходный код с LXFDVD и начните эксперименты, внося собственные изменения. Так начинали все программисты!
Наш выбор языка программирования – Python, потому что его простой синтаксис и чистота кода очень удобны для чтения. PyGame, расширение языка, являющееся оберткой мультимедийной библиотеки SDL вокруг Python, обеспечит графический водопровод для нашей программы, спасая нас от тяжкой ручной работы манипулирования изображениями. Большинство дистрибутивов имеют Python предустановленным, а PyGame доступен в ближайшем репозитории, поэтому собирайте инструменты, открывайте текстовый редактор, и начнем кодирование…
Часть 1 Пример на Python
Прежде чем приступать к любому программному проекту, важно разобраться в языке, который вы собираетесь использовать, хотя бы в его основах. Учитывая, что 99% программ комбинируют манипулирование переменными (типизированные хранилища данных), вызов подпрограмм (отдельных кусков кода), и действий с результатом (если a = b делаем с), можно показать работу Python на очень кратком примере. (Если вы заядлый Python’щик, просто пропустите его)
def Multiply(x, y): z=x*y return z a=5 b = 10 print “a is”, a, “and b is”, b answer = Multiply(a, b) if answer > 10: print “Result is bigger than 10” else: print “Less or equal to 10”
Программа короткая, но она демонстрирует массу возможностей Python в действии. Сохраните этот код в файле под именем test.py в вашей домашней директории, затем откройте терминал и введите python test.py для его запуска. Вы должны увидеть несколько строк вывода на экране – это не интерактивная программа, поэтому она возвратит вас назад в командную строку.
Первые три строки определяют (define) функцию с именем Multiply – это кусок кода, который не запускается при старте приложения, а является разновидностью подпрограммы, вызываемой позже. x и y в строке описания функции – две переменные, которые нам надо послать в подпрограмму при ее вызове. Далее вы можете увидеть новую переменную z, созданную внутри функции – ей присваивается произведение x и y. Потом мы возвращаем значение этой переменной обратно в вызывающую программу.
После функции Multiply начинается выполнение основного кода. Мы знаем это, потому что здесь нет отступов – то есть знаков табуляции или пробелов в начале строки. В Python все завязано на использовании отступов, показывающих, где идет код, где он – часть функции или цикла и т.п. В нашем случае отступов нет, потому что это не часть предшествующей функции Multiply, и запуск программы начинается отсюда.
Мы создаем две переменные с именами a и b, присваивая им значения 5 и 10 соответственно. (Переменная является контейнером для данных – она может содержать разную информацию в течение всего времени работы программы.) Мы выводим содержимое переменных и затем посылаем его в функцию Multiply, которую создали ранее. Помните return в теле функции? Так вот, он вернет результат умножения, и мы сохраним его в новой переменной answer. Наконец, проверим, не больше ли переменная answer десяти; если да, печатаем сообщение. Если она меньше (или равна 10), печатаем другое сообщение. Попробуйте менять значения в этой программе и поэкспериментировать с кодом, чтобы освоиться с Python – не бойтесь, вы не сотрете все ваши файлы и не уничтожите вашу систему, допустив ошибку!
Нужна помощь?
В вашу Python-программу можно добавить комментарии, набрав символ решетки (#) перед их текстом. Вы можете поместить комментарий на своей строке, либо приписать в существующей, после кода – это полезно для отметки внесенных изменений или потенциальных ошибок.
Чтобы разобраться в этом уроке, не обязательно быть гуру Python. Если вы полностью поняли предыдущий фрагмент кода и пояснения к нему, вы готовы идти дальше. Но если вы хотите получше разобраться в Python, в интернете для этого есть много великолепных ресурсов. См. превосходное руководство пользователя от Гвидо ван Россума, создателя Python, на http://docs.python.org/tut.
Часть 2 Вторжение инопланетян
Перед написанием кода нужно разжиться графикой. Текстовый режим Space Invaders увеличил бы ваш рейтинг среди технарей, но лучше не давать ржаветь нашим видеокартам. Для PyInvaders понадобится пять небольших изображений; их можно создать вручную с помощью GIMP или позаимствовать из LXFDVD (в разделе Журнал/PyInvaders). Вот что вам нужно, если вы захотите создать их сами:
- backdrop.bmp Изображение размером 640 x 480 пикселей, оно будет фоном игры. Лучше не делать его слишком ярким или сложным, это будет только отвлекать вас от спрайтов.
- hero.bmp Изображение корабля игрока размером 32 x 32 пикселя; те части, которые должны быть прозрачными, надо залить черным цветом.
- baddie.bmp Тоже корабль, но со злобными пришельцами.
- heromissile.bmp и baddiemissile.bmp Снова 32 x 32 пикселя с черным цветом для прозрачных мест, с ракетой игрока, направленной вверх, и противника, направленной вниз.
Создайте директорию PyInvaders в вашей домашней папке, а затем сделайте поддиректорию с именем data, содержащую указанные выше файлы.
Давайте также обсудим, что мы будем делать в игре типа Space Invaders. Если вы никогда не видели ее раньше, то знайте: в ней есть несколько шеренг пришельцев, снующих вправо и влево в верхней части экрана, стреляющих ракетами и постепенно сдвигающихся вниз, в направлении игрока. Вы тоже можете палить во врагов ракетами: ваша цель – уничтожить их, прежде чем они уничтожат вас.
Код в целом
Чтобы сделать себе жизнь проще (а код компактнее), ограничимся одним рядом пришельцев и не будем делать счетчик очков или бонусы. Эти вещи можно добавить потом, когда вы разберетесь в происходящем! Здесь мы пройдемся по участкам кода, поясняя их, а полную версию программы можно найти в отдельном файле (PyInvaders.py) раздела Журнал/PyInvaders на LXFDVD. Теперь читайте текст дальше, чтобы понять, как все это работает.
from pygame import * import random
Эти первые две строчки очень просты: они сообщают Python, что мы хотим использовать модуль PyGame, чтобы загружать изображения и легко управлять ими на экране, и дают нам потом генерировать случайные числа.
class Sprite: def __init__(self, xpos, ypos, filename): self.x = xpos self.y = ypos self.bitmap = image.load(filename) self.bitmap.set_colorkey((0,0,0)) def set_position(self, xpos, ypos): self.x = xpos self.y = ypos def render(self): screen.blit(self.bitmap, (self.x, self.y))
Далее идет класс Sprite. Кто близко знаком с объектно-ориентированным программированием, уже знает, как он работает, а кто не знает, представьте его как тип хранилища для данных и команд. Этот код не запускается при старте программы – он просто говорит: «Вот хранилище данных и команд, называемое Sprite, его мы используем позднее». Класс устанавливает переменные для спрайтов – главные здесь х и у, они будут хранить координаты позиции спрайта на экране. Подпрограмма __init__ запускается, когда мы хотим создать новый экземпляр класса (новое хранилище на основе этого описания), и загружает изображение спрайта по имени filename.
Строка set_colorkey говорит PyGame, что мы хотим сделать черные (0,0,0 в формате RGB) пиксели прозрачными.
Новичков в объектно-ориентированном программировании все это может малость запутать. Но опять же, просто считайте это видом хранилища, содержащего переменные и подпрограммы (они помечены словом def), причем можно создавать много образчиков (экземпляров) этого хранилища с различным содержанием. Итак, создадим 10 экземпляров класса Sprite для врагов, один для игрока и так далее.
def Intersect(s1_x, s1_y, s2_x, s2_y): if (s1_x > s2_x - 32) and (s1_x < s2_x + 32) and (s1_y > s2_y - 32) and (s1_y < s2_y + 32): return 1 else: return 0
Хоть этот кусок кода и выглядит сложным, не дайте ему вас смутить. Это ведь функция (обозначается def), и она не выполняется при запуске программы, но может быть вызвана позже. Здесь проверяется перекрытие двух спрайтов – он берет координаты пикселей х и у одного спрайта (переменные s1_x и s2_x) и сравнивает их с позиций другого (s2_x и s2_y), возвращая 1, если они перекрываются. Вуаля: простое обнаружение столкновений! Заметим, что в код жестко «зашиты» спрайты размером 32 x 32 пикселя, но вы можете изменить числа, если позже будете использовать спрайты побольше.
init() screen = display.set_mode((640,480)) key.set_repeat(1, 1) display.set_caption(‘PyInvaders’) backdrop = image.load(‘data/backdrop.bmp’)
Далее мы инициализируем PyGame, настраиваем режим экрана, конфигурируем повтор клавиш на более быстрое значение (для управления кораблем игрока), устанавливаем текст заголовка и загружаем фоновую картинку.
enemies = [] x=0 for count in range(10): enemies.append(Sprite(50 * x + 50, 50, ‘data/baddie.bmp’)) x += 1 hero = Sprite(20, 400, ‘data/hero.bmp’) ourmissile = Sprite(0, 480, ‘data/heromissile.bmp’) enemymissile = Sprite(0, 480, ‘data/baddiemissile.bmp’)
Здесь мы создаем новый список объектов – “enemies”, врагов (список в Python похож на массив на других языках, хотя гораздо более гибок). Список сперва пуст, поэтому мы в цикле добавляем (append) 10 новых объектов класса Sprite. Здесь вы можете увидеть, каким образом мы создаем экземпляры предварительно созданного класса (или копии хранилища), предоставляя первоначальные значения координат x', y и имя файла. Часть 50 * х + 50 выглядит немного странно, но это означает, что по горизонтали новые вражеские спрайты выстраиваются ступенчато. То есть х-координата первого спрайта равна 50, следующего – 100, и так далее, пока мы движемся по циклу.
К оружию! Залп!
Последние три строки в прошлом куске элементарны для понимания – они загружают спрайт для игрока (героя), ракету игрока и ракету противника. Вы помните, что мы выбрали режим экрана 640 x 480? Ну, а здесь мы установили y-координату ракеты (по вертикали) в 480, вблизи нижней части экрана, поскольку мы не хотим показывать их до тех пор, пока они не выстрелят.
quit = 0 enemyspeed = 3 while quit == 0: screen.blit(backdrop, (0, 0)) for count in range(len(enemies)): enemies[count].x += enemyspeed enemies[count].render() if enemies[len(enemies)-1].x > 590: enemyspeed = -3 for count in range(len(enemies)): enemies[count].y += 5 if enemies[0].x < 10: enemyspeed = 3 for count in range(len(enemies)): enemies[count].y += 5
Теперь беремся за основную часть игры. quit представляет собой переменную типа «да/нет» (1/0), она используется для определения конца игры (т.е. либо игрок уничтожил всех «плохишей», либо сам сбит их ракетой). Затем, enemyspeed определяет скорость перемещения врагов – вы можете поиграть с этим параметром позднее, чтобы усложнить игру.
После этого игра переходит в основной цикл, начинающийся в строке while. Во-первых, мы рисуем фон со значениями координат х и у равными 0 и 0 соответственно (считая от верхнего левого угла экрана). Потом запускаем с помощью цикла подсчет врагов из ранее созданного списка объектов. len(enemies) сообщает, сколько вражеских объектов находятся в списке – это число будет уменьшаться от 10 при каждом убийстве игроком «плохиша». В цикле мы увеличиваем x-координату для каждого объекта спрайта неприятеля, затем вызываем для него функцию render() в описании Sprite из начала файла.
Следующие два цикла определяют направление и вертикальную позицию ряда врагов. Если крайний правый враг, enemies[len(enemies)-1], достигает правой стороны экрана (590 пикселей), то посылается сигнал движения в другом направлении путем инвертирования значения скорости. Кроме того, враги передвигаются вниз путем добавления 5 к их у-координате (по вертикали).
Следующий цикл делает то же самое, но для левой части экрана.
if ourmissile.y < 479 and ourmissile.y > 0: ourmissile.render() ourmissile.y -= 5 if enemymissile.y >= 480 and len(enemies) > 0: enemymissile.x = enemies[random.randint(0, len(enemies) - 1)].x enemymissile.y = enemies[0].y
Пора заняться ракетами. Первое условие if создает изображение ракеты игрока, если она на экране (то есть в игре), уменьшая на 5 пикселей ее y-координату на каждой итерации цикла, чтобы она перемещалась вверх по экрану. Вторая часть кода проверяет, есть ли в игре вражеские ракеты – если это не так, она создает одну новую от случайно выбранного противника. Она делает это путем установки x-координаты ракеты одного из врагов: random.randint(0, len(enemies) - 1) выбирает врага в списке объектов в диапазоне от 0 (первый противник) до последнего врага, согласно размеру списка.
if Intersect(hero.x, hero.y, enemymissile.x, enemymissile.y): quit = 1 for count in range(0, len(enemies)): if Intersect(ourmissile.x, ourmissile.y, enemies[count].x, enemies[count].y): del enemies[count] break if len(enemies) == 0: quit = 1
За рамки столкновений
Теперь об обнаружении столкновений. Если спрайт игрока (героя) вступил в контакт со спрайтом противника, то надо выйти из игры. В следующем участке кода мы перебираем список врагов в цикле ‘for’ c целью обнаружения, не пересекается ли кто-нибудь из них с нашей ракетой. Если да, мы удаляем объект противника, которого коснулся снаряд, из списка и прерываем цикл (чтобы не продолжать работу с уже несуществующим объектом). По удалении вражеского объекта список становится короче. Наконец, мы проверяем, не равна ли длина списка врагов нулю – это произойдет, когда игрок уничтожит всех противников. Если да, выходим с победой!
for ourevent in event.get(): if ourevent.type == QUIT: quit = 1 if ourevent.type == KEYDOWN: if ourevent.key == K_RIGHT and hero.x < 590: hero.x += 5 if ourevent.key == K_LEFT and hero.x > 10: hero.x -= 5 if ourevent.key == K_SPACE: ourmissile.x = hero.x ourmissile.y = hero.y
А вот блок поддержки клавиатуры. Мы получаем список событий SDL (клавиатура, мышь, оконный менеджер и т.д.) и работаем с ним. Если мы видим событие QUIT, это означает, что пользователь пытался закрыть окно, поэтому устанавливаем переменную quit, которая вызовет прерывание нашей программы в основном цикле ‘while’. В свою очередь, если произойдет событие KEYDOWN, то мы должны его обработать. Тут все кристально ясно: если нажата клавиша курсора вправо и мы не вылетели за экран, добавьте 5 к горизонтальной позиции игрока. Похожее действие необходимо выполнить для клавиши курсора влево, но наоборот. Мы также проверяем клавишу Пробел: если она нажата, порождаем новую ракету, поместив ее в положение, где находится спрайт игрока.
enemymissile.render() enemymissile.y += 5 hero.render() display.update() time.delay(5)
И вот последний кусок кода. Мы выводим вражескую ракету и перемещаем ее вниз по экрану, а затем выводим спрайт игрока. Отметим, что display.update() – существенная часть программирования PyGame. Что бы вы ни делали на экране, оно не будет реально отображаться, пока вы не вызовете эту подпрограмму; именно поэтому ее вызов расположен в конце основного цикла игры while. Наконец, мы добавляем задержку, чтобы игра не протекала слишком быстро – попробуйте с ней поэкспериментировать.
Вот и все
Код завершен! Он достаточно большой, если вы новичок в Python и PyGame, но если тщательно ему следовать, то можно понять его смысл. Возьмите полный файл (PyInvaders.py) из раздела Журнал/PyInvaders LXFDVD, затем скопируйте его в созданную ранее директорию PyInvaders, в которой также содержится директория с изображениями. Затем откройте терминал и введите:
cd PyInvaders python pyinvaders.py
для запуска игры. Если у вас возникли проблемы с его работой, или вы совершенно запутались в какой-то из концепций программирования, зайдите на наш форум http://unixforum.org/ и оставьте сообщение в разделе Программирование. Кто-нибудь обязательно подаст вам руку помощи. LXF
Переходим на новый уровень
Если вы разобрались в коде, почему бы не украсить PyInvaders новыми функциями? Ниже приведены советы о возможных изменениях…
- Легко Добавить счетчик очков. Все, что для этого нужно – добавить score = 0 где-то в начале кода, создав новую переменную, а затем увеличивать его (score += 1) при каждом попадании в противника. Потом при выходе из программы вы можете вставить print “You scored:” score для отображения баллов игрока.
- Средне Проверка столкновения «ракета-ракета». Сейчас игра определяет, когда ракета попала в космический корабль, но не когда одна ракета столкнулась с другой. Можно добавить проверку посреди кода, рядом с нынешней подпрограммой обнаружения столкновений, а потом сбрасывать позиции ракет игрока и противника, если они соприкасаются.
- Сложно Добавить еще один ряд инопланетян. Вам придется продублировать часть кода. Во-первых, если вы захотите создать второй список спрайтов врагов, позиция y должна быть больше, чем в существующем ряде (например, 100). Тогда вам придется еще раз добавить проверку столкновений и создать еще одну вражескую ракету. Она безусловно усложнит игру!
Есть немало и других идей, которые можно реализовать: например, изменять спрайты при попадании ракеты или добавить джойстик. См. http://www.pygame.org/docs – в частности, раздел Tutorials и руководство игры Chimp для получения справки по использованию звуковых эффектов.