- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF103:Игра с мячом
Материал из Linuxformat.
Программирование |
---|
|
Содержание |
Кодируем: игра с мячом!
- ЧАСТЬ 3 На последнем уроке этой серии, Майк Сондерс займется кодированием простой, но захватывающей игры...
За последние два урока мы неплохо набили руку, создав IRC-бота и программу всплывающих карточек – вещи довольно серьезные; давайте же в последнем проекте развлечемся игрой. Создание большинства современных игр требует тысячи человеко-часов, не считая армии художников и музыкантов, но все еще есть область, где хакеры-одиночки могут написать что-то забавное. В конце концов, для создания Тетриса не потребовалась команда из 500 кодеров и бюджет голливудского фильма – Алексей Пажитнов вполне обошелся своими силами (конечно, пока подлые акулы капитализма с запада не подхватили его идею...). Как и для проекта прошлого месяца, в качестве основы нашего проекта используем Python и PyGame. Кстати, уже имеется три реализации Тетриса на базе PyGame, см. http://www.pygame.org/tags/tetris.
Если это первый номер LXF, который вы взяли в руки, и до этого вы ни строчки не написали на Python, вы будете приятно удивлены, насколько прост он в понимании: код Python знаменит в мире программирования своей самодокументированностью. А если вы знакомы с другим языком программирования, типа C или PHP, вы восхититесь простотой Python. Например, блоки кода выделяются отступами, а не фигурными скобками – взгляните сюда:
def saystuff(somestring): print "String passed: ", somestring saystuff("Wowzers")
Если вы новичок в Python, то убедитесь, что он у вас установлен (большинство дистрибутивов инсталлируют его по умолчанию, но если это не ваш случай, то он доступен в вашем менеджере пакетов). Введите указанный выше код в текстовом редакторе и сохраните в вашем домашнем каталоге как test.py. Затем откройте терминал и наберите:
python test.py
Если все в порядке, то Python интерпретирует код и выдаст строку текста. В данном примере просто определяется подпрограмма с именем saystuff – она выводит любую строку текста, которая ей передается. Вы можете видеть, что код подпрограмы имеет отступ на одну табуляцию. Выполнение начинается с первого вызова saystuff, приводящего к печати строки Wowzers. Вот так все просто; вы практически готовы к кодированию.
Еще один момент: для данного урока вам понадобятся модули PyGame. PyGame – это дополнительный слой, связывающий SDL и Python и позволяющий отображать картинки и использовать звуковые эффекты в ваших программах. Он широко распространен и скорее всего доступен в репозиториях вашего дистрибутива; в противном случае обратитесь к разделу Разработка нашего DVD. (Если вы выполнили урок проекта прошлого месяца, то PyGame у вас уже установлен!)
Скачки по кругу
Есть проблемы с проектом PyGame? Отслеживайте значения переменных, просто выводя их в терминал с помощью простого оператора print. Например, если в нашей игре с перемещением мяча что-то пошло наперекосяк, вы сможете легко выяснить причину, выводя значения переменных xmove или ymove – разместите в основном цикле игры print имя_переменной, и сможете следить за изменениями во время работы программы в терминале.
Хотя жанр игр весьма разнообразен, основы механики большинства из них, включающих передвижение спрайтов (изображений объектов), укладываются в следующее описание:
- Настраиваем экран, графику, счетчик очков и т.д.
- Запускаем цикл до момента смерти/выхода игрока.
- Отрисовываем графические объекты на экране.
- Получаем ввод пользователя (например от мыши или с клавиатуры).
- Следуем логике игры (например, ударил игрок врага?).
- Соответственно обновляем графику.
- Возвращаемся к шагу 3.
Мы напишем небольшую игру, где будет несколько мячей, прыгающих по экрану, а задача игрока – постараться избегать столкновения указателя мыши с мячами. Звучит просто? Ну, если мы введем некоторую случайность в движение мячей – то есть они не всегда будут двигаться с одинаковой скоростью – то все мигом осложнится. Вы, например, не сможете просто держать указатель мыши в нижнем левом углу экрана, потому что мяч может упасть туда в любой момент. Счетчик будет отслеживать, сколько секунд вы продержались. Это очень простая концепция, но она требует немалой ловкости с мышью и буквально лазерной фокусировки на экране.
Но все по порядку: давайте подумаем, как заставить один мяч скакать по экрану. Как мяч узнает, что пора менять направление? К счастью, для этого имеется очень простой метод: заведем две переменные, которые используем для изменения движения мяча. Для каждого прохода цикла игры будем добавлять их значения к позиции мяча. Если мяч, движется, например, вправо – это потому, что мы на каждом шаге цикла добавляем 1 к его горизонтальной координате. Если мяч ударяется о правый край экрана, то мы начинаем прибавлять к его горизонтальной координате -1, и он пойдет влево.
Представили? Если вы не понимаете, как это работает, вот вам программа на Python, демонстрирующая это в действии. Вы можете найти этот код в разделе DVD Журнал/CodeProject в файле ball1. py. Для запуска программы, кроме кода, нужно еще изображение с именем ball.png – это картинка размером 32х32 пикселя: закрашенный белый круг на черном фоне. На DVD оно есть, но вы можете нарисовать его за пару секунд в GIMP – создайте новое изображение 32х32 пикселя, залейте его черным, вырежьте кружок инструментом выделения окружности и залейте его белым. Сохраните файл как ball. png в том же каталоге, что и ball1.py, а затем запустите программу, набрав python ball1.py.
from pygame import * # Подключаем функционал PyGame! ballpic = image.load(‘ball.png’) done = False ballx = 0 # переменные позиции мяча bally = 0 ballxmove = 1 ballymove = 1 init() # Запуск PyGame screen = display.set_mode((640, 480)) # Получаем прекрасное окно display.set_caption(‘Ball game’) # И устанавливаем его заголовок while done == False: screen.fill(0) # Заполняем экран черным (цвет 0) screen.blit(ballpic, (ballx, bally)) # Рисуем мяч display.update() time.delay(1) # Задержка! ballx = ballx + ballxmove # Обновляем позицию мяча bally = bally + ballymove if ballx > 600: # Мяч достиг границ экрана? ballxmove = -1 if ballx < 0: ballxmove = 1 if bally > 440: ballymove = -1 if bally < 0: ballymove = 1 for e in event.get(): # Проверяем нажат ли ESC if e.type == KEYUP: if e.key == K_ESCAPE: done = True
Пройдемся по шагам. В первой строке мы сообщаем Python, что хотим использовать подпрограммы из библиотеки PyGame. Затем загружаем созданное нами изображение мяча, сохраняем его в объекте с именем ballpic и создаем логическую [true/false] переменную для определения завершения игры.
Следующие четыре строки очень важны: в них описываются переменные, управляющие позицией и перемещением мяча. ballx и bally хранят положение (в пикселях) мяча в нашем игровом окне: 0,0 означает верхний левый, а 640,480 – правый нижний пиксель. ballxmove и ballymove хранят числа, добавляемые к позиции мяча на каждом шаге; в начале мы устанавливаем в них 1, и когда начинается игра, 1 добавляется к ballx и bally на каждом шаге цикла, тем самым перемещая мяч направо вниз. Итак, при запуске программы наш мяч находится слева вверху и начинает двигаться по диагонали вправо вниз.
Затем мы открываем новое окно PyGame и запускаем основной цикл игры, заполняя (очищая) экран черным и отрисовывая наш мяч в текущей позиции (комментарии в коде обозначены символом #). Следующий кусок кода определяет, как будет двигаться мяч:
ballx = ballx + ballxmove bally = bally + ballymove if ballx > 600: ballxmove = -1 if ballx < 0: ballxmove = 1 if bally > 440: ballymove = -1 if bally < 0: ballymove = 1
В первых двух строках мы обновляем положение мяча по горизонтали (x) и вертикали (y), прибавляя две переменные передвижения. Если ballxmove и ballymove равны 1, то мяч переместится на 1 пиксель вправо и 1 пиксель вниз на каждом шаге цикла. Но затем оператор if проверяет, достиг ли мяч края экрана, и если это так, изменяет соответственно ballxmove и ballymove. Если, например, значение горизонтальной координаты мяча более 600 пикселей, он должен отскочить и начать двигаться влево – то есть мы начинаем прибавлять к его позиции -1 (по сути, вычитая 1). Несколькими строками кода мы создали впечатление, что мяч отскакивает от границ экрана – неплохо! Последние строки этой программы устанавливают связь с клавиатурой, чтобы вы могли в любой момент выйти из игры, нажав клавишу Esc.
Игра с мячом 2.0
Окончательная версия нашей игры – не прорыв на графическом фронте, но мы можем принарядить ее, добавив фоновое изображение. Важно только помнить, как мы определяем столкновение с мячом – мы ищем белые пиксели. Поэтому фоновое изображение не должно содержать пикселей совершенно белого цвета (255,255,255 RGB), не то игра закончится, когда мышь окажется над ними!
Подыщите изображение и измените его размер до 640х480. Если на изображении окажется белый пиксель, вы всегда можете понизить яркость в GIMP и избавиться от проблемы. Сохраните изображение рядом с ball2.py и назовите его background.jpg. Теперь, в ball2.py, введите следующий код под строкой ballpic.set_ colorkey:
backdrop = image.load('background.jpg')
Теперь наша фоновая картинка находится в памяти и готова к использованию. Нам необходимо отображать ее на экране на каждом шаге, так что переместитесь вниз по ball2.py и замените строку screen. fill(0) следующим:
screen.blit(backdrop, (0,0))
и фоновое изображение будет отрисовываться до мячей. Заметьте, что если изображение сложное (то есть в нем много цветов), этот дополнительный процесс немного замедлит игру – но вы можете подстроить скорость мячей и переменную delay, чтобы это скомпенсировать.
Пока все отлично – мы создали базовую структуру нашей игры. Теперь добавим мячей, а также определим столкновения курсора мыши с любым их них. Для первой задачи введем массив-словарь для отслеживания мячей. Это придаст программе гибкость: мы сможем иметь столько мячей, сколько захотим, не ограничиваясь ball0, ball1, ball2 и т. д. Словари – плевое дело в Python:
mydict = {‘Bach’: 100, ‘Handel’: 75, ‘Vivaldi’: 90} print mydict[‘Vivaldi’]
Здесь мы ставим числа в соответствие трем словам, а затем выводим значение, содержащиеся в ‘Vivaldi’, то есть 90. Используем словарь для хранения значений X, Y, X-перемещения и Y-перемещения каждого мяча – почти как структуру в C. Но если C погружает нас в сумятицу управления памятью, то в Python можно создавать наборы объектов-мячей без труда, давая им свои записи в словаре.
Наконец, подумаем об обнаружении столкновений. Как угадать, что указатель мыши столкнулся с мячом? Логически кажется очевидным идти от позиции каждого мяча и сравнивать ее с позицией курсора мыши. Но мы пойдем на хитрость: мячи белые, а фон черный, так почему бы просто не проверять, находится ли курсор мыши над белым пикселем? Это всего одна строка кода, и так быстро...
Вот код, который вы можете найти в файле ball2.py в разделе Журнал/CodeProject на DVD, вместе с картинкой ball.png, которую мы создали ранее (он точно такой же).
from pygame import * import random ballpic = image.load(‘ball.png’) ballpic.set_colorkey((0,0,0)) numballs = 10 delay = 5 done = False balls = [] for count in range(numballs): balls.append(dict) balls[count] = {‘x’: 0, ‘y’: 0, ‘xmove’: random.randint(1, 2),‘ymove’: random.randint(1, 2)} init() screen = display.set_mode((640, 480)) display.set_caption(‘Ball game’) event.set_grab(1) while done == False: screen.fill(0) for count in range(numballs): screen.blit(ballpic, (balls[count][‘x’], balls[count][‘y’])) display.update() time.delay(delay) for count in range(numballs): balls[count][‘x’] = balls[count][‘x’] + balls[count][‘xmove’] balls[count][‘y’] = balls[count][‘y’] + balls[count][‘ymove’] for count in range(numballs): if balls[count][‘x’] > 620: balls[count][‘xmove’] = random.randint(-2, 0) if balls[count][‘x’] < -10: balls[count][‘xmove’] = random.randint(0, 2) if balls[count][‘y’] > 470: balls[count][‘ymove’] = random.randint(-2, 0) if balls[count][‘y’] < -10: balls[count][‘ymove’] = random.randint(0, 2) for e in event.get(): if e.type == KEYUP: if e.key == K_ESCAPE: done = True if screen.get_at((mouse.get_pos())) == (255, 255, 255, 255): done = True print “You lasted for”, time.get_ticks()/1000, “seconds!”
Основная идея этой программы та же, что и раньше, но добавился смачный код, требующий объяснений. В самом начале, где мы загружаем изображение мяча, мы заодно устанавливаем его colorkey в (0,0,0), что соответствует черному в RGB (Red/Green/Blue – Красный/Зеленый/Синий). Так мы превращаем черные пиксели картинки нашего мяча в прозрачные. Это важно, когда у нас перемещается несколько мячей, если мы хотим, чтобы они накладывались изящно, не создавая черных углов поверх друг друга. Итак, у наших мячей будут отображаться только белые пиксели.
Следующие переменные, numballs и delay, влияют на сложность игры. numballs управляет числом мячей, а delay – время (в миллисекундах) остановки игры после каждой итерации цикла. Можете оставить их как есть; но если вы стремитесь к большей сложности, увеличьте число мячей и снизьте задержку.
Строка balls = [] создает новый массив объектов-мячей, и, в типичной манере Python, количество объектов не ограничивается (и не нужно указывать его прямо сейчас). Строка
for count in range(numballs):
создает цикл, который выполняется numball раз (10), добавляя новые объекты словаря к массиву balls и присваивая им начальные значения – левый верхний угол экрана и случайные смещения вниз-вправо. Числа 1, 2 в генераторе случайных чисел означают «любое число в промежутке от 1 до 2 (включительно)». Итак, мы получили 10 мячей, стартующих со случайными скоростями.
Затем мы настраиваем экран, как раньше, и добавляем строку event. set_grab(1), которая заключает курсор мыши внутри окна игры – было бы слишком просто, если бы курсор мыши мог сбежать за границы! Затем идет главный цикл игры. Как и ранее, мы заполняем экран черным, а затем в другом цикле for вбрасываем все мячи на экран.
После обновления экрана и задержки (чтобы игра шла с одинаковой скоростью на всех машинах), мы вновь проходимся по массиву мячей, обновляя их позиции при помощи переменных перемещения. Каждый мяч имеет свою собственную копию xmove и ymove в своем словаре, так что все они передвигаются независимо. Далее следует логика игры, определяющая, достигли ли мячи границ экрана. Здесь мы слегка подогнали значения так, чтобы мячи могли чуть-чуть заходить за край экрана (помните, их размер 32х32 пикселей). Это жизненно важно для игрового процесса, поскольку означает, что вам нельзя просто забиться курсором мыши в угол, где мячи вас не достанут! Мячи теперь достигают любой точки экрана, так что пошевеливайте мышью.
Последние три строки кода новые: screen.get_at() возвращает значение цвета пиксела в указанной позиции, то есть в положении курсора мыши, определяемого при помощи mouse.get_pos(). Мы говорим: «если цвет пикселя в точке нахождения курсора белый (255,255,255), то выполнить done = True», и главный цикл игры while закончится.
И наконец, мы выводим число секунд, в течение которых игрок смог выжить – time.get_ticks() возвращает его в миллисекундах, так что перед выводом мы делим его на 1000.
Отделка
Неплохо для 55 строк кода, не так ли? Как уже говорилось, вы можете усложнить игру, увеличив значение numballs в начале – стандартное значение 10 достаточно непросто, но если вы надеетесь на свое проворство, рискните установить 15 или 20, для уворачивания с буквально бешеной скоростью. Есть еще много аспектов игры, с которыми можно поэкспериментировать: например, изменить случайные числа в разделе основной логики программы (при ударе мяча о край экрана).
PyGame ломится от функций, готовых к экспериментам, и, используя несколько строк кода, вы можете добавить в игру звуковые эффекты или даже фоновую музыку. На http://www.pygame.org/docs/ имеется фантастически основательная документация, помогающая пользователям изучить функциональность библиотеки, включая подпрограммы, использованные на нашем уроке. Имея опыт программирования на бесчисленном множестве языков и в различных средах, от Amiga Blitz Basic до C#-SDL в Mono/.NET, я могу смело заявить, что PyGame – один из самых простых в мире наборов для программирования игр: это прекрасный способ воплотить любые идеи, возникшие в вашей голове. Удачи!