Мэтиз. Изучаем Python. Crash course2 n d e d i t i o na h a n d s o n, p r o j e c t b a s e d i n t r o d u c t i o n t o p r o g r a m m i n g
Скачать 6.2 Mb.
|
bullet.py def update(self): """Перемещает снаряд вверх по экрану.""" # Обновление позиции снаряда в вещественном формате. ❶ self.y -= self.settings.bullet_speed # Обновление позиции прямоугольника. 262 Глава 12 • Инопланетное вторжение ❷ self.rect.y = self.y def draw_bullet(self): """Вывод снаряда на экран.""" ❸ pygame.draw.rect(self.screen, self.color, self.rect) Метод update() управляет позицией снаряда. Когда происходит выстрел, снаряд двигается вверх по экрану, что соответствует уменьшению координаты y ; следова- тельно, для обновления позиции снаряда следует вычесть величину, хранящуюся в settings.bullet_speed , из self.y . Затем значение self.y используется для изменения значения self.rect.y . Атрибут bullet_speed позволяет увеличить скорость снарядов по ходу игры или при изменении ее поведения. Координата x снаряда после выстрела не изменяется, поэтому снаряд летит вертикально по прямой линии. Для вывода снаряда на экран вызывается функция draw_bullet() . Функция draw_ rect() заполняет часть экрана, определяемую прямоугольником снаряда, цветом из self.color . Группировка снарядов Класс Bullet и все необходимые настройки готовы; можно переходить к написанию кода, который будет выпускать снаряд каждый раз, когда игрок нажимает клавишу «пробел». Сначала мы создадим в AlienInvasion группу для хранения всех летя- щих снарядов, чтобы программа могла управлять их полетом. Эта группа будет представлена экземпляром класса pygame.sprite.Group — своего рода списком с расширенной функциональностью, которая может быть полезна при построении игр. Мы воспользуемся группой для прорисовки снарядов на экране при каждом проходе основного цикла и обновления текущей позиции каждого снаряда. Группа будет создаваться в __init__() : alien_invasion.py def __init__(self): self.ship = Ship(self) self.bullets = pygame.sprite.Group() Позиция снаряда будет обновляться при каждом проходе цикла while : alien_invasion.py def run_game(): """Запуск основного цикла игры.""" while True: self._check_events() self.ship.update() ❶ self.bullets.update() self._update_screen() Стрельба 263 Вызов update() для группы приводит к автоматическому вызову update() для каждого спрайта в группе. Строка self.bullets.update() вызывает bullet. update() для каждого снаряда, включенного в группу bullets Обработка выстрелов В классе AlienInvasion необходимо внести изменения в метод _check_keydown_ events() , чтобы при нажатии клавиши «пробел» происходил выстрел. Изменять _check_keyup_events() не нужно, потому что при отпускании клавиши ничего не происходит. Также необходимо изменить _update_screen() и вывести каждый снаряд на экран перед вызовом flip() При обработке выстрела придется выполнить довольно значительную работу, для которой мы напишем новый метод fire_bullet() : alien_invasion.py from ship import Ship ❶ from bullet import Bullet class AlienInvasion: def _check_keydown_events(self, event): elif event.key == pygame.K_q: sys.exit() ❷ elif event.key == pygame.K_SPACE: self._fire_bullet() def _check_keyup_events(self, event): def _fire_bullet(self): """Создание нового снаряда и включение его в группу bullets.""" ❸ new_bullet = Bullet(self) ❹ self.bullets.add(new_bullet) def _update_screen(self): """Обновляет изображения на экране и отображает новый экран.""" self.screen.fill(self.settings.bg_color) self.ship.blitme() ❺ for bullet in self.bullets.sprites(): bullet.draw_bullet() pygame.display.flip() Сначала импортируется класс Bullet . Затем при нажатии клавиши «пробел» вы- зывается _fire_bullet() . В коде _fire_bullet() мы создаем экземпляр Bullet , которому присваивается имя new_bullet . Он включается в группу bullets вызовом метода add() . Метод add() похож на append() , но этот метод написан специально для групп Pygame. 264 Глава 12 • Инопланетное вторжение Метод bullets.sprites() возвращает список всех спрайтов в группе bullets . Что- бы нарисовать все выпущенные снаряды на экране, программа перебирает спрайты в bullets и вызывает для каждого draw_bullet() . Если запустить alien_invasion .py сейчас, вы сможете двигать корабль влево и вправо и выпускать сколько угодно снарядов. Снаряды перемещаются вверх по экрану и исчезают при достижении верхнего края (рис. 12.3). Размер, цвет и скорость можно изменить при помощи настроек в settings .py Рис. 12.3. Экран игры после серии выстрелов Удаление старых снарядов На данный момент снаряды исчезают при достижении верхнего края, но только потому, что Pygame не может нарисовать их выше края экрана. На самом деле снаряды продолжают существовать; их координата y продолжает уменьшаться. И это создает проблему, потому что снаряды продолжают потреблять память и вы- числительные мощности. От старых снарядов необходимо избавиться, иначе игра замедлится из-за большого объема лишней работы. Для этого необходимо определить момент, когда атрибут bottom прямоугольника снаряда достигнет 0 — это означает, что снаряд вышел за верхний край экрана: Стрельба 265 alien_invasion.py def run_game(self): # Запуск основного цикла игры. while True: self._check_events() self.ship.update() self.bullets.update() # Удаление снарядов, вышедших за край экрана. ❶ for bullet in self.bullets.copy(): ❷ if bullet.rect.bottom <= 0: ❸ self.bullets.remove(bullet) ❹ print(len(self.bullets)) self._update_screen() При использовании цикла for со списком (или группой в Pygame) Python ожидает, что список сохраняет постоянную длину во время выполнения цикла. Так как эле- менты из списка или группы в цикле for не должны удаляться, перебирать нужно копию группы. Метод copy() используется для создания цикла for , в котором возможно изменять содержимое bullets . Программа проверяет каждый снаряд и определяет, вышел ли он за верхний край экрана . Если снаряд пересек гра- ницу, он удаляется из bullets . В точке добавляется команда print , которая сообщает, сколько снарядов сейчас существует в игре; по выведенному значению можно убедиться в том, что снаряды действительно удаляются при достижении верхнего края экрана. Если код работает правильно, вы можете понаблюдать за выводом на терминале и убедиться в том, что количество снарядов уменьшается до 0 после того, как очередной залп уходит за верхний край экрана. После запуска игры, когда вы убедитесь в том, что снаряды правильно удаляются из группы, удалите команду print . Если команда останется в программе, она существенно замедлит игру, по- тому что вывод на терминал занимает больше времени, чем отображение графики в игровом окне. Ограничение количества снарядов Многие игры-стрелялки ограничивают количество снарядов, одновременно нахо- дящихся на экране, чтобы у игроков появился стимул стрелять более метко. То же самое будет сделано и в игре Alien Invasion. Сначала сохраним максимально допустимое количество снарядов в settings .py : settings.py # Параметры снаряда self.bullet_color = (60, 60, 60) self.bullets_allowed = 3 266 Глава 12 • Инопланетное вторжение В любой момент времени на экране может находиться не более трех снарядов. Эта настройка будет использоваться в AlienInvasion для проверки количества суще- ствующих снарядов перед созданием нового снаряда в _fire_bullet() : alien_invasion.py def _fire_bullet(self): """Создание нового снаряда и включение его в группу bullets.""" if len(self.bullets) < self.settings.bullets_allowed: new_bullet = Bullet(self) self.bullets.add(new_bullet). При нажатии клавиши «пробел» программа проверяет длину bullets . Если значе- ние len(self.bullets) меньше трех, создается новый снаряд. Но если на экране уже находятся три активных снаряда, при нажатии пробела ничего не происходит. Если вы запустите игру сейчас, вы сможете выпускать снаряды только группами по три. Создание метода _update_bullets() Мы хотим, чтобы класс AlienInvasion был как можно более простым, поэтому по- сле написания и проверки кода управления снарядами этот код можно переместить в отдельный метод. Мы создадим новый метод _update_bullets() и добавим его непосредственно перед _update_screen() : alien_invasion.py def _update_bullets(self): """Обновляет позиции снарядов и уничтожает старые снаряды.""" # Обновление позиций снарядов. self.bullets.update() # Удаление снарядов, вышедших за край экрана. for bullet in self.bullets.copy(): if bullet.rect.bottom <= 0: self.bullets.remove(bullet) Код _update_bullets() вырезается и вставляется из run_game() ; мы всего лишь немного уточнили комментарии. Цикл while в run_game() снова выглядит просто: alien_invasion.py while True: self._check_events() self.ship.update() self._update_bullets() self._update_screen() В результате преобразования основной цикл содержит минимум кода, чтобы можно было легко прочитать имена функций и понять, что происходит в игре. Основной цикл проверяет ввод, полученный от игрока, а затем обновляет позицию корабля Итоги 267 и всех выпущенных снарядов. Затем обновленные позиции игровых элементов используются для вывода нового экрана в точке. Снова запустите alien_invasion .py и убедитесь в том, что стрельба происходит без ошибок. УПРАЖНЕНИЯ 12.6. Боковая стрельба: напишите игру, в которой корабль размещается у левого края экрана, а игрок может перемещать корабль вверх и вниз. При нажатии клавиши «пробел» корабль стреляет и снаряд двигается вправо по экрану. Проследите за тем, чтобы снаряды удалялись при выходе за край экрана. Итоги В этой главе вы научились планировать ход игры, а также усвоили базовую струк- туру игры, написанной с использованием Pygame. Вы узнали, как задать цвет фона и как сохранить настройки в отдельном классе, чтобы они были доступны для всех частей игры. Вы научились выводить изображения на экран и управлять перемеще- нием игровых элементов. Также вы узнали, как создавать элементы, двигающиеся самостоятельно (например, снаряды, летящие по экрану), и как удалять объекты, которые стали лишними. Также в этой главе рассматривалась методика регуляр- ного рефакторинга кода для упрощения текущей разработки. В главе 13 в игру Alien Invasion будут добавлены пришельцы. К концу главы 13 игрок сможет сбивать корабли пришельцев — конечно, если они не доберутся до него первыми! 13 Осторожно, пришельцы! В этой главе в игру Alien Invasion будут добавлены пришельцы. Сначала мы до- бавим одного пришельца у верхнего края экрана, а потом сгенерируем целый флот. Пришельцы будут перемещаться в сторону и вниз; при этом пришельцы, в которых попадают снаряды, исчезают с экрана. Наконец, мы ограничим количество кора- блей у игрока, так что при гибели последнего корабля игра завершается. В этой главе вы узнаете больше о Pygame и о ведении крупного проекта. Вы также научитесь обнаруживать коллизии (столкновения) игровых объектов, на- пример снарядов и пришельцев. Обнаружение коллизий помогает определять взаимодействия между элементами игры: например, ограничить перемещение персонажа областью между стенами лабиринта или организовать передачу мяча между двумя персонажами. Работа будет продолжаться на основе плана, к кото- рому мы будем время от времени возвращаться, чтобы не отклоняться от цели во время написания кода. Прежде чем браться за новый код для добавления флота пришельцев на экран, рассмотрим проект и обновим план. Анализ проекта Приступая к новой фазе разработки крупного проекта, всегда полезно вернуться к исходному плану и уточнить, чего же вы хотите добиться в том коде, который собираетесь написать. В этой главе мы: проанализируем код и определим, нужно ли провести рефакторинг перед реа- лизацией новых возможностей; добавим в левом верхнем углу экрана одного пришельца, отделив его от краев экрана интервалами; по величине интервалов вокруг первого пришельца и общим размерам экрана вычислим, сколько пришельцев поместится на экране. Для создания пришель- цев, заполняющих верхнюю часть экрана, будет написан цикл; организуем перемещение флота пришельцев в сторону и вниз, пока весь флот не будет уничтожен, или пока пришелец не столкнется с кораблем игрока, или Создание пришельца 269 пока пришелец не достигнет земли. Если весь флот будет уничтожен, програм- ма создает новый флот. Если пришелец сталкивается с кораблем или с землей, программа уничтожает корабль и создает новый флот; ограничим количество кораблей, которые могут использоваться игроком, и за- вершаем игру в конце последней попытки. Этот план будет уточняться по мере реализации новых возможностей, но для на- чала и этого достаточно. Также проводите анализ кода, когда вы начинаете работу над новой серией воз- можностей проекта. Так как с каждой новой фазой проект обычно становится более сложным, лучше всего заняться расчисткой излишне громоздкого или не- эффективного кода. Ранее мы уже проводили рефакторинг, так что сейчас особой расчистки не потребуется. Создание пришельца Размещение одного пришельца на экране мало чем отличается от размещения корабля. Поведением каждого пришельца будет управлять класс с именем Alien , который по своей структуре очень похож на класс Ship . Для простоты мы снова воспользуемся готовыми графическими изображениями. Вы можете найти соб- ственное изображение пришельца или использовать изображение на рис. 13.1, доступное в ресурсах книги по адресу https://www .nostarch .com/pythoncrashcourse2e/ Это изображение имеет серый фон, совпадающий с цветом фона экрана. Не забудь- те сохранить выбранный файл в каталоге images Рис. 13.1. Пришелец, который будет использоваться для создания флота Создание класса Alien Теперь можно написать класс Alien и сохранить его в файле alien .py : 270 Глава 13 • Осторожно, пришельцы! alien.py import pygame from pygame.sprite import Sprite class Alien(Sprite): """Класс, представляющий одного пришельца.""" def __init__(self, ai_game): """Инициализирует пришельца и задает его начальную позицию.""" super().__init__() self.screen = ai_game.screen # Загрузка изображения пришельца и назначение атрибута 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) В основном этот класс похож на класс Ship (если не считать размещения при- шельца). Изначально каждый пришелец размещается в левом верхнем углу экрана, при этом слева от него добавляется интервал, равный ширине пришельца, а над ним — интервал, равный высоте . Нас в первую очередь интересует горизонталь- ная скорость пришельца, поэтому горизонтальная позиция каждого пришельца отслеживается точно . Классу Alien не нужен метод для вывода на экран; вместо этого мы воспользуемся методом групп Pygame, который автоматически рисует все элементы группы на экране. Создание экземпляра Alien Начнем с создания экземпляра Alien , чтобы первый пришелец появился на экране. Так как эта операция входит в подготовительную часть, код для текущего экземп- ляра будет добавлен в конец метода __init__() в AlienInvasion . Позднее будет создан целый флот вторжения, что потребует определенной работы, поэтому мы определим новый вспомогательный метод с именем _create_fleet() Порядок следования методов в классе может быть любым — важно лишь, чтобы в этом порядке существовала некая закономерность. Я размещу _create_fleet() не- посредственно перед методом _update_screen() , но с таким же успехом его можно разместить в любой точке AlienInvasion . Начнем с импортирования класса Alien Обновленные команды импортирования в файле alien_invasion .py : Создание пришельца 271 alien_invasion.py from bullet import Bullet from alien import Alien Обновленный метод __init__() : alien_invasion.py def __init__(self): self.ship = Ship(self) self.bullets = pygame.sprite.Group() self.aliens = pygame.sprite.Group() self._create_fleet() Создадим группу для хранения флота вторжения и вызовем метод _create_fleet() , который будет вскоре написан. Новый метод _create_fleet() выглядит так: alien_invasion.py def _create_fleet(self): """Создание флота вторжения.""" # Создание пришельца. alien = Alien(self) self.aliens.add(alien) В этом методе создается один экземпляр Alien , который затем добавляется в груп- пу для хранения флота. По умолчанию объект размещается в левом верхнем углу экрана — эта позиция прекрасно подходит для первого пришельца. Чтобы пришелец появился на экране, программа вызывает метод draw() группы в _update_screen() : alien_invasion.py def _update_screen(self): for bullet in self.bullets.sprites(): bullet.draw_bullet() self.aliens.draw(self.screen) pygame.display.flip() При вызове draw() для группы Pygame выводит каждый элемент группы в пози- ции, определяемой его атрибутом rect . Метод draw() получает один аргумент — поверхность для вывода элементов группы. На рис. 13.2 изображен первый пришелец. 272 Глава 13 • Осторожно, пришельцы! Рис. 13.2. Появился первый пришелец После того как первый пришелец появится на экране, мы напишем код для вывода всего флота. Построение флота Чтобы нарисовать флот пришельцев, необходимо вычислить, сколько пришельцев поместится в одном ряду и сколько рядов поместится по высоте. Сначала мы вы- числим горизонтальные интервалы между пришельцами и создадим ряд; затем будет вычислен вертикальный интервал и создан весь флот. Вычисление количества пришельцев в одном ряду Чтобы определить, сколько пришельцев помещается в одном ряду, сначала вычис- лим доступное горизонтальное пространство. Ширина экрана хранится в settings. screen_width , но с обеих сторон экрана необходимо зарезервировать пустые ин- тервалы. Определим их равными ширине одного пришельца. Так как ширина уменьшается на величину двух интервалов, доступное пространство равно ширине экрана за вычетом удвоенной ширины пришельца: available_space_x = settings.screen_width — (2 * alien_width) Также необходимо зарезервировать интервалы между пришельцами; они будут со- ставлять одну ширину пришельца. Пространство, необходимое для вывода одного |