Книга Изучаем Python
Скачать 4.68 Mb.
|
283 font.render() также передается логический признак режима сглаживания текста. В остальных аргументах передаются цвет шрифта и цвет фона. В нашем примере режим сглаживания включен ( True ), а цвет фона совпадает с цветом фона кнопки. (Если цвет фона не указан, Pygame пытается вывести шрифт с прозрачным фоном.) В точке изображение текста выравнивается по центру кнопки, для чего создается объект 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. play_button = Button(ai_settings, screen, "Play") # Запуск основного цикла игры. while True: gf.update_screen(ai_settings, screen, stats, ship, aliens, bullets, play_button) run_game() Программа импортирует класс Button и создает экземпляр play_button , после чего передает play_button функции update_screen() , чтобы кнопка появлялась при обновлении экрана . 284 Глава 14 • Ведение счета Затем следует внести изменения в 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): """Обрабатывает нажатия клавиш и события мыши.""" Добавление кнопки Play 285 for event in pygame.event.get(): if event.type == pygame.QUIT: elif event.type == pygame.MOUSEBUTTONDOWN: mouse_x, mouse_y = pygame.mouse.get_pos() check_play_button(stats, play_button, mouse_x, mouse_y) def check_play_button(stats, play_button, mouse_x, mouse_y): """Запускает новую игру при нажатии кнопки Play.""" 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 , когда игрок щелкает в любой точке экрана , но мы хотим ограничить игру, чтобы она реагировала только на щелчки на кнопке Play . Для этого будет использоваться метод pygame.mouse.get_pos() , возвращающий кортеж с координатами x и y точки щелчка . Эти значения пере- даются функции check_play_button() , которая использует метод collidepoint() для проверки того, находится ли точка щелчка в пределах области, определяемой прямоугольником кнопки Play . Если точка находится в пределах кнопки, флаг 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): 286 Глава 14 • Ведение счета """Запускает новую игру при нажатии кнопки Play.""" if play_button.rect.collidepoint(mouse_x, mouse_y): # Сброс игровой статистики. stats.reset_stats() stats.game_active = True # Очистка списков пришельцев и пуль. aliens.empty() bullets.empty() # Создание нового флота и размещение корабля в центре. create_fleet(ai_settings, screen, ship, aliens) ship.center_ship() Мы обновляем определение check_play_button() , чтобы в нем были доступны объекты ai_settings , stats , ship , aliens и bullets . Эти объекты необходимы для сброса настроек, изменившихся в ходе игры, и для обновления визуальных эле- ментов игры. В точке обновляется игровая статистика, вследствие чего игрок получает три новых корабля. После этого флаг game_active переводится в состояние True (чтобы игра началась сразу же после выполнения кода функции), группы aliens и bullets очищаются , создается новый флот, а корабль выравнивается по центру . Для этого необходимо изменить определение 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() 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() с включением соответствующих аргументов . Теперь обновите вызов check_events() в alien_invasion .py , чтобы в нем передавался аргумент aliens : alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, stats, play_button, ship, aliens, bullets) Добавление кнопки Play 287 После этих изменений игра будет правильно переходить в исходное состояние при каждом нажатии 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.""" button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y) if button_clicked and not stats.game_active: # Сброс игровой статистики. Флаг button_clicked содержит значение True или 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.""" 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): 288 Глава 14 • Ведение счета """Обрабатывает столкновение корабля с пришельцем.""" 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 . Нажатие этой кнопки перезапускает игру . Повышение сложности В текущей версии после того, как весь флот пришельцев будет уничтожен, игрок переходит на новый уровень, но сложность игры остается неизменной. Давайте немного оживим игру и повысим ее сложность; для этого скорость игры будет по- вышаться каждый раз, когда игрок уничтожает весь флот. Изменение настроек скорости Начнем с реорганизации класса Settings и разделения настроек игры на две кате- гории: постоянные и изменяющиеся. Также необходимо проследить за тем, чтобы настройки, изменяющиеся в ходе игры, сбрасывались в исходное состояние в на- чале новой игры. Метод __init__() из файла settings .py выглядит так: settings.py def __init__(self): """Инициализирует статические настройки игры.""" # Настройки экрана self.screen_width = 1200 self.screen_height = 800 self.bg_color = (230, 230, 230) # Настройки корабля self.ship_limit = 3 # Настройки пуль Повышение сложности 289 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = 60, 60, 60 self.bullets_allowed = 3 # Настройки пришельцев self.fleet_drop_speed = 10 # Темп ускорения игры self.speedup_scale = 1.1 self.initialize_dynamic_settings() Значения, которые остаются неизменными, по-прежнему инициализируются в ме- тоде __init__() . В точке добавляется настройка speedup_scale , управляющая быстротой нарастания скорости; со значением 2 скорость удваивается каждый раз, когда игрок переходит на следующий уровень, а со значением 1 скорость остается постоянной. С таким значением, как 1,1, скорость будет увеличиваться в достаточ- ной степени, чтобы игра усложнилась, но не стала невозможной. Наконец, вызов initialize_dynamic_settings() инициализирует значения атрибутов, которые должны изменяться в ходе игры . Код initialize_dynamic_settings() выглядит так: settings.py def initialize_dynamic_settings(self): """Инициализирует настройки, изменяющиеся в ходе игры.""" self.ship_speed_factor = 1.5 self.bullet_speed_factor = 3 self.alien_speed_factor = 1 # fleet_direction = 1 обозначает движение вправо; а -1 - влево. self.fleet_direction = 1 Метод задает исходные значения скоростей корабля, пуль и пришельцев. Эти ско- рости будут увеличиваться по ходу игры и будут сбрасываться каждый раз, когда игрок запускает новую игру. Мы включаем в этот метод fleet_direction , чтобы пришельцы в начале новой игры всегда двигались вправо. Для увеличения скорости корабля, пуль и пришельцев каждый раз, когда игрок достигает нового уровня, в программе используется функция increase_ speed() : settings.py def increase_speed(self): """Увеличивает настройки скорости.""" self.ship_speed_factor *= self.speedup_scale self.bullet_speed_factor *= self.speedup_scale self.alien_speed_factor *= self.speedup_scale Чтобы увеличить скорость этих игровых элементов, мы умножаем каждую на- стройку скорости на значение s peedup_scale 290 Глава 14 • Ведение счета Темп игры повышается вызовом increase_speed() в check_bullet_alien_ collisions() при уничтожении последнего пришельца во флоте, но перед созда- нием нового флота: game_functions.py def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets): if len(aliens) == 0: # Уничтожение пуль, повышение скорости и создание нового флота. bullets.empty() ai_settings.increase_speed() create_fleet(ai_settings, screen, ship, aliens) Изменения значений настроек скорости ship_speed_factor , alien_speed_factor и bullet_speed_factor достаточно для того, чтобы ускорить всю игру! Сброс скорости Каждый раз, когда игрок начинает новую игру, все измененные настройки должны вернуться к исходным значениям, иначе каждая новая игра будет начинаться с по- вышенными настройками скорости предыдущей игры: 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: # Сброс игровых настроек. ai_settings.initialize_dynamic_settings() # Указатель мыши скрывается. pygame.mouse.set_visible(False) Игра Alien Invasion стала достаточно сложной и интересной. Каждый раз, когда игрок очищает экран, игра должна слегка ускориться, а ее сложность должна слегка возрасти. Если сложность игры возрастает слишком быстро, уменьшите значение settings.speedup_scale , а если наоборот, сложность недостаточна, — слегка уве- личьте это значение. Найдите оптимальное значение, оценивая сложность игры за разумный промежуток времени. Первая пара флотов должна быть простой, не- сколько следующих — сложными, но возможными, а при последующих попытках сложность должна становиться практически безнадежной. УПРАЖНЕНИЯ 14-3 . Учебная стрельба с нарастающей сложностью: начните с кода упражнения 14-2 (с . 288) . Скорость мишени должна увеличиваться по ходу игры, а при нажатии игроком кнопки Play мишень должна возвращаться к исходной скорости . Подсчет очков 291 Подсчет очков Система подсчета очков позволит отслеживать счет игры в реальном времени; так- же на экране будет выводиться текущий рекорд, уровень и количество оставшихся кораблей. Счет игры также относится к игровой статистике, поэтому мы добавим атрибут score в класс GameStats : game_stats.py class GameStats(): def reset_stats(self): """Инициализирует статистику, изменяющуюся в ходе игры.""" self.ships_left = self.ai_settings.ship_limit self.score = 0 Чтобы счет сбрасывался при запуске новой игры, мы инициализируем score в reset_stats() вместо __init__() Вывод счета Чтобы вывести счет на экран, мы сначала создаем новый класс Scoreboard . Пока этот класс ограничивается выводом текущего счета, но мы используем его для вы- вода рекордного счета, уровня и количества оставшихся кораблей. Ниже приведена первая часть класса; сохраните ее под именем scoreboard .py : scoreboard.py import pygame.font class Scoreboard(): """Класс для вывода игровой информации.""" def __init__(self, ai_settings, screen, stats): """Инициализирует атрибуты подсчета очков.""" self.screen = screen self.screen_rect = screen.get_rect() self.ai_settings = ai_settings self.stats = stats # Настройки шрифта для вывода счета. self.text_color = (30, 30, 30) self.font = pygame.font.SysFont(None, 48) # Подготовка исходного изображения. self.prep_score() Так как Scoreboard выводит текст на экран, код начинается с импортирования модуля pygame.font . Затем __init__() передаются параметры ai_settings , screen и stats , чтобы класс мог выводить информацию об отслеживаемых показателях . Далее назначается цвет текста и создается экземпляр объекта шрифта . Чтобы преобразовать выводимый текст в изображение, мы вызываем метод prep_ score() , который определяется следующим образом: scoreboard.py def prep_score(self): """Преобразует текущий счет в графическое изображение.""" |