Книга Изучаем Python
Скачать 4.68 Mb.
|
292 Глава 14 • Ведение счета score_str = str(self.stats.score) self.score_image = self.font.render(score_str, True, self. text_color, self.ai_settings.bg_color) # Вывод счета в правой верхней части экрана. self.score_rect = self.score_image.get_rect() self.score_rect.right = self.screen_rect.right - 20 self.score_rect.top = 20 В методе prep_score() преобразуем числовое значение stats.score в строку ; эта строка передается методу render() , создающему изображение . Чтобы счет был хорошо виден на экране, мы передаем render() цвет фона и цвет текста. Счет размещается в правой верхней части экрана и расширяется влево с ростом значения и ширины числа. Чтобы счет всегда оставался выровненным по правой стороне, мы создаем прямоугольник rect с именем score_rect и смещаем его правую сторону на 20 пикселов от правого края экрана . Затем верхняя сторона прямоугольника смещается на 20 пикселов вниз от верхнего края экрана . Остается создать метод show_score() для вывода построенного графического изо- бражения: scoreboard.py def show_score(self): """Выводит счет на экран.""" self.screen.blit(self.score_image, self.score_rect) Метод выводит счет на экран в позиции, определяемой score_rect Создание экземпляра Scoreboard Чтобы вывести счет, мы создадим в alien_invasion .py экземпляр Scoreboard : alien_invasion.py from game_stats import GameStats from scoreboard import Scoreboard def run_game(): # Создание экземпляров GameStats и Scoreboard. stats = GameStats(ai_settings) sb = Scoreboard(ai_settings, screen, stats) # Запуск основного цикла игры. while True: gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button) run_game() Подсчет очков 293 Мы импортируем новый класс Scoreboard и создаем экземпляр sb после создания экземпляра stats . Затем экземпляр sb передается методу update_screen() , чтобы счет можно было вывести на экран . Для отображения счета метод update_screen() изменяется следующим образом: game_functions.py def update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button): # Вывод счета. sb.show_score() # Кнопка Play отображается в том случае, если игра неактивна. if not stats.game_active: play_button.draw_button() # Отображение последнего прорисованного экрана. pygame.display.flip() Мы добавляем sb в список параметров, определяющих update_screen() , и вызыва- ем show_score() непосредственно перед отображением кнопки Play Если запустить Alien Invasion сейчас, в правом верхнем углу экрана отображается счет 0. (Пока мы просто хотим убедиться в том, что счет отображается в нужном месте, прежде чем заниматься дальнейшей доработкой системы подсчета очков.) На рис. 14.2 изображено окно игры перед ее началом. Рис. 14.2. Счет отображается в правом верхнем углу экрана А теперь нужно организовать начисление очков за каждого пришельца! 294 Глава 14 • Ведение счета Обновление счета при уничтожении пришельцев Чтобы на экране выводился оперативно обновляемый счет, мы будем обновлять значение stats.score при каждом попадании в пришельца, а затем вызывать prep_score() для обновления изображения счета. Но сначала нужно определить, сколько очков игрок будет получать за каждого пришельца: settings.py def initialize_dynamic_settings(self): # Подсчет очков self.alien_points = 50 Стоимость каждого пришельца в очках будет увеличиваться по ходу игры. Что- бы значение сбрасывалось в начале каждой новой игры, мы задаем значение в initialize_dynamic_settings() Счет будет обновляться за каждого сбитого пришельца в check_bullet_alien_ collisions() : game_functions.py def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets): """Обработка коллизий пуль с пришельцами.""" # Удаление пуль и пришельцев, участвующих в коллизиях. collisions = pygame.sprite.groupcollide(bullets, aliens, True, True) if collisions: stats.score += ai_settings.alien_points sb.prep_score() Мы обновляем определение check_bullet_alien_collisions() и включаем пара- метры stats и sb , чтобы функция могла обновлять счет и рекорд. При попадании пули в пришельца Pygame возвращает словарь collisions . Программа проверяет, существует ли словарь, и если существует — стоимость пришельца добавляется к счету . Затем вызов prep_score() создает новое изображение для обновлен- ного счета. Также необходимо обновить определение update_bullets() , чтобы соответствую- щие аргументы передавались между функциями: game_functions.py def update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets): """Обновляет позиции пуль и удаляет старые пули.""" check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets) Определению update_bullets() необходимы дополнительные параметры stats и sb . Кроме того, вызов check_bullet_alien_collisions() должен включать аргу- менты stats и sb Подсчет очков 295 Также необходимо изменить вызов update_bullets() в основном цикле while : alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, stats, play_button, ship, aliens, bullets) if stats.game_active: ship.update() gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets) При вызове update_bullets() должны передаваться аргументы stats и sb Теперь во время игры вы сможете набирать очки! Начисление очков за все попадания В том виде, в котором написан код, некоторые пришельцы будут пропускаться при подсчете. Например, если две пули попадают в пришельцев во время одного про- хода цикла или если вы создадите широкую «пулю» для поражения нескольких пришельцев одновременно, игрок получит очки только за одного подстреленного пришельца. Чтобы устранить этот недостаток, нужно доработать механизм обна- ружения коллизий между пулями и пришельцами. В функции check_bullet_alien_collisions() любая пуля, столкнувшаяся с при- шельцем, становится ключом словаря collisions . С каждой пулей связывается значение — список пришельцев, участвующих в коллизии. Переберем словарь collisions и убедимся в том, что очки начисляются за каждого подбитого при- шельца: game_functions.py def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets): if collisions: for aliens in collisions.values(): stats.score += ai_settings.alien_points * len(aliens) sb.prep_score() Если словарь collisions был определен, программа перебирает все значения в словаре collisions . Вспомните, что каждое значение представляет собой спи- сок пришельцев, в которых попала одна пуля. Стоимость каждого пришельца умножается на количество пришельцев в списке, а результат прибавляется к те- кущему счету. Чтобы протестировать эту систему, увеличьте ширину пули до 300 пикселов и убе- дитесь в том, что игра начисляет очки за каждого пришельца, в которого попала эта большая пуля; затем верните ширину пули к нормальному состоянию. 296 Глава 14 • Ведение счета Увеличение стоимости пришельцев Так как с каждым достижением нового уровня игра становится более сложной, за пришельцев на этих уровнях следует давать больше очков. Чтобы реализовать эту функциональность, мы добавим код, увеличивающий стоимость пришельцев при возрастании скорости игры: settings.py class Settings(): """Класс для хранения всех настроек игры Alien Invasion.""" def __init__(self): # Темп ускорения игры self.speedup_scale = 1.1 # Темп роста стоимости пришельцев self.score_scale = 1.5 self.initialize_dynamic_settings() 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 self.alien_points = int(self.alien_points * self.score_ scale) В программе определяется коэффициент прироста начисляемых очков; он на- зывается score_scale . С небольшим увеличением скорости (1,1) игра быстро усложняется, но, чтобы увидеть заметную разницу в очках, необходимо изменять стоимость пришельцев в большем темпе (1,5). После увеличения скорости игры стоимость каждого попадания также увеличивается . Чтобы счет возрастал на целое количество очков, в программе используется функция int() Чтобы увидеть стоимость каждого пришельца, добавьте в метод increase_speed() в классе Settings команду print : settings.py def increase_speed(self): self.alien_points = int(self.alien_points * self.score_scale) print(self.alien_points) Новое значение должно выводиться в терминальном окне каждый раз, когда игрок переходит на новый уровень. ПРИМЕЧАНИЕ Убедившись, что стоимость пришельцев действительно возрастает, не забудьте удалить команду print; в противном случае лишний вывод повлияет на быстродействие игры и будет отвлекать игрока . Подсчет очков 297 Округление счета В большинстве аркадных «стрелялок» счет ведется значениями, кратными 10, и мы воспользуемся этой схемой в своей игре. Давайте отформатируем счет так, чтобы в больших числах группы разрядов разделялись запятыми. Изменения вносятся в классе Scoreboard : scoreboard.py def prep_score(self): """Преобразует текущий счет в графическое изображение.""" rounded_score = int(round(self.stats.score, -1)) score_str = "{:,}".format(rounded_score) self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color) Функция round() обычно округляет дробное число до заданного количества зна- ков, переданного во втором аргументе. Но если во втором аргументе передается отрицательное число, round() округляет значение до ближайших десятков, сотен, тысяч и т. д. Код приказывает Python округлить значение stats.score до десят- ков и сохранить его в rounded_score ПРИМЕЧАНИЕ В Python 2 .7 функция round() всегда возвращает дробное значение, поэтому мы используем int(), чтобы гарантировать, что счет будет выводиться в виде целого числа . Если вы используете Python 3, вызов int() можно опустить . Рис. 14.3. Округленный счет с разделителями групп 298 Глава 14 • Ведение счета В точке директива форматирования строки приказывает Python вставить запятые при преобразовании числового значения в строку — например, чтобы вместо 1000000 выводилась строка 1,000,000. Теперь при запуске игры всегда будет отображаться аккуратно отформатированный, округленный счет (рис. 14.3). Рекорды Каждый игрок желает превзойти предыдущий рекорд игры, поэтому мы будем от- слеживать и выводить рекорды, чтобы у игрока была ясная цель. Рекорды будут храниться в классе GameStats : game_stats.py def __init__(self, ai_settings): # Рекорд не должен сбрасываться. self.high_score = 0 Так как рекорд не должен сбрасываться при повторном запуске, значение high_ score инициализируется в __init__() , а не в reset_stats() Теперь изменим класс Scoreboard для отображения рекорда. Начнем с метода __init__() : scoreboard.py def __init__(self, ai_settings, screen, stats): # Подготовка изображений счетов. self.prep_score() self.prep_high_score() Рекорд должен отображаться отдельно от текущего счета, поэтому для подготовки его изображения понадобится новый метод prep_high_score() : scoreboard.py def prep_high_score(self): """Преобразует рекордный счет в графическое изображение.""" high_score = int(round(self.stats.high_score, -1)) high_score_str = "{:,}".format(high_score) self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.ai_settings.bg_color) # Рекорд выравнивается по центру верхней стороны. self.high_score_rect = self.high_score_image.get_rect() self.high_score_rect.centerx = self.screen_rect.centerx self.high_score_rect.top = self.score_rect.top Рекорд округляется до десятков и форматируется с запятыми . Затем для рекорда строится графическое изображение , выполняется горизонтальное вы- равнивание прямоугольника по центру экрана , а атрибут top прямоугольника приводится в соответствие с верхней стороной изображения счета . Подсчет очков 299 Теперь метод show_score() выводит текущий счет в правом верхнем углу, а ре- корд — в центре верхней стороны: scoreboard.py def show_score(self): """Выводит счет на экран.""" self.screen.blit(self.score_image, self.score_rect) self.screen.blit(self.high_score_image, self.high_score_rect) Для обновления рекорда в файл game_functions .py добавляется новая функция check_high_score() : game_functions.py def check_high_score(stats, sb): """Проверяет, появился ли новый рекорд.""" if stats.score > stats.high_score: stats.high_score = stats.score sb.prep_high_score() Рис. 14.4. Рекордный счет выводится в середине экрана Функция check_high_score() получает два параметра, stats и sb . Параметр stats используется для проверки текущего счета и рекорда, а параметр sb необходим для изменения изображения рекорда при необходимости. В точке программа сравнивает текущий счет с рекордом. Если текущий счет выше, мы обновляем значение high_score и вызываем prep_high_score() для обновления изображения рекорда. Функция check_high_score() должна вызываться при каждом попадании в при- шельца после обновления счета в check_bullet_alien_collisions() : 300 Глава 14 • Ведение счета game_functions.py def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets): if collisions: for aliens in collisions.values(): stats.score += ai_settings.alien_points * len(aliens) sb.prep_score() check_high_score(stats, sb) Когда вы впервые играете в Alien Invasion, текущий счет одновременно будет наивысшим, поэтому он будет отображаться и как текущий счет, и как рекорд. Но в начале второй игры ваш предыдущий рекорд должен отображаться в середине, а текущий счет справа, как показано на рис. 14.4. Вывод уровня Чтобы в игре выводился текущий уровень, сначала в класс GameStats следует вклю- чить атрибут для его представления. Чтобы уровень сбрасывался в начале каждой игры, инициализируйте его в reset_stats() : game_stats.py def reset_stats(self): """Инициализирует статистику, изменяющуюся в ходе игры.""" self.ships_left = self.ai_settings.ship_limit self.score = 0 self.level = 1 Чтобы класс Scoreboard выводил текущий уровень (сразу же под текущим счетом), мы вызываем новый метод prep_level() из __init__() : scoreboard.py def __init__(self, ai_settings, screen, stats): # Подготовка изображений счетов. self.prep_score() self.prep_high_score() self.prep_level() Метод prep_level() выглядит так: scoreboard.py def prep_level(self): """Преобразует уровень в графическое изображение.""" self.level_image = self.font.render(str(self.stats.level), True, self.text_color, self.ai_settings.bg_color) # Уровень выводится под текущим счетом. self.level_rect = self.level_image.get_rect() Подсчет очков 301 self.level_rect.right = self.score_rect.right self.level_rect.top = self.score_rect.bottom + 10 Метод prep_level() создает изображение на базе значения, хранящегося в stats. level , и приводит атрибут right изображения в соответствие с атрибутом right счета . Затем атрибут top сдвигается на 10 пикселов ниже нижнего края изображения текущего счета, чтобы между счетом и уровнем оставался пустой интервал . В метод show_score() также необходимо внести изменения: scoreboard.py def show_score(self): """Выводит текущий счет, рекорд и число оставшихся кораблей.""" self.screen.blit(self.score_image, self.score_rect) self.screen.blit(self.high_score_image, self.high_score_rect) self.screen.blit(self.level_image, self.level_rect) Добавленная строка выводит на экран изображение, представляющее уровень. Увеличение stats.level и обновление изображения уровня выполняются в check_ bullet_alien_collisions() : game_functions.py def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets): if len(aliens) == 0: # Если весь флот уничтожен, начинается следующий уровень. bullets.empty() ai_settings.increase_speed() # Увеличение уровня. stats.level += 1 sb.prep_level() create_fleet(ai_settings, screen, ship, aliens) Если все пришельцы уничтожены, программа увеличивает значение stats.level и вызывает prep_level() для обновления уровня . Чтобы убедиться в том, что изображения текущего счета и уровня правильно обновляются в начале новой игры, инициируйте сброс при нажатии кнопки Play : 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: # Сброс игровой статистики. stats.reset_stats() stats.game_active = True # Сброс изображений счетов и уровня. 302 Глава 14 • Ведение счета sb.prep_score() sb.prep_high_score() sb.prep_level() # Очистка списков пришельцев и пуль. aliens.empty() bullets.empty() Определению check_play_button() необходим объект sb . Чтобы сбросить изобра- жения на экране, после сброса игровых настроек следуют вызовы prep_score() , prep_high_score() и prep_level() Затем объект sb передается check_events() , чтобы объект Scoreboard был доступен для check_play_button() : game_functions.py def check_events(ai_settings, screen, stats, sb, 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, sb, play_ button, ship, aliens, bullets, mouse_x, mouse_y) Определение check_events() должно получать sb в параметре, чтобы при вызове check_play_button() можно было передать sb в аргументе . Остается обновить вызов check_events() в alien_invasion .py , чтобы в нем также передавался объект sb : alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets) Теперь количество пройденных уровней отображается на экране (рис. 14.5). |