Вывод количества кораблей
Остается вывести количество кораблей, оставшихся у игрока, но на этот раз информация будет выводиться в графическом виде. Как во многих классических аркадных играх, в левом верхнем углу экрана программа рисует несколько изображений корабля. Каждый корабль обозначает одну оставшуюся попытку.
Для начала нужно сделать так, чтобы класс Ship наследовал от Sprite, — это необходимо для создания группы кораблей:
ship.py
import pygame
from pygame.sprite import Sprite
(1) class Ship(Sprite):
. .
def __init__(self, ai_settings, screen):
"""Инициализирует корабль и задает его начальную позицию."""
(2) . . . .super(Ship, self).__init__()
...
Здесь мы импортируем Sprite, объявляем о наследовании Ship от Sprite (1) и вызываем super() в начале __init__() (2).
Далее необходимо изменить Scoreboard и создать группу кораблей для вывода на экран. Команды import и метод __init__() выглядят так:
scoreboard.py
import pygame.font
from pygame.sprite import Group
from ship import Ship
class Scoreboard():
"""Класс для вывода игровой информации."""
def __init__(self, ai_settings, screen, stats):
...
self.prep_level()
. . . .self.prep_ships()
...
Так как мы собираемся создать группу кораблей, программа импортирует классы Group и Ship. Метод prep_ships() будет вызываться после prep_level(). Он выглядит так:
scoreboard.py
. .def prep_ships(self):
. . . ."""Сообщает количество оставшихся кораблей."""
(1) . . . .self.ships = Group()
(2) . . . .for ship_number in range(self.stats.ships_left):
. . . . . .ship = Ship(self.ai_settings, self.screen)
(3) . . . . . .ship.rect.x = 10 + ship_number * ship.rect.width
(4) . . . . . .ship.rect.y = 10
(5) . . . . . .self.ships.add(ship)
Метод prep_ships() создает пустую группу self.ships для хранения экземпляров кораблей (1) . В ходе заполнения этой группы цикл выполняется по одному разу для каждого корабля, оставшегося у игрока (3). В цикле создается новый корабль, а координата x этого корабля задается так, чтобы корабли размещались рядом друг с другом, разделенные интервалами величиной 10 пикселов (3). Координата y задается так, чтобы корабли были смещены на 10 пикселов от верхнего края экрана и были выровнены по изображению текущего счета (4). Наконец, каждый корабль добавляется в группу ships (5).
Следующим шагом становится вывод кораблей на экран:
scoreboard.py
def show_score(self):
...
self.screen.blit(self.level_image, self.level_rect)
. .# Вывод кораблей.
. .self.ships.draw(self.screen)
При выводе кораблей на экран мы вызываем метод draw() для группы, а Pygame рисует каждый отдельный корабль.
Чтобы игрок видел, сколько попыток у него в начале игры, мы вызываем prep_ships() при запуске новой игры. Это происходит в функции check_play_button() из файла game_functions.py:
game_functions.py
def check_play_button(ai_settings, screen, stats, sb, play_button, ship,
aliens, bullets, mouse_x, mouse_y):
"""Запускает новую игру при нажатии кнопки Play."""
button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
if button_clicked and not stats.game_active:
...
# Сброс изображений счетов и уровня.
sb.prep_score()
sb.prep_high_score()
sb.prep_level()
. . . .sb.prep_ships()
...
Метод prep_ships() также вызывается при столкновении пришельца с кораблем, чтобы изображение обновлялось при потере корабля:
game_functions.py
(1) def update_aliens(ai_settings, screen, stats, sb, ship, aliens, bullets):
...
# Проверка коллизий "пришелец-корабль".
if pygame.sprite.spritecollideany(ship, aliens):
(2) . . . .ship_hit(ai_settings, screen, stats, sb, ship, aliens, bullets)
. .
# Проверяет, добрались ли пришельцы до нижнего края экрана.
(3) . .check_aliens_bottom(ai_settings, screen, stats, sb, ship, aliens, bullets)
(4)def ship_hit(ai_settings, screen, stats, sb, ship, aliens, bullets):
"""Обрабатывает столкновение корабля с пришельцем."""
if stats.ships_left > 0:
# Уменьшение ships_left.
stats.ships_left -= 1
. . . .
. . . .# Обновление игровой информации.
(5) . . . .sb.prep_ships()
# Очистка списков пришельцев и пуль.
...
Сначала параметр sb добавляется в определение update_aliens() (1) . Затем программа передает sb функциям ship_hit() (2) и check_aliens_bottom(), чтобы эти функции имели доступ к объекту Scoreboard (3).
Затем определение ship_hit() изменяется с включением sb (4). Метод prep_ships() вызывается после уменьшения значения ships_left (5), так что при каждой потере корабля выводится правильное количество изображений.
Вызов ship_hit() также включен в check_aliens_bottom(), так что эту функцию тоже нужно обновить:
game_functions.py
def check_aliens_bottom(ai_settings, screen, stats, sb, ship, aliens,
. . . .bullets):
"""Проверяет, добрались ли пришельцы до нижнего края экрана."""
screen_rect = screen.get_rect()
for alien in aliens.sprites():
if alien.rect.bottom >= screen_rect.bottom:
# Происходит то же, что при столкновении с кораблем.
. . . . . .ship_hit(ai_settings, screen, stats, sb, ship, aliens, bullets)
break
Так как check_aliens_bottom() теперь получает параметр sb, мы добавляем аргумент sb в вызов ship_hit().
Остается добавить sb в вызов update_aliens() в файле alien_invasion.py:
alien_invasion.py
# Запуск основного цикла игры.
while True:
...
if stats.game_active:
ship.update()
gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens,
bullets)
. . . .gf.update_aliens(ai_settings, screen, stats, sb, ship, aliens,
. . . . . .bullets)
...
На рис. 14.6 показана полная игровая информация на экране, с количеством оставшихся кораблей в левой верхней части экрана.
Рис. 14.6. Полная игровая информация в Alien Invasion
Упражнения
14-4. Исторический рекорд: в текущей версии рекорд сбрасывается каждый раз, когда игрок закрывает и перезапускает Alien Invasion. Чтобы этого не происходило, запишите рекорд в файл перед вызовом sys.exit() и загрузите его при инициализации значения в GameStats.
14-5. Рефакторинг: найдите функции и методы, которые решают более одной задачи, и проведите рефакторинг, улучшающий структуру и эффективность кода. Например, переместите часть кода функции check_bullet_alien_collisions(), которая запускает новый уровень при уничтожении флота, в функцию start_new_level(). Также переместите четыре метода, вызываемых в методе __init__() класса Scoreboard, в метод prep_images() для сокращения длины __init__(). Метод prep_images() также может оказать помощь check_play_button() или start_game(), если вы уже провели рефакторинг check_play_button().
примечание
Прежде чем браться за рефакторинг проекта, обратитесь к приложению Г. В нем рассказано, как восстановить рабочее состояние проекта, если в ходе рефакторинга были допущены ошибки.
14-6. Расширение Alien Invasion: подумайте над возможными расширениями Alien Invasion. Например, пришельцы тоже могут стрелять по кораблю, или же вы можете добавить укрытия, за которыми может скрываться корабль (укрытия могут разрушаться пулями с обеих сторон). Или добавьте звуковые эффекты (например, взрывы или звуки выстрелов) средствами модуля pygame.mixer.