Мэтиз. Изучаем 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.
|
282 Глава 13 • Осторожно, пришельцы! Метод sprite.groupcollide() сравнивает прямоугольник rect каждого элемента с прямоугольником rect каждого элемента другой группы. В данном случае он сравнивает прямоугольник каждого снаряда с прямоугольником каждого при- шельца и возвращает словарь со снарядами и пришельцами, между которыми обнаружены коллизии. Каждый ключ в словаре представляет снаряд, а ассоцииро- ванное с ним значение — пришельца, в которого попал снаряд. (Этот словарь будет использоваться в реализации системы подсчета очков в главе 14.) Для проверки коллизий в конец функции update_bullets() добавляется следую- щий код: alien_invasion.py def _update_bullets(self): """Обновляет позиции снарядов и удаляет старые пули.""" # Проверка попаданий в пришельцев. # При обнаружении попадания удалить снаряд и пришельца. collisions = pygame.sprite.groupcollide( self.bullets, self.aliens, True, True) Рис. 13.5. Снаряды уничтожают пришельцев! Новая строка сначала перебирает все снаряды в self.bullets , а затем перебирает всех пришельцев в self.aliens . Каждый раз, когда между прямоугольником снаряда и пришельца обнаруживается перекрытие, groupcollide() добавляет пару «ключ- значение» в возвращаемый словарь. Два аргумента True сообщают Pygame, нужно ли Уничтожение пришельцев 283 удалять столкнувшиеся объекты: снаряд и пришельца. (Чтобы создать сверхмощный снаряд, который будет уничтожать всех пришельцев на своем пути, можно передать в первом аргументе False , а во втором True . Пришельцы, в которых попадает снаряд, будут исчезать, но все снаряды будут оставаться активными до верхнего края экрана.) Если запустить Alien Invasion сейчас, пришельцы, в которых попадает снаряд, будут исчезать с экрана. На рис. 13.5 изображен частично уничтоженный флот. Создание больших снарядов для тестирования Многие игровые возможности тестируются простым запуском игры, но некоторые аспекты слишком утомительно тестировать в обычной версии игры. Например, что- бы проверить, правильно ли обрабатывается уничтожение последнего пришельца, нам пришлось бы несколько раз сбивать всех пришельцев на экране. Для тестирования конкретных аспектов игры можно изменить игровые настройки так, чтобы упростить конкретную область. Например, можно уменьшить экран, чтобы на нем было меньше пришельцев, или увеличить скорость снаряда и коли- чество снарядов, одновременно находящихся на экране. Мое любимое изменение при тестировании Alien Invasion — использование сверх- широких снарядов, которые остаются активными даже после попадания в при- шельца (рис. 13.6). Попробуйте задать настройке bullet_width значение 300 (или даже 3000!) и посмотрите, сколько времени вам понадобится для уничтожения флота пришельцев! Рис. 13.6. Сверхмощные снаряды упрощают тестирование некоторых аспектов игры 284 Глава 13 • Осторожно, пришельцы! Такие изменения повышают эффективность тестирования, а заодно могут подска- зать идеи для всевозможных игровых бонусов. (Только не забудьте восстановить нормальное состояние настроек после завершения тестирования.) Восстановление флота Одна из ключевых особенностей Alien Invasion — бесконечные орды пришельцев: каждый раз, когда вы уничтожаете один флот, на его месте появляется другой. Чтобы после уничтожения одного флота появлялся другой, сначала нужно убе- диться в том, что группа aliens пуста. Если она пуста, вызывается метод _create_ fleet() . Проверка будет выполняться в конце _update_bullets() , потому что именно здесь уничтожаются отдельные пришельцы: alien_invasion.py def _update_bullets(self): ❶ if not self.aliens: # Уничтожение существующих снарядов и создание нового флота. ❷ self.bullets.empty() self._create_fleet() В точке программа проверяет, пуста ли группа aliens . Пустая группа интер- претируется как False ; это самый простой способ проверить группу на наличие элементов. Если группа пуста, то все существующие снаряды убираются методом empty() , который удаляет все существующие спрайты из группы . Вызов метода _create_fleet() снова заполняет экран пришельцами. Теперь сразу же после уничтожения текущего флота на экране появляется новый флот. Ускорение снарядов Попытавшись стрелять по пришельцам в текущем состоянии игры, можно заме- тить, что скорость движения снарядов не оптимальна — в вашей системе она может быть слишком высокой или слишком низкой. На этой стадии можно изменить настройки, чтобы игра была более интересной и приятной. Скорость снарядов можно увеличить настройкой bullet_speed в settings .py . Напри- мер, если задать в моей системе значение 1.5, снаряды будут двигаться по экрану немного быстрее: settings.py # Настройки снарядов self.bullet_speed = 1.5 self.bullet_width = 3 Завершение игры 285 Оптимальное значение этой настройки зависит от производительности вашей системы. Найдите значение, которое лучше подходит для вашей конкретной кон- фигурации. Рефакторинг _update_bullets() Переработаем метод _ update_bullets() , чтобы он не решал такое количество раз- ных задач. Код обработки коллизий будет выделен в отдельный метод: alien_invasion.py def _update_bullets(self): # Уничтожение исчезнувших снарядов. for bullet in self.bullets.copy(): if bullet.rect.bottom <= 0: self.bullets.remove(bullet) self._check_bullet_alien_collisions() def _check_bullet_alien_collisions(self): """Обработка коллизий снарядов с пришельцами.""" # Удаление снарядов и пришельцев, участвующих в коллизиях. collisions = pygame.sprite.groupcollide( self.bullets, self.aliens, True, True) if not self.aliens: # Уничтожение существующих снарядов и создание нового флота. self.bullets.empty() self._create_fleet() Мы создали новый метод check_bullet_alien_collisions() для выявления колли- зий между снарядами и пришельцами и для реакции на уничтожение всего флота. Это сделано для того, чтобы сократить длину _update_bullets() и упростить дальнейшую разработку. УПРАЖНЕНИЯ 13.5. Боковая стрельба-2: после упражнения 12.6 «Боковая стрельба» программа была серьезно доработана. В этом упражнении вам предлагается усовершенствовать «Боковую стрельбу» до того же состояния, к которому была приведена игра Alien Invasion. Добавьте флот пришельцев и заставьте их перемещаться по горизонтали по направлению к кораблю. Другой вариант: напишите код, который размещает пришельцев в случайных позициях у правого края экрана, а затем заставляет их двигаться к кораблю. Также напишите код, который заставляет пришельцев исчезать при попадании в них. Завершение игры Какое удовольствие от игры, в которой невозможно проиграть? Если игрок не успеет сбить флот достаточно быстро, пришельцы уничтожат корабль при столк- 286 Глава 13 • Осторожно, пришельцы! новении. При этом количество кораблей, используемых игроком, ограниченно, и корабль уничтожается, когда пришелец достигает нижнего края экрана. Игра завершается в тот момент, когда у игрока кончатся все корабли. Обнаружение коллизий с кораблем Начнем с проверки коллизий между пришельцами и кораблем, чтобы мы могли правильно обработать столкновения с пришельцами. Коллизии «пришелец — ко- рабль» проверяются немедленно после обновления позиции каждого пришельца в AlienInvasion : alien_invasion.py def _update_aliens(self): self.aliens.update() # Проверка коллизий "пришелец — корабль". ❶ if pygame.sprite.spritecollideany(self.ship, self.aliens): ❷ print("Ship hit!!!") Функция spritecollideany() получает два аргумента: спрайт и группу. Функция пытается найти любой элемент группы, вступивший в коллизию со спрайтом, и останавливает цикл по группе сразу же после обнаружения столкнувшегося элемента. В данном случае он перебирает группу aliens и возвращает первого пришельца, столкнувшегося с кораблем ship Если ни одна коллизия не обнаружена, spritecollideany() возвращает None , и блок if в точке не выполняется. Если же будет обнаружен пришелец, столк- нувшийся с кораблем, метод возвращает этого пришельца и выполняется блок if : выводится сообщение Ship hit!!! . При столкновении пришельца с кораблем необходимо выполнить ряд операций: удалить всех оставшихся пришельцев и сна- ряды, вернуть корабль в центр и создать новый флот. Прежде чем писать код всех этих операций, необходимо убедиться в том, что решение с обнаружением коллизий с кораблем работает правильно. Вызов print() всего лишь позволяет легко про- верить правильность обнаружения коллизий. Если вы запустите Alien Invasion, при столкновении пришельца с кораблем в тер- минальном окне появляется сообщение Ship hit!!! . В ходе тестирования этого аспекта присвойте alien_drop_speed более высокое значение (например, 50 или 100), чтобы пришельцы быстрее добирались до вашего корабля. Обработка столкновений с кораблем Теперь нужно разобраться, что же происходит при столкновении пришельца с кора- блем. Вместо того чтобы уничтожать экземпляр ship и создавать новый, мы будем подсчитывать количество уничтоженных кораблей; для этого следует организовать сбор статистики по игре. (Статистика также пригодится для подсчета очков.) Завершение игры 287 Напишем новый класс GameStats для ведения статистики и сохраним его в файле game_stats .py : game_stats.py class GameStats(): """Отслеживание статистики для игры Alien Invasion.""" def __init__(self, ai_game): """Инициализирует статистику.""" self.settings = ai_game.settings ❶ self.reset_stats() def reset_stats(self): """Инициализирует статистику, изменяющуюся в ходе игры.""" self.ships_left = self.settings.ship_limit На все время работы Alien Invasion будет создаваться один экземпляр GameStats , но часть статистики должна сбрасываться в начале каждой новой игры. Для этого большая часть статистики будет инициализироваться в методе reset_stats() вместо __init__() . Этот метод будет вызываться из __init__() , чтобы статистика правильно инициализировалась при первом создании экземпляра GameStats , а метод reset_stats() будет вызываться в начале каждой новой игры. Пока в игре используется всего один вид статистики — значение ships_left , из- меняющееся в ходе игры. Количество кораблей в начале игры хранится в settings .py под именем ship_limit : settings.py # Настройки корабля self.ship_speed = 1.5 self.ship_limit = 3 Также необходимо внести ряд изменений в alien_invasion .py для создания экземпляра GameStats . Начнем с обновления команд import в начале файла: alien_invasion.py import sys from time import sleep import pygame from settings import Settings from game_stats import GameStats from ship import Ship Мы импортируем функцию sleep() из модуля time стандартной библиотеки Python, чтобы игру можно было ненадолго приостановить в момент столкновения с кораблем. Также импортируется класс GameStats 288 Глава 13 • Осторожно, пришельцы! Экземпляр GameStats создается в __init__() : alien_invasion.py def __init__(self): self.screen = pygame.display.set_mode( (self.settings.screen_width, self.settings.screen_height)) pygame.display.set_caption("Alien Invasion") # Создание экземпляра для хранения игровой статистики. self.stats = GameStats(self) self.ship = Ship(self) Экземпляр создается после создания игрового окна, но перед определением других игровых элементов (например, корабля). Когда пришелец сталкивается с кораблем, программа уменьшает количество оставшихся кораблей на 1, уничтожает всех существующих пришельцев и снаряды, создает новый флот и возвращает корабль в середину экрана. Также игра ненадолго приостанавливается, чтобы игрок заметил столкновение и перестроился перед по- явлением нового флота. Большая часть этого кода будет вынесена в новый метод _ship_hit() . Этот метод вызывается из _update_aliens() при столкновении пришельца с кораблем: alien_invasion.py def _ship_hit(self): """Обрабатывает столкновение корабля с пришельцем.""" # Уменьшение ships_left. ❶ self.stats.ships_left -= 1 # Очистка списков пришельцев и снарядов. ❷ self.aliens.empty() self.bullets.empty() # Создание нового флота и размещение корабля в центре. ❸ self._create_fleet() self.ship.center_ship() # Пауза. ❹ sleep(0.5) Новый метод _ship_hit() управляет реакцией игры на столкновение корабля с пришельцем. Внутри _ship_hit() число оставшихся кораблей уменьшается на 1 , после чего происходит очистка групп aliens и bullets . Затем программа создает новый флот и выравнивает корабль по центру нижнего края . (Вскоре мы добавим метод center_ship() в класс Ship .) Наконец, после внесения изменений во все игровые элементы, но до перерисовки изменений на Завершение игры 289 экране делается короткая пауза, чтобы игрок увидел, что его корабль столкнулся с пришельцем . Вызов sleep() приостанавливает программу на 0,5 секунды. По- сле завершения паузы управление передается методу _update_screen() , который перерисовывает новый флот на экране. Внутри _update_aliens() вызов print() заменяется вызовом _ship_hit() при столкновении пришельца с кораблем: alien_invasion.py def _update_aliens(self): if pygame.sprite.spritecollideany(self.ship, self.aliens): self._ship_hit() Новый метод center_ship() добавляется в конец файла ship .py : def center_ship(self): """Размещает корабль в центре нижней стороны.""" self.rect.midbottom = self.screen_rect.midbottom self.x = float(self.rect.x) Выравнивание корабля по центру выполняется так же, как и в __init__() . После выравнивания сбрасывается атрибут self.x , чтобы в программе отслеживалась точная позиция корабля. ПРИМЕЧАНИЕ Обратите внимание: программа никогда не создает более одного ко- рабля . Один экземпляр ship используется на протяжении всей игры, а при столкно- вении с пришельцем он просто возвращается к центру экрана . О том, что у игрока не осталось ни одного корабля, программа узнает из атрибута ships_left . Запустите игру, подбейте нескольких пришельцев, а затем позвольте пришельцу столкнуться с кораблем. Происходит небольшая пауза, на экране появляется новый флот вторжения, а корабль возвращается в центр нижней части экрана. Достижение нижнего края экрана Если пришелец добирается до нижнего края экрана, программа будет реагировать так же, как при столкновении с кораблем. Добавьте для проверки этого условия новый метод в alien_invasion .py : alien_invasion.py def _check_aliens_bottom(self): """Проверяет, добрались ли пришельцы до нижнего края экрана.""" screen_rect = self.screen.get_rect() for alien in self.aliens.sprites(): ❶ if alien.rect.bottom >= screen_rect.bottom: # Происходит то же, что при столкновении с кораблем. self._ship_hit() break 290 Глава 13 • Осторожно, пришельцы! Метод check_aliens_bottom() проверяет, есть ли хотя бы один пришелец, добрав- шийся до нижнего края экрана. Условие выполняется, когда атрибут rect.bottom пришельца больше или равен атрибуту rect.bottom экрана . Если пришелец добрался до низа, вызывается функция _ship_hit() . Если хотя бы один пришелец добрался до нижнего края, проверять остальных уже не нужно, поэтому после вы- зова _ship_hit() цикл прерывается. Этот метод вызывается из _update_aliens() : alien_invasion.py def _update_aliens(self): # Проверка коллизий "пришелец — корабль". if pygame.sprite.spritecollideany(self.ship, self.aliens): self._ship_hit() # Проверить, добрались ли пришельцы до нижнего края экрана. self._check_aliens_bottom() Метод _check_aliens_bottom() вызывается после обновления позиций всех при- шельцев и после проверки столкновений «пришелец — корабль» . Теперь новый флот будет появляться как при столкновении корабля с пришельцем, так и в том случае, если кто-то из пришельцев смог добраться до нижнего края экрана. Конец игры Программа Alien Invasion уже на что-то похожа, но игра длится бесконечно. Значе- ние ships_left просто продолжает уходить в отрицательную бесконечность. Доба- вим в GameStats новый атрибут — флаг game_active , который завершает игру после потери последнего корабля. Этот флаг устанавливается в конце метода __init__() в GameStats: game_stats.py def __init__(self, ai_game): # Игра Alien Invasion запускается в активном состоянии. self.game_active = True Добавим в ship_hit() код, который сбрасывает флаг game_active в состояние False при потере игроком последнего корабля: alien_invasion.py def _ship_hit(self): """Обрабатывает столкновение корабля с пришельцем.""" if stats.ships_left > 0: # Уменьшение ships_left. self.stats.ships_left -= 1 Определение исполняемых частей игры 291 # Пауза. sleep(0.5) else: self.stats.game_active = False Большая часть кода _ship_hit() осталась неизменной. Весь существующий код был перемещен в блок if , который проверяет, что у игрока остался хотя бы один корабль. Если корабли еще остаются, программа создает новый флот, делает паузу и продолжает игру. Если же игрок потерял последний корабль, флаг game_active переводится в состояние False Определение исполняемых частей игры В файле alien_invasion .py необходимо определить части игры, которые должны вы- полняться всегда, и те части, которые должны выполняться только при активной игре: alien_invasion.py def run_game(self): """ Запуск основного цикла игры.""" while True: self._check_events() if self.stats.game_active: self.ship.update() self._update_bullets() self._update_aliens() self._update_screen() В основном цикле всегда должна вызываться функция _check_events() , даже если игра находится в неактивном состоянии. Например, программа все равно должна узнать о том, что пользователь нажал клавишу Q для завершения игры или щелк- нул на кнопке закрытия окна. Также экран должен обновляться в то время, пока игрок решает, хочет ли он начать новую игру. Остальные вызовы функций должны происходить только при активной игре, потому что в то время, когда игра неактив- на, обновлять позиции игровых элементов не нужно. В обновленной версии игра должна останавливаться после потери игроком по- следнего корабля. |