Журнал LinuxFormat - перейти на главную

LXF112:Игры

Материал из Linuxformat
Перейти к: навигация, поиск
Программирование Пишем игру «ралли» и изучаем PyGame


Содержание

Кодируем: Напишем ралли

Два месяца назад мы выступили с учебником по программированию, создав клон Space Invaders под названием PyInvaders. Реакция оказалась фантастической: из сообщений на форумах и полученных электронных писем мы поняли, что вам понравилось изучать новые технологии и улучшать код. Огромное спасибо! В этом месяце мы запускаем новую серию из двух статей, продолжающую тему программирования игр, и первая из них – ралли.

Как и раньше, использовать будем PyGame – сочетание легко читаемого языка Python и мультимедиа-библиотек SDL. Синтаксис Python очень прост для понимания: он не обременен фигурными скобками и прочей неразберихой, обычной для других языков программирования. А значит, это идеальный вариант для новичков: наши статьи не учат программировать с нуля, но тем, кто уже что-то пробовал, будет нетрудно им следовать.

Если вы сроду не видали ни строчки кода на Python, возьмите LXF110, там есть небольшой ликбез. В этом месяце мы расширим свои знания PyGame в трех основных областях:

  • Обработка мыши Если в PyInvaders использовалась только клавиатура, то здесь мы узнаем, как отслеживать положение курсора мыши для управления игроком.
  • Воспроизведение музыки и звуков Что за игра без бодрящих мелодий и четких звуковых эффектов? Полная серость, вот что. PyGame трудоустроит ваши колонки.
  • Использование текста и шрифтов В PyInvaders мы использовали только спрайты, но в играх обычно показывается количество очков. К счастью, в PyGame есть несколько прекрасных подпрограмм для создания и управления текстом всего в несколько строк кода.

Часть 1 Обзаводимся нужными файлами

Для нашего ралли Pyracer понадобятся несколько звуковых файлов и пара картинок. Если вы в творческом расположении духа (весьма вероятно, коли вы прочли главную статью номера!), можете создать их сами по приведенным ниже инструкциям. Если нет, возьмите их из архива в разделе Журнал/PyRacer нашего DVD.

Исходный код Pyracer будет сохранен в файле pyracer.py, рядом с которым мы расположим каталог data. В него следует поместить:

  • car_player.png Изображение автомобиля игрока размером 40х100 пикселей (вид сверху, автомобиль движется в окне снизу вверх). Для отрезания пикселей, не относящихся к автомобилю, используется прозрачность PNG. Поэтому это не сплошной прямоугольник, и за ним будет видна дорога.
  • car_enemy.png Автомобиль врага: предыдущее изображение, отраженное зеркально по вертикали.
  • whiteline.png Полоска размером 20 на 80 пикселей. Белая или с текстурой, если вам хочется чего-нибудь посимпатичнее. Как можно догадаться, она нужна для линии разметки дороги.
  • tree.png Изображение размером 65 на 110 пикселей. Да, это дерево! Мы размножим его и будем перемещать вниз, чтобы создать ощущение скорости. Как и в случае с картинкой автомобиля, прозрачность позволит увидеть фон около ствола.
  • background.mod Фоновая музыка в формате MOD, созданная, например, программой SoundTracker (см. раздел Звук на DVD). Можно легко создать собственный мотивчик или найти что-нибудь в Интернете (но помните об авторских правах!).
  • crash.wav Прекрасный звук удара, который будет воспроизводиться, когда автомобиль игрока врезается в другой автомобиль.

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

Часть 2 Запускаем двигатель

Посмотрим на программу. Вы увидите, что код в архиве на нашем DVD разделен на части, которые мы и будем разъяснять. Обратите внимание, что код на языке Python имеет отступы, выполняемые при помощи клавиши табуляции – в журнале они сделаны чуть поменьше, чтобы строки кода не заламывались. Также помните, что в код можно добавлять комментарии (с помощью символа #). Это удобно в тех случаях, когда нужно оставить напоминание или что-то пояснить.

 from pygame import *
 import random

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

 class Sprite:
 def __init__(self, xpos, ypos, filename):
   self.x = xpos
   self.y = ypos
   self.bitmap = image.load(filename)
 def render(self):
   screen.blit(self.bitmap, (self.x, self.y))

Может, вы помните код в учебнике по PyInvaders, похожий на этот. Класс, в терминах объектно-ориентированного программирования, это определение набора данных и связанных с ними методов. Представьте себе это как чертеж коробки, объясняющий, что в ней содержится и как это использовать. Определение класса class Sprite здесь только описывает класс, а делать ничего не делает; потом мы создадим настоящие коробки (экземпляры класса).

Тренируйте координацию

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

В компьютерной графике (по большей части!) отсчет ведется с левого верхнего угла вниз и вправо. Так если размер окна установлен в 640 на 480 пикселей, то точка с координатами 0,0 находится в левом верхнем углу (смещение – ноль пикселей вправо и ноль пикселей вниз). Точка 0,240 – это ноль пикселей вправо и 240 вниз, т.е. середина экрана по вертикали. Точка 320,240 – это центр экрана, а 639,479 – правая нижняя точка.

Минуточку… а почему 639, а не 640? Ну, обычно отсчет ведется с 0, поэтому 639 – то же самое, что 640, если считать с единицы. Подобные штучки всегда держат хакеров начеку!

Начнем, innit?

При создании нового экземпляра класса Sprite вызывается метод __init__, и, как вы видите, он принимает четыре параметра. О self можно забыть – это внутренняя переменная класса, поэтому при создании объекта класса Sprite мы передаем ему начальные координаты X и Y и имя файла, из которого нужно загрузить картинку спрайта. Этот класс также содержит метод отрисовки, отображающий спрайт на экране. Позже мы посмотрим, как он используется.

 def Intersect(s1_x, s1_y, s2_x, s2_y):
   if (s1_x > s2_x - 40) and (s1_x < s2_x + 40) and (s1_y > s2_y -
 40) and (s1_y < s2_y + 40):
     return 1
   else:
     return 0

Возможно, вы помните этот код в PyInvaders. Это метод определения столкновения, и выглядит он непознаваемым, хотя на деле не так уж сложен. Он принимает четыре параметра: координаты X и Y от двух спрайтов. Потом он определяет, перекрывают ли спрайты друг друга, и возвращает 1, если да, и 0 если нет. В коде часто встречается число 40 – это ширина спрайтов автомобилей. (Да, признаю: числа лучше жестко не кодировать; в игре побольше размер спрайта описывался бы переменной, ради гибкости. Однако здесь у нас во главе угла простота.)

  init()
  screen = display.set_mode((640,480))
  display.set_caption(‘PyRacer’)

Далее мы велим PyGame слезть со стула и поработать с методом init(), а потом устанавливаем видеорежим и заголовок окна нашей игры.

  mixer.music.load(‘data/background.mod)
  mixer.music.play(-1)

Вот наша первая вылазка в мир звука. В PyGame есть модуль mixer, поддерживающий музыку и звуковые эффекты, и в первой строке кода выше мы загружаем мелодию в формате MOD – она будет проигрываться в фоне. Вторая строка запускает воспроизведение музыки, и ей можно передать параметр, указывающий, сколько раз воспроизводить фрагмент. Если установить его равным -1, как мы и сделали, то музыка будет играть бесконечно (ну, пока мы не закроем игру).

  playercar = Sprite(20, 400, ‘data/car_player.png)
  enemycar = Sprite(random.randrange(100, 500), 0, ‘data/car_
  enemy.png)
  tree1 = Sprite(10, 0, ‘data/tree.png)
  tree2 = Sprite(550, 240, ‘data/tree.png)
  whiteline1 = Sprite(315, 0, ‘data/whiteline.png)
  whiteline2 = Sprite(315, 240, ‘data/whiteline.png)

Теперь применим определенный нами класс Sprite. В строке 1 мы говорим, что хотим создать новый объект Sprite с именем playercar и начальными координатами X=20 (т.е. 20 пикселей вправо от левого верхнего угла окна) и Y=400, а для изображения спрайта возьмем car_player.png в каталоге с данными.

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

 scorefont = font.Font(None, 60)
 crasheffect = mixer.Sound(‘data/crash.wav)

Осталось только задать шрифт для показа количества очков и звуковой эффект для столкновения. Для первого вызовем подпрограммы ‘font’ библиотеки PyGame; чтобы создать новый объект шрифта – укажем параметр ‘None’, это означает выбор некого доступного шрифта (можно указать и конкретный шрифт, но тогда игру будет сложнее переносить на другие платформы). Число 60 определяет размер шрифта. Для второго создадим новый объект звука crasheffect, используя файл data/crash.wav.

score = 0
maxscore = 0
quit = 0

Прежде чем запустить основной цикл программы, установим несколько переменных. Учтите, что в Python инициализировать их не обязательно, и часто можно просто с ходу использовать их там, где понадобится, но явное их объявление делает код гораздо проще для понимания – по крайней мере, ясно, что происходит. Score – текущее количество очков, maxscore содержит максимальное количество очков, набранное по ходу игры (оно не сбрасывается, когда автомобили сталкиваются), а quit изменяется на 1, когда окно игры получает событие закрытия (например, пользователь нажимает на кнопку закрытия в заголовке).

 while quit == 0:
   screen.fill((0,200,0))
   screen.fill((200,200,200), ((100, 0), (440, 480)))

Ну вот, начинается основной цикл. Пока quit равен нулю, выполняется код с отступом. В начале каждого цикла мы рисуем фон, сперва заполняя весь экран зеленым цветом. 0,200,0 – это тройка RGB (красный, зеленый, синий), в ней красная составляющая равна нулю, довольно сильная зеленая составляющая (200) и нулевая синяя составляющая. (Максимальное значение каждой из них – 255.)

Зеленая, зеленая трава

Вторая строка screen.fill рисует серую дорогу на зеленом фоне. Так как дорога не заполняет весь экран, мы передаем два дополнительных параметра – начальную точку (100 пикселей вправо и 0 пикселей вниз) и конечную точку (440 пикселей вправо и нижняя граница окна).

 tree1.render()
 tree1.y += 10
 if (tree1.y > 480):
   tree1.y = -110
 tree2.render()
 tree2.y += 10
 if (tree2.y > 480):
   tree2.y = -110

Теперь рисуем деревья: одно с левой стороны дороги и одно с правой. Инициализируя эти переменные раньше, мы приравняли вертикальную координату tree1 нулю, а tree2 – 240. Таким образом, одно появляется в верхней части экрана, а второе – в середине. Это немного разнообразит пейзаж.

В каждом цикле мы вызываем метод render() для объектов деревьев и увеличиваем их вертикальные координаты на 10 пикселей. Когда деревья опускаются за пределы экрана, мы восстанавливаем их начальные координаты – вместо нуля используем -110. Если бы мы использовали 0, деревья волшебным образом появились бы в верхней части окна целиком, а не постепенно.

 whiteline1.render()
 whiteline1.y += 10
 if (whiteline1.y > 480):
   whiteline1.y = -80
 whiteline2.render()
 whiteline2.y += 10
 if (whiteline2.y > 480):
   whiteline2.y = -80

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

 enemycar.render()
 enemycar.y += 15
 if (enemycar.y > 480):
   enemycar.y = -100
   enemycar.x = random.randrange(100, 500)

Вот фрагмент кода, который отвечает за встречные автомобили. Сначала мы рисуем автомобиль в его текущей позиции, затем увеличиваем ее вертикальную координату на 15 пикселей (то есть он перемещается быстрее, чем деревья и белые полоски). Когда автомобиль соперника уходит за пределы экрана, мы помещаем его наверх, так что он снова элегантно появляется на экране, и устанавливаем его горизонтальную координату (X) в случайное значение между 100 и 500.

 x, y = mouse.get_pos()
 if (x < 100):
   x = 100
 if (x > 500):
   x = 500
 playercar.x = x
 playercar.render()

Пора поработать с мышью. Чтобы определить координаты ее курсора в PyGame, просто вызовите метод mouse.getpos(), возвращающий два значения – горизонтальную и вертикальную координаты. В нашем случае автомобиль игрока остается в нижней части экрана, поэтому нам интересна только координата X (горизонтальная).

И мы также хотим, чтобы автомобиль оставался на дороге, когда игрок перемещает мышь (если только вы не собрались добавить пару методов для обработки столкновения с деревом!). Итак, мы ограничиваем координату X значениями в 100 пикселей от левой границы окна и в 140 от правой. Помните, что ширина спрайта автомобиля – 40 пикселей: это означает, что правая сторона автомобиля может быть в точке с координатой X=540.

Слова, слова

 scoretext = scorefont.render(‘Score: ‘ + str(score), True,
 (255,255,255), (0,0,0))
 screen.blit(scoretext, (5,5))

Вывести текст на экран в PyGame очень легко. В первой строке мы создаем новый битовый образ картинки scoretext. Методу scorefont.render() передаются четыре параметра: текст, который мы хотим вывести, нужно ли его сглаживать, цвет текста и цвет фона. Обратите внимание, что мы добавляем слово ‘Score:’ к действительному значению переменной, конвертируя последнее в строку с помощью функции str(score).

Во второй строке мы просто выводим объект scoretext в заданную позицию на экране. Мы сделали отступы в 5 пикселов по горизонтали и вертикали, потому что так аккуратнее.

 if (Intersect(playercar.x, playercar.y, enemycar.x, enemycar.y)):
    mixer.Sound.play(crasheffect)
    if (score > maxscore):
       maxscore = score
    score = 0

Здесь в дело вступает метод Intersect, который мы написали раньше. Он проверяет, не перекрываются ли автомобиль игрока и встречный автомобиль; если да, то воспроизводит звук столкновения, проверяет, является ли текущее количество очков максимальным, и если да, обновляет maxscore. Затем он сбрасывает текущее количество очков и продолжает цикл игры.

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


В процессе выполнения программы PyGame могут получать события от оконного менеджера и клавиатуры. Здесь мы проверяем, есть ли события, ожидающие обработки, и если да,то проверяем, есть ли среди них событие QUIT (игрок попытался закрыть окно). Если есть, мы устанавливаем переменную quit в 1, что завершает основной цикл.

 display.update()
 time.delay(5)
 score += 1

display.update() – важная функция PyGame: она отображает на экране все фоновые действия и действия со спрайтами, и без нее мы бы ничего не увидели. Затем мы добавляем задержку, чтобы игра не работала слишком быстро на мощных компьютерах, и увеличиваем счетчик очков. Вы заметите, что здесь в коде заканчиваются отступы, и мы возвращаемся в начало цикла (while).

 print ‘Your maximum score was:’, maxscore

А последняя строка просто выводит максимальное количество очков в окно терминала.

Чтобы использовать код с нашего DVD, скопируйте архив pyracer.tar.gz из раздела Журнал/Pyracer в свой домашний каталог, откройте окно терминала и введите команды:

 tar xfvz pyracer.tar.gz
 cd pyracer
 python pyracer.py

Они распакуют содержимое архива .tar.gz, переключается в созданный каталог и запускает игру. Теперь можно отредактировать pyracer.py, чтобы добавить туда новые возможности, описанные во врезке, или приукрасить спрайты в каталоге данных – удачи вам! LXF

Новые возможности

Разобрались с кодом? Прекрасно – пора кое- что добавить! Попробуйте-ка дополнительные возможности …

  • Легко Случайные скорости. Сейчас встречные автомобили всегда движутся с одинаковой скоростью (15 пикселей на цикл). Перед основным циклом можно объявить еще одну переменную, а затем в цикле каждый раз записывать в нее случайное значение и учитывать его при сдвиге машины противника. Когда автомобиль соперника исчезает с экрана, вы устанавливаете новое случайное значение скорости для следующего.
  • Средний уровень Индикатор повреждений. Не слишком ли жестоко сбрасывать счетчик очков при столкновении? Может, позволить автомобилю несколько столкновений, прежде чем счетчик обнулится? Это не так сложно, но сделать это можно разными способами. Попробуйте добавить индикатор столкновений в правый верхний угол экрана, который будет увеличиваться от 0% до 100%, на основе методов работы с текстом.
  • Трудно Больше машин. Несколько автомобилей на экране могут реально обогатить игру, но здесь нужно быть осторожным. Нельзя

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

Обсудим, что у вас получилось, на сайте http://unixforum.org !

Персональные инструменты
купить
подписаться
Яндекс.Метрика