Книга Изучаем Python
Скачать 4.68 Mb.
|
254 Глава 12 • Стреляющий корабль game_functions.py def check_keydown_events(event, ai_settings, screen, ship, bullets): elif event.key == pygame.K_SPACE: # Создание новой пули и включение ее в группу bullets. if len(bullets) < ai_settings.bullets_allowed: new_bullet = Bullet(ai_settings, screen, ship) bullets.add(new_bullet) При нажатии клавиши «пробел» программа проверяет длину bullets . Если значение len(bullets) меньше трех, создается новая пуля. Но, если на экране уже находятся три активные пули, при нажатии пробела ничего не происходит. Если вы запустите игру сейчас, вы сможете выпускать пули только группами по три. Создание функции update_bullets() Мы хотим, чтобы главный файл программы alien_invasion .py был как можно более простым, поэтому после написания и проверки кода управления пулями этот код можно переместить в модуль game_functions . Мы создадим новую функцию update_bullets() и добавим ее в конец game_functions .py : game_functions.py def update_bullets(bullets): """Обновляет позиции пуль и уничтожает старые пули.""" # Обновление позиций пуль. bullets.update() # Удаление пуль, вышедших за край экрана. for bullet in bullets.copy(): if bullet.rect.bottom <= 0: bullets.remove(bullet) Код update_bullets() вырезается и вставляется из alien_invasion .py ; единственным необходимым параметром функции является группа bullets Цикл while в alien_invasion .py снова выглядит просто: alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() gf.update_bullets(bullets) gf.update_screen(ai_settings, screen, ship, bullets) В результате преобразования основной цикл содержит минимум кода, чтобы можно было легко прочитать имена функций и понять, что происходит в игре. Основной цикл проверяет ввод, полученный от игрока , а затем обновляет пози- цию корабля и всех выпущенных пуль . Затем обновленные позиции игровых элементов используются для вывода нового экрана в точке . Итоги 255 Создание функции fire_bullet() Переместим код стрельбы в отдельную функцию, чтобы выстрел выполнялся всего одной строкой кода, а блок elif в check_keydown_events() оставался простым: game_functions.py def check_keydown_events(event, ai_settings, screen, ship, bullets): """Реагирует на нажатия клавиш.""" elif event.key == pygame.K_SPACE: fire_bullet(ai_settings, screen, ship, bullets) def fire_bullet(ai_settings, screen, ship, bullets): """Выпускает пулю, если максимум еще не достигнут.""" # Создание новой пули и включение ее в группу bullets. if len(bullets) < ai_settings.bullets_allowed: new_bullet = Bullet(ai_settings, screen, ship) bullets.add(new_bullet) Функция fire_bullet() просто содержит код, который использовался для выстрела при нажатии клавиши «пробел»; вызов fire_bullet() добавляется в check_keydown_ events() при нажатии клавиши «пробел». Запустите alien_invasion .py еще раз и убедитесь в том, что стрельба проходит без ошибок. УПРАЖНЕНИЯ 12-5 . Боковая стрельба: напишите игру, в которой корабль размещается у левого края экрана, а игрок может перемещать корабль вверх и вниз . При нажатии клавиши «пробел» корабль стреляет, и пуля двигается вправо по экрану . Проследите за тем, чтобы пули уда- лялись при выходе за край экрана . Итоги В этой главе вы научились составлять план игры, а также усвоили базовую струк- туру игры, написанной с использованием Pygame. Вы узнали, как задать цвет фона и как сохранить настройки в отдельном классе, чтобы они были доступны для всех частей игры. Вы научились выводить изображения на экран и управлять перемеще- нием игровых элементов. Также вы узнали, как создавать элементы, двигающиеся самостоятельно (например, пули, летящие по экрану), и как удалять объекты, ко- торые стали лишними. Также в этой главе рассматривалась методика регулярного рефакторинга кода для упрощения текущей разработки. В главе 13 в игру Alien Invasion будут добавлены пришельцы. К концу главы 13 игрок сможет сбивать корабли пришельцев — конечно, если они не доберутся до него первыми! 13 Осторожно, пришельцы! В этой главе в игру Alien Invasion будут добавлены пришельцы. Сначала мы до- бавим одного пришельца у верхнего края экрана, а потом сгенерируем целый флот. Пришельцы будут перемещаться в сторону и вниз; при этом пришельцы, в которых попадают пули, исчезают с экрана. Наконец, мы ограничим количе- ство кораблей у игрока, так что при гибели последнего корабля игра завершается. В этой главе вы узнаете больше о Pygame и о ведении крупного проекта. Вы также научитесь обнаруживать коллизии (столкновения) игровых объектов, например пуль и пришельцев. Обнаружение коллизий помогает определять взаимодействия между элементами игры: например, ограничить перемещение персонажа областью между стенами лабиринта или организовать передачу мяча между двумя персонажами. Работа будет продолжаться на основе плана, к кото- рому мы будем время от времени возвращаться, чтобы не отклоняться от цели во время написания кода. Итак, прежде чем браться за новый код для добавления флота пришельцев на экран, рассмотрим проект и обновим план. Анализ проекта Приступая к новой фазе разработки крупного проекта, всегда полезно вернуться к исходному плану и уточнить, чего же вы хотите добиться в том коде, который собираетесь написать. В этой главе мы: Проанализируем код и определим, нужно ли провести рефакторинг перед реа- лизацией новых возможностей. Добавим в левом верхнем углу экрана одного пришельца, отделив его от краев экрана интервалами. По величине интервалов вокруг первого пришельца и общим размерам экрана вычислим, сколько пришельцев поместится на экране. Для создания пришель- цев, заполняющих верхнюю часть экрана, будет написан цикл. Организуем перемещение флота пришельцев в сторону и вниз, пока весь флот не будет уничтожен, пока пришелец не столкнется с кораблем игрока или пока пришелец не достигнет земли. Если весь флот будет уничтожен, программа создает новый флот. Если пришелец сталкивается с кораблем или с землей, программа уничтожает корабль и создает новый флот. Создание пришельца 257 Ограничим количество кораблей, которые могут использоваться игроком, и за- вершим игру в конце последней попытки. Этот план будет уточняться по мере реализации новых возможностей, но для на- чала и этого достаточно. Также проводите анализ кода, когда вы начинаете работу над новой серией воз- можностей проекта. Так как с каждой новой фазой проект обычно становится более сложным, лучше всего заняться расчисткой излишне громоздкого или не- эффективного кода. И хотя сейчас особой расчистки не потребуется, потому что мы уже проводили промежуточный рефакторинг, необходимость использовать мышь для закрытия игры каждый раз, когда потребуется протестировать новую функцию, раздражает. Добавим возможность быстрого завершения игры при на- жатии клавиши Q: game_functions.py def check_keydown_events(event, ai_settings, screen, ship, bullets): elif event.key == pygame.K_q: sys.exit() В check_keydown_events() добавляется новый блок, который завершает игру при нажатии клавиши Q. Это довольно безопасное изменение, потому что клавиша Q находится достаточно далеко от клавиш со стрелками и пробела, так что вероят- ность случайного нажатия Q и завершения игры невелика. Теперь при тестирова- нии игру можно закрыть клавишей Q, не прибегая к использованию мыши. Создание пришельца Размещение одного пришельца на экране мало чем отличается от размещения корабля. Поведением каждого пришельца будет управлять класс с именем Alien , который по своей структуре очень похож на класс Ship . Для простоты мы сно- ва воспользуемся готовыми графическими изображениями. Вы можете найти Рис. 13.1. Пришелец, который будет использоваться для создания флота 258 Глава 13 • Осторожно, пришельцы! собственное изображение пришельца или использовать изображение на рис. 13.1, доступное в ресурсах книги по адресу https://www.nostarch.com/pythoncrashcourse/. Это изображение имеет серый фон, совпадающий с цветом фона экрана. Не за- будьте сохранить выбранный файл в каталоге images Создание класса Alien Теперь можно написать класс Alien : alien.py import pygame from pygame.sprite import Sprite class Alien(Sprite): """Класс, представляющий одного пришельца.""" def __init__(self, ai_settings, screen): """Инициализирует пришельца и задает его начальную позицию.""" super(Alien, self).__init__() self.screen = screen self.ai_settings = ai_settings # Загрузка изображения пришельца и назначение атрибута rect. self.image = pygame.image.load('images/alien.bmp') self.rect = self.image.get_rect() # Каждый новый пришелец появляется в левом верхнем углу экрана. self.rect.x = self.rect.width self.rect.y = self.rect.height # Сохранение точной позиции пришельца. self.x = float(self.rect.x) def blitme(self): """Выводит пришельца в текущем положении.""" self.screen.blit(self.image, self.rect) В основном этот класс похож на класс Ship (если не считать размещения пришель- ца). Изначально каждый пришелец размещается в левом верхнем углу экрана, при этом слева от него добавляется интервал, равный ширине пришельца, а над ним — интервал, равный высоте . Создание экземпляра Alien Создадим экземпляр Alien в alien_invasion .py : alien_invasion.py from ship import Ship from alien import Alien import game_functions as gf Создание пришельца 259 def run_game(): # Создание пришельца. alien = Alien(ai_settings, screen) # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() gf.update_bullets(bullets) gf.update_screen(ai_settings, screen, ship, alien, bullets) run_game() Программа импортирует новый класс Alien и создает экземпляр Alien непосред- ственно перед входом в основной цикл while . Так как позиция пришельца еще не успела измениться, ничего нового в цикле не добавляется; изменения вносятся только в вызов update_screen() , которому передается экземпляр alien Отображение пришельца на экране Рис. 13.2. Появился первый пришелец Чтобы пришелец появился на экране, программа вызывает его метод blitme() в update_screen() : game_functions.py def update_screen(ai_settings, screen, ship, alien, bullets): 260 Глава 13 • Осторожно, пришельцы! # Все пули выводятся позади изображений корабля и пришельцев. for bullet in bullets: bullet.draw_bullet() ship.blitme() alien.blitme() # Отображение последнего прорисованного экрана. pygame.display.flip() Пришелец выводится после прорисовки корабля и пуль, так что пришельцы будут находиться на верхнем «слое» экрана. На рис. 13.2 изображен первый пришелец. После того как первый пришелец появится на экране, мы напишем код для вывода всего флота. Построение флота Чтобы нарисовать флот пришельцев, необходимо вычислить, сколько пришельцев поместится в одном ряду и сколько рядов поместится по высоте. Сначала мы вы- числим горизонтальные интервалы между пришельцами и создадим ряд; затем будет вычислен вертикальный интервал и создан весь флот. Вычисление количества пришельцев в одном ряду Чтобы определить, сколько пришельцев помещается в одном ряду, сначала вы- числим доступное горизонтальное пространство. Ширина экрана хранится в ai_ settings.screen_width , но с обеих сторон экрана необходимо зарезервировать пустые интервалы. Определим их равными ширине одного пришельца. Так как ширина уменьшается на величину двух интервалов, доступное пространство равно ширине экрана за вычетом удвоенной ширины пришельца: available_space_x = ai_settings.screen_width — (2 * alien_width) Также необходимо зарезервировать интервалы между пришельцами; они будут со- ставлять одну ширину пришельца. Пространство, необходимое для вывода одного пришельца, равно его удвоенной ширине: одна ширина для самого пришельца и еще одна для пустого интервала справа. Чтобы определить количество пришельцев на экране, разделим доступное пространство на удвоенную ширину пришельца: number_aliens_x = available_space_x / (2 * alien_width) Эти вычисления будут включены в программу при создании флота. ПРИМЕЧАНИЕ У вычислений в программировании есть одна замечательная особенность: не обязательно быть полностью уверенным в правильности формулы, когда вы ее пишете . Вы можете опробовать формулу на практике и посмотреть, что из этого получится . В худшем случае получится экран, до отказа забитый пришельцами, — или наоборот, пустой . В этом случае вы пересмотрите формулу на основании полученных результатов . Построение флота 261 Создание ряда Чтобы создать один ряд пришельцев, сначала создадим в alien_invasion .py пустую группу с именем aliens для хранения всех пришельцев, а затем вызовем функцию в game_functions .py для создания флота: alien_invasion.py import pygame from pygame.sprite import Group from settings import Settings from ship import Ship import game_functions as gf def run_game(): # Создание корабля, группы пуль и группы пришельцев. ship = Ship(ai_settings, screen) bullets = Group() aliens = Group() # Создание флота пришельцев. gf.create_fleet(ai_settings, screen, aliens) # Запуск основного цикла игры. while True: gf.update_screen(ai_settings, screen, ship, aliens, bullets) run_game() Так как пришельцы уже не создаются напрямую в alien_invasion .py , импортировать класс Alien в этот файл не обязательно. Создайте пустую группу для хранения всех пришельцев в игре . Затем создайте новую функцию create_fleet() , которую мы вскоре вызовем, и передайте ей ai_settings , объект screen и пустую группу aliens . Затем измените вызов update_ screen() , чтобы предоставить функции доступ к группе пришельцев . Также необходимо внести изменения в update_screen() : game_functions.py def update_screen(ai_settings, screen, ship, aliens, bullets): ship.blitme() aliens.draw(screen) # Отображение последнего прорисованного экрана. pygame.display.flip() Когда вы вызываете метод draw() для группы, Pygame автоматически выводит каждый элемент группы в позиции, определяемой его атрибутом rect . В дан- ном случае вызов aliens.draw(screen) рисует каждого пришельца в группе на экране. 262 Глава 13 • Осторожно, пришельцы! Создание флота Теперь можно перейти к созданию флота. Ниже приведена новая функция create_ fleet(), которую мы поместим в конец game_functions .py . Также необходимо импортировать класс Alien , не забудьте добавить команду import в начало файла: game_functions.py from bullet import Bullet from alien import Alien def create_fleet(ai_settings, screen, aliens): """Создает флот пришельцев.""" # Создание пришельца и вычисление количества пришельцев в ряду. # Интервал между соседними пришельцами равен одной ширине пришельца. alien = Alien(ai_settings, screen) alien_width = alien.rect.width available_space_x = ai_settings.screen_width - 2 * alien_width number_aliens_x = int(available_space_x / (2 * alien_width)) # Создание первого ряда пришельцев. for alien_number in range(number_aliens_x): # Создание пришельца и размещение его в ряду. alien = Alien(ai_settings, screen) alien.x = alien_width + 2 * alien_width * alien_number alien.rect.x = alien.x aliens.add(alien) Бульшая часть этого кода уже была описана ранее. Для размещения пришельцев необходимо знать ширину и высоту одного пришельца, и мы создаем его в точке перед выполнением вычислений. Этот пришелец не войдет во флот, поэтому он не включается в группу aliens . В точке ширина пришельца определяется по его атрибуту rect , а полученное значение сохраняется в alien_width , чтобы избежать лишних обращений к атрибуту rect . В точке вычисляется горизонтальное про- странство и количество пришельцев, которые в нем поместятся. По сравнению с исходными формулами всего одно изменение: мы используем int() , чтобы вычисленное количество пришельцев было целым, — во-первых, неясно, что делать с неполным пришельцем, а во-вторых, функция range() должна получать целое число. Функция int() отсекает дробную часть числа, фактически выполняя округление в меньшую сторону. (И это правильно: лучше оставить лиш- нее свободное место в каждом ряду, чем забивать ряды до отказа.) Затем создается цикл от 0 до количества создаваемых пришельцев . В теле цикла создается новый пришелец, после чего задается его координата x для размещения его в ряду . Каждый пришелец сдвигается вправо на одну ширину от левого поля. Затем ширина пришельца умножается на 2, чтобы учесть полное пространство, выделенное для одного пришельца, включая пустой интервал справа, а получен- ная величина умножается на позицию пришельца в ряду. Затем новый пришелец добавляется в группу aliens Построение флота 263 Рис. 13.3. Первый ряд пришельцев Запустив программу Alien Invasion, вы увидите, что на экране появился первый ряд пришельцев (рис. 13.3). Первый ряд сдвинут влево, и это хорошо, потому что флот пришельцев должен дви- гаться вправо, пока не дойдет до края экрана, затем немного опуститься вниз, затем двигаться влево и т. д. Как и в классической игре Space Invaders, такое перемещение интереснее, чем постепенное снижение по прямой. Движение будет продолжаться до тех пор, пока все пришельцы не будут сбиты или пока пришелец не столкнется с кораблем или нижним краем экрана. ПРИМЕЧАНИЕ В зависимости от выбранной ширины экрана расположение первого ряда пришельцев в вашей системе может выглядеть немного иначе . Рефакторинг create_fleet() Если бы создание флота на этом было завершено, то функцию create_fleet() , пожалуй, можно было бы оставить в таком виде, но работа еще не закончена, по- этому мы немного подчистим код функции. Ниже приведена версия create_fleet() с двумя новыми функциями: get_number_aliens_x() и create_alien() : game_functions.py def get_number_aliens_x(ai_settings, alien_width): """Вычисляет количество пришельцев в ряду.""" available_space_x = ai_settings.screen_width - 2 * alien_width number_aliens_x = int(available_space_x / (2 * alien_width)) return number_aliens_x |