Изучаем Python — страница 39 из 61


Какое удовольствие от игры, в которой невозможно проиграть? Если игрок не ­успеет сбить флот достаточно быстро, пришельцы уничтожат корабль при столкновении. При этом количество кораблей, используемых игроком, ограничено, и корабль уничтожается, когда пришелец достигает нижнего края экрана. Игра ­завершается в тот момент, когда у игрока кончатся все корабли.

Обнаружение коллизий с кораблем


Начнем с проверки коллизий между пришельцами и кораблем, чтобы мы могли правильно обработать столкновения с пришельцами. Коллизии «пришелец-корабль» проверяются немедленно после обновления позиции каждого пришельца:

game_functions.py

def update_aliens(ai_settings, ship, aliens):

"""

Проверяет, достиг ли флот края экрана,

после чего обновляет позиции всех пришельцев во флоте.

"""

check_fleet_edges(ai_settings, aliens)

aliens.update()

. .

. .# Проверка коллизий "пришелец-корабль".

(1) . .if pygame.sprite.spritecollideany(ship, aliens):

(2) . . . .print("Ship hit!!!")

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

Если ни одна коллизия не обнаружена, spritecollideany() возвращает None, и блок if в точке (1) не выполняется. Если же будет обнаружен пришелец, столкнувшийся с кораблем, метод возвращает этого пришельца, и выполняется блок if: выводится сообщение Ship hit!!! (2). (При столкновении пришельца с кораблем необходимо выполнить ряд операций: удалить всех оставшихся пришельцев и пули, вернуть корабль в центр и создать новый флот. Прежде чем писать код всех этих операций, необходимо убедиться в том, что решение с обнаружением коллизий с кораблем работает правильно. Команда print всего лишь позволяет легко проверить правильность обнаружения коллизий.)

Далее необходимо передать ship функции update_aliens():

alien_invasion.py

# Запуск основного цикла игры.

while True:

gf.check_events(ai_settings, screen, ship, bullets)

ship.update()

gf.update_bullets(ai_settings, screen, ship, aliens, bullets)

. . gf.update_aliens(ai_settings, ship, aliens)

gf.update_screen(ai_settings, screen, ship, aliens, bullets)

Если вы запустите Alien Invasion, при столкновении пришельца с кораблем в терминальном окне появляется сообщение Ship hit!!!. В ходе тестирования этого аспекта присвойте alien_drop_speed более высокое значение (например, 50 или 100), чтобы пришельцы быстрее добирались до вашего корабля.

Обработка столкновений с кораблем


Теперь нужно разобраться, что же происходит при столкновении пришельца с кораблем. Вместо того чтобы уничтожать экземпляр ship и создавать новый, мы будем подсчитывать количество уничтоженных кораблей; для этого следует организовать сбор статистики по игре. (Статистика также пригодится для подсчета очков.)

Напишем новый класс GameStats для ведения статистики и сохраним его в файле game_stats.py:

game_stats.py

class GameStats():

. ."""Отслеживание статистики для игры Alien Invasion."""

. .

. .def __init__(self, ai_settings):

. . . ."""Инициализирует статистику."""

. . . .self.ai_settings = ai_settings

(1) . . . .self.reset_stats()

. . . .

. .def reset_stats(self):

. . . ."""Инициализирует статистику, изменяющуюся в ходе игры."""

. . . .self.ships_left = self.ai_settings.ship_limit

На все время работы Alien Invasion будет создаваться один экземпляр GameStats, но часть статистики должна сбрасываться в начале каждой новой игры. Для этого бульшая часть статистики будет инициализироваться в методе reset_stats() вместо __init__(). Этот метод будет вызываться из __init__(), чтобы статистика правильно инициализировалась при первом создании экземпляра GameStats (1) , а метод reset_stats() будет вызываться в начале каждой новой игры.

Пока в игре используется всего один вид статистики — значение ships_left, изменяющееся в ходе игры. Количество кораблей в начале игры хранится в settings.py под именем ship_limit:

settings.py

# Настройки корабля

self.ship_speed_factor = 1.5

self.ship_limit = 3

Также необходимо внести ряд изменений в alien_invasion.py для создания экземпляра GameStats:

alien_invasion.py

...

from settings import Settings

(1) from game_stats import GameStats

...


def run_game():

...

pygame.display.set_caption("Alien Invasion")

. .# Создание экземпляра для хранения игровой статистики.

(2) . .stats = GameStats(ai_settings)

...

# Запуск основного цикла игры.

while True:

...

gf.update_bullets(ai_settings, screen, ship, aliens, bullets)

(3) . . . .gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)

...

Мы импортируем новый класс GameStats (1) , создаем экземпляр stats (2), а затем добавляем аргументы stats, screen и ship в вызов update_aliens() (3). Эти аргументы будут использоваться для отслеживания количества кораблей, оставшихся у игрока, и построения нового флота при столкновении пришельца с кораблем.

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

Бульшая часть этого кода будет включена в функцию ship_hit():

game_functions.py

import sys

(1) from time import sleep


import pygame

...


def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):

. ."""Обрабатывает столкновение корабля с пришельцем."""

. .# Уменьшение ships_left.

(2) . .stats.ships_left -= 1

. .

. .# Очистка списков пришельцев и пуль.

(3) . .aliens.empty()

. .bullets.empty()

. .

. .# Создание нового флота и размещение корабля в центре.

(4) . .create_fleet(ai_settings, screen, ship, aliens)

. .ship.center_ship()

. .

. .# Пауза.

(5) . .sleep(0.5)

? def update_aliens(ai_settings, stats, screen, ship, aliens, bullets):

...

# Проверка коллизий "пришелец-корабль".

if pygame.sprite.spritecollideany(ship, aliens):

. . . .ship_hit(ai_settings, stats, screen, ship, aliens, bullets)

Сначала мы импортируем функцию sleep() из модуля time, чтобы приостановить игру (1) . Новая функция ship_hit() управляет реакцией игры на столкновение ­корабля с пришельцем. Внутри ship_hit() число оставшихся кораблей уменьшается на 1 (2), после чего происходит очистка групп aliens и bullets (3).

Затем программа создает новый флот и выравнивает корабль по центру нижнего края (4). (Вскоре мы добавим метод center_ship() в класс Ship.) Наконец, после внесения изменений во все игровые элементы, но до перерисовки изменений на экране делается короткая пауза, чтобы игрок увидел, что его корабль столкнулся с пришельцем (5). После завершения паузы sleep() код переходит к функции update_screen(), которая перерисовывает новый флот на экране.

Также необходимо обновить определение update_aliens() и добавить параметры stats, screen и bullets ?, чтобы эти значения можно было передать при вызове ship_hit().

Ниже приведен новый метод center_ship(); добавьте его в конец ship.py:

ship.py

def center_ship(self):

. ."""Размещает корабль в центре нижней стороны."""

. .self.center = self.screen_rect.centerx

Чтобы выровнять корабль по центру, мы задаем атрибуту center корабля значение, соответствующее центру экрана (полученное при помощи атрибута screen_rect).

примечание

Обратите внимание: программа никогда не создает более одного корабля. Один экземпляр ship используется на протяжении всей игры, а при столкновении с пришельцем он просто возвращается к центру экрана. О том, что у игрока не осталось ни одного корабля, программа узнаёт из атрибута ships_left.

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

Достижение нижнего края экрана


Если пришелец добирается до нижнего края экрана, программа будет реагировать так же, как при столкновении с кораблем. Добавьте для проверки этого условия новую функцию, которая будет называться update_aliens():

game_functions.py

def check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets):

. ."""Проверяет, добрались ли пришельцы до нижнего края экрана."""

. .screen_rect = screen.get_rect()

. .for alien in aliens.sprites():

(1) . . . .if alien.rect.bottom >= screen_rect.bottom:

. . . . . .# Происходит то же, что при столкновении с кораблем.

. . . . . .ship_hit(ai_settings, stats, screen, ship, aliens, bullets)

. . . . . .break

. . . . . .

def update_aliens(ai_settings, stats, screen, ship, aliens, bullets):

...

. .# Проверка пришельцев, добравшихся до нижнего края экрана.

(2) . .check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets)

Функция check_aliens_bottom() проверяет, есть ли хотя бы один пришелец, добравшийся до нижнего края экрана. Условие выполняется, когда атрибут rect.bottom пришельца больше или равен атрибуту rect.bottom экрана (1) . Если пришелец добрался до низа, вызывается функция ship_hit(). Если хотя бы один пришелец добрался до нижнего края, проверять остальных уже не нужно, поэтому после вызова ship_hit() цикл прерывается.

Функция check_aliens_bottom() вызывается после обновления позиций всех пришельцев и после проверки столкновений «пришелец-корабль» (2). Теперь новый флот будет появляться как при столкновении корабля с пришельцем, так и в том случае, если кто-то из пришельцев смог добраться до нижнего края экрана.

Конец игры


Программа Alien Invasion уже на что-то похожа, но игра длится бесконечно. Значение ships_left просто продолжает уходить в отрицательную бесконечность. Добавим в GameStats новый атрибут — флаг game_active, который завершает игру после потери последнего корабля:

game_stats.py

def __init__(self, settings):

....

. .# Игра Alien Invasion запускается в активном состоянии.

. .self.game_active = True

Добавим в ship_hit() код, который сбрасывает флаг game_active в состояние False при потере игроком последнего корабля:

game_functions.py

def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):

"""Обрабатывает столкновение корабля с пришельцем."""

. .if stats.ships_left > 0:

# Уменьшение ships_left.

stats.ships_left -= 1

...

# Пауза.

sleep(0.5)


. .else:

. . . .stats.game_active = False

Бульшая часть кода ship_hit() осталась неизменной. Весь существующий код был перемещен в блок if, который проверяет, что у игрока остался хотя бы один корабль. Если корабли не кончились, программа создает новый флот, делает паузу и продолжает игру. Если же игрок потерял последний корабль, флаг game_active переводится в состояние False.

Определение исполняемых частей игры