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


В файле alien_invasion.py необходимо определить части игры, которые должны выполняться всегда, и те части, которые должны выполняться только при активной игре:

alien_invasion.py

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

while True:

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


. .if stats.game_active:

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)

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

В обновленной версии игра должна останавливаться после потери игроком последнего корабля.

Упражнения

13-6. Конец игры: в коде из упражнения 13-5 (с. 274) подсчитывайте, сколько раз игрок не поймал мяч. После трех промахов игра должна заканчиваться.

Итоги


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

В последней главе этого проекта будет добавлена кнопка Play, чтобы игрок мог самостоятельно запустить свою первую игру, а также повторить игру после ее завершения. После каждого уничтожения вражеского флота скорость игры будет возрастать, а мы реализуем систему подсчета очков. В результате вы получите полностью работоспособную игру!

14. Ведение счета


В этой главе построение игры Alien Invasion будет завершено. Мы добавим кнопку Play для запуска игры по желанию игрока или перезапуска игры после ее завершения. Мы также изменим игру, чтобы она ускорялась при переходе игрока на следующий уровень, и реализуем систему подсчета очков. К концу главы вы будете знать достаточно, чтобы заняться разработкой игр, сложность которых нарастает по ходу игры и в которых реализована система подсчета очков.

Добавление кнопки Play


В этом разделе мы добавим кнопку Play, которая отображается перед началом игры и появляется после ее завершения, чтобы игрок мог сыграть снова.

В текущей версии игра начинается сразу же после запуска alien_invasion.py. После очередных изменений игра будет запускаться в неактивном состоянии и предлагать игроку нажать кнопку Play для запуска. Для этого включите следующий код в game_stats.py:

game_stats.py

def __init__(self, ai_settings):

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

self.ai_settings = ai_settings

self.reset_stats()


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

. .self.game_active = False


def reset_stats(self):

...

Итак, программа запускается в неактивном состоянии, а игру можно запустить только нажатием кнопки Play.

Создание класса Button


Так как в Pygame не существует встроенного метода создания кнопок, мы напишем класс Button для создания заполненного прямоугольника с текстовой надписью. Следующий код может использоваться для создания кнопок в любой игре. Ниже приведена первая часть класса Button; сохраните ее в файле button.py:

button.py

import pygame.font


class Button():


(1) . .def __init__(self, ai_settings, screen, msg):

. . . ."""Инициализирует атрибуты кнопки."""

. . . .self.screen = screen

. . . .self.screen_rect = screen.get_rect()

. . . .

. . . .# Назначение размеров и свойств кнопок.

(2) . . . .self.width, self.height = 200, 50

. . . .self.button_color = (0, 255, 0)

. . . .self.text_color = (255, 255, 255)

(3) . . . .self.font = pygame.font.SysFont(None, 48)

. . . .

. . . .# Построение объекта rect кнопки и выравнивание по центру экрана.

(4) . . . .self.rect = pygame.Rect(0, 0, self.width, self.height)

. . . .self.rect.center = self.screen_rect.center

. . . .

. . . .# Сообщение кнопки создается только один раз.

(5) . . . .self.prep_msg(msg)

Сначала программа импортирует модуль pygame.font, который позволяет Pygame выводить текст на экран. Метод __init__() получает параметры self, объекты ai_settings и screen, а также строку msg с текстом кнопки (1) . Размеры кнопки задаются в точке (2), после чего атрибуты button_color и text_color задаются так, чтобы прямоугольник кнопки был окрашен в ярко-зеленый цвет, а текст выводился белым цветом.

В точке (3) происходит подготовка атрибута font для вывода текста. Аргумент None сообщает Pygame, что для вывода текста должен использоваться шрифт по умолчанию, а значение 48 определяет размер текста. Чтобы выровнять кнопку по центру экрана, мы создаем объект rect для кнопки (4) и задаем его атрибут center в соответствии с одноименным атрибутом экрана.

Pygame выводит строку текста в виде графического изображения. В точке (5) эта задача решается методом prep_msg(). Код prep_msg() выглядит так:

button.py

def prep_msg(self, msg):

. ."""Преобразует msg в прямоугольник и выравнивает текст по центру."""

(1) . .self.msg_image = self.font.render(msg, True, self.text_color,

. . . .self.button_color)

(2) . .self.msg_image_rect = self.msg_image.get_rect()

. .self.msg_image_rect.center = self.rect.center

Метод prep_msg() должен получать параметр self и текст, который нужно вывести в графическом виде (msg). Вызов font.render() преобразует текст, хранящийся в msg, в изображение, которое затем сохраняется в msg_image (1) . Методу font.render() также передается логический признак режима сглаживания текста. В остальных аргументах передаются цвет шрифта и цвет фона. В нашем примере режим сглаживания включен (True), а цвет фона совпадает с цветом фона кнопки. (Если цвет фона не указан, Pygame пытается вывести шрифт с прозрачным фоном.)

В точке (3) изображение текста выравнивается по центру кнопки, для чего создается объект rect изображения, а его атрибут center приводится в соответствие с одноименным атрибутом кнопки.

Остается создать метод draw_button(), который может вызываться для отображения кнопки на экране:

button.py

def draw_button(self):

. .# Отображение пустой кнопки и вывод сообщения.

. .self.screen.fill(self.button_color, self.rect)

. .self.screen.blit(self.msg_image, self.msg_image_rect)

Вызов метода screen.fill() рисует прямоугольную часть кнопки. Затем вызов screen.blit() выводит изображение текста на экран с передачей изображения и объекта rect, связанного с изображением. Класс Button готов.

Вывод кнопки на экран


В программе класс Button используется для создания кнопки Play. Так как нам нужна только одна кнопка Play, мы создадим кнопку прямо в файле alien_invasion.py:

alien_invasion.py

...

from game_stats import GameStats

from button import Button

...


def run_game():

...

pygame.display.set_caption("Alien Invasion")

. .

. .# Создание кнопки Play.

(1) . .play_button = Button(ai_settings, screen, "Play")

...


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

while True:

...

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

. . . . . .play_button)


run_game()

Программа импортирует класс Button и создает экземпляр play_button (1) , после чего передает play_button функции update_screen(), чтобы кнопка появлялась при обновлении экрана (2).

Затем следует внести изменения в update_screen(), чтобы кнопка Play появлялась только в неактивном состоянии игры:

game_functions.py

def update_screen(ai_settings, screen, stats, ship, aliens, bullets,

. . . .play_button):

"""Обновляет изображения на экране и отображает новый экран."""

...

. .

. .# Кнопка Play отображается в том случае, если игра неактивна.

. .if not stats.game_active:

. . . .play_button.draw_button()

. . . . . .

# Отображение последнего прорисованного экрана.

pygame.display.flip()

Чтобы кнопка Play не закрывалась другими элементами экрана, мы отображаем ее после всех остальных игровых элементов, но перед переключением на новый экран. Теперь при запуске Alien Invasion в центре экрана отображается кнопка Play (рис. 14.1).

Рис. 14.1. Кнопка Play выводится тогда, когда игра неактивна

Запуск игры


Чтобы при нажатии кнопки Play запускалась новая игра, добавьте в файл game_functions.py следующий код для отслеживания событий мыши над кнопкой:

game_functions.py

def check_events(ai_settings, screen, stats, play_button, ship, bullets):

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

for event in pygame.event.get():

if event.type == pygame.QUIT:

...

(1) . . . .elif event.type == pygame.MOUSEBUTTONDOWN:

(2) . . . . . .mouse_x, mouse_y = pygame.mouse.get_pos()

(3) . . . . . .check_play_button(stats, play_button, mouse_x, mouse_y)


def check_play_button(stats, play_button, mouse_x, mouse_y):

. ."""Запускает новую игру при нажатии кнопки Play."""

(4) . .if play_button.rect.collidepoint(mouse_x, mouse_y):

. . . .stats.game_active = True

Обновленное определение check_events() получает параметры stats и play_button. Параметр stats будет использоваться для обращения к флагу game_active, а play_button — для проверки того, была ли нажата кнопка Play.

Pygame обнаруживает событие MOUSEBUTTONDOWN, когда игрок щелкает в любой точке экрана (1) , но мы хотим ограничить игру, чтобы она реагировала только на щелчки на кнопке Play. Для этого будет использоваться метод pygame.mouse.get_pos(), возвращающий кортеж с координатами x и y точки щелчка (2). Эти значения передаются функции check_play_button() (3), которая использует метод collidepoint() для проверки того, находится ли точка щелчка в пределах области, определяемой прямоугольником кнопки Play (4). Если точка находится в пределах кнопки, флаг game_active переводится в состояние True, и игра начинается!

При вызове check_events() в alien_invasion.py должны передаваться два дополнительных аргумента, stats и play_button:

alien_invasion.py

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

while True:

gf.check_events(ai_settings, screen, stats, play_button, ship,

. . bullets)

...

К этому моменту вы сможете запустить и сыграть полноценную игру. После завершения игры значение game_active становится равным False, а кнопка Play снова появится на экране.

Сброс игры


Только что написанный нами код работает при первом нажатии кнопки Play, но не работает после завершения первой игры, потому что условия, приводящие к окончанию игры, еще не были сброшены.

Чтобы игра сбрасывалась при каждом нажатии кнопки Play, необходимо сбросить игровую статистику, стереть старых пришельцев и пули, построить новый флот и вернуть корабль в центр нижней стороны:

game_functions.py

def check_play_button(ai_settings, screen, stats, play_button, ship, aliens,

. . . .bullets, mouse_x, mouse_y):

"""Запускает новую игру при нажатии кнопки Play."""

if play_button.rect.collidepoint(mouse_x, mouse_y):

. . . .# Сброс игровой статистики.

(1) . . . .stats.reset_stats()

stats.game_active = True

. . . .

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

(2) . . . .aliens.empty()

. . . .bullets.empty()

. . . .

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

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

. . . .ship.center_ship()

Мы обновляем определение check_play_button(), чтобы в нем были доступны объекты ai_settings, stats, ship, aliens и bullets. Эти объекты необходимы для сброса настроек, изменившихся в ходе игры, и для обновления визуальных элементов игры.

В точке (1) обновляется игровая статистика, вследствие чего игрок получает три новых корабля. После этого флаг game_active переводится в состояние True (чтобы игра началась сразу же после выполнения кода функции), группы aliens и bullets очищаются (2), создается новый флот, а корабль выравнивается по ­центру (3).

Для этого необходимо изменить определение check_events(), как и вызов check_play_button():

game_functions.py

def check_events(ai_settings, screen, stats, play_button, ship, aliens,

. . . .bullets):

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

for event in pygame.event.get():

if event.type == pygame.QUIT:

...

elif event.type == pygame.MOUSEBUTTONDOWN:

mouse_x, mouse_y = pygame.mouse.get_pos()

(1) . . . . . .check_play_button(ai_settings, screen, stats, play_ button, ship,

. . . . . . . .aliens, bullets, mouse_x, mouse_y)

Определению check_events() необходим параметр aliens, который будет передаваться check_play_button(). Также обновляется вызов check_play_button() с включением соответствующих аргументов (1) .

Теперь обновите вызов check_events() в alien_invasion.py, чтобы в нем передавался аргумент aliens:

alien_invasion.py

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

while True:

. .gf.check_events(ai_settings, screen, stats, play_button, ship,

. . . .aliens, bullets)

...

После этих изменений игра будет правильно переходить в исходное состояние при каждом нажатии Play, и вы сможете сыграть столько раз, сколько вам захочется!

Блокировка кнопки Play


У кнопки Play в нашем приложении есть одна проблема: область кнопки на экране продолжает реагировать на щелчки, даже если кнопка Play не отображается. Если случайно щелкнуть на месте кнопки Play после начала игры, то игра перезапустится!

Чтобы исправить этот недостаток, следует запускать игру только в том случае, если флаг game_active находится в состоянии False:

game_functions.py

def check_play_button(ai_settings, screen, stats, play_button, ship, aliens,

bullets, mouse_x, mouse_y):

"""Запускает новую игру при нажатии кнопки Play."""

(1) . .button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)

(2) . .if button_clicked and not stats.game_active:

# Сброс игровой статистики.

...

Флаг button_clicked содержит значение True или False (1) ; а игра перезапускается только в том случае, если пользователь нажал кнопку Play, а игра не активна в данный момент (2). Чтобы протестировать это поведение, запустите новую игру и многократно щелкайте в том месте, где должна находиться кнопка Play. Если все работает так, как положено, нажатия кнопки Play не должны влиять на ход игры.

Сокрытие указателя мыши


Указатель мыши должен быть видимым, чтобы пользователь мог начать игру, но после начала игры он только мешает. Чтобы исправить этот недостаток, мы скроем указатель мыши после того, как игра станет активной:

game_functions.py

def check_play_button(ai_settings, screen, stats, 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:

. . . .# Указатель мыши скрывается.

. . . .pygame.mouse.set_visible(False)

...

Вызов set_visible() со значением False приказывает Pygame скрыть указатель, когда он находится над окном игры.

После завершения игры указатель должен появляться снова, чтобы игрок мог нажать кнопку Play для запуска новой игры. Эту задачу решает следующий код:

game_functions.py

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

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

if stats.ships_left > 0:

...

else:

stats.game_active = False

. . . .pygame.mouse.set_visible(True)

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

Упражнения

14-1. Запуск игры клавишей P: так как в Alien Invasion игрок управляет кораблем с клавиатуры, для запуска игры также лучше использовать клавиатуру. Добавьте код, с которым игрок сможет запустить игру нажатием клавиши P. Возможно, часть кода из check_play_button() стоит переместить в функцию start_game(), которая будет вызываться из check_play_button() и check_keydown_events().

14-2. Учебная стрельба: создайте у правого края экрана прямоугольник, который двигается вверх и вниз с постоянной скоростью. У левого края располагается корабль, который перемещается вверх и вниз игроком и стреляет по движущейся прямоугольной мишени. ­Добавьте кнопку Play для запуска игры. После трех промахов игра заканчивается, а на экране снова появляется кнопка Play. Нажатие этой кнопки перезапускает игру.

Повышение сложности