Игра Space Invaders с Python часть 4: SCORING! ! !

см. часть1
см. часть2
см. часть3

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

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

В этом разделе мы добавим кнопку Play, которая появляется перед началом игры
и снова появляется, когда игра заканчивается, чтобы игрок мог играть снова.
Сейчас игра начинается, как только вы запускаете файл alien_invasion.py. Давайте
запустим игру в неактивном состоянии, а затем предложим игроку нажать кнопку Play
чтобы начать игру. Для этого измените метод init() в GameStats:

#game_stats.py

def __init__(self, ai_game):
    """Initialize statistics."""

    self.settings = ai_game.settings
    self.reset_stats()

    # Start game in an inactive state.
    self.game_active = False
Войти в полноэкранный режим Выйти из полноэкранного режима

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

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

Поскольку в Pygame нет встроенного метода для создания кнопок, мы
напишем класс Button для создания заполненного прямоугольника с надписью. Вот первая часть класса Button;
сохраните его как button.py:

#   button.py
import pygame.font

class Button:

    def __init__(self, ai_game, msg):
        """Initialize button attributes."""

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

        # Set the dimensions and properties of the button.
        self.width, self.height = 200, 50
        self.button_color = (0, 255, 0)
        self.text_color = (255, 255, 255)
        self.font = pygame.font.SysFont(None, 48)

        # Build the button's rect object and center it.
        self.rect = pygame.Rect(0, 0, self.width, self.height)
        self.rect.center = self.screen_rect.center

        # The button message needs to be prepped only once.
        self._prep_msg(msg)
Вход в полноэкранный режим Выход из полноэкранного режима

Сначала мы импортируем модуль pygame.font, который позволяет Pygame выводить текст
на экран. Метод init() принимает параметры self, объект ai_game
и msg, который содержит текст кнопки. Мы задаем размеры кнопки, затем устанавливаем button_color, чтобы окрасить объект button’s rect в ярко
зеленый и задаем text_color для отображения текста белым цветом.
Затем мы подготовим атрибут шрифта для отрисовки текста. Аргумент None
указывает Pygame на использование шрифта по умолчанию, а 48 задает размер текста. Чтобы
центрировать кнопку на экране, мы создаем прямоугольник для кнопки и устанавливаем его атрибут
center в соответствии с атрибутом экрана.
Pygame работает с текстом, отображая строку, которую вы хотите вывести на экран, как
изображение. Затем мы вызываем _prep_msg() для обработки этого рендеринга.
Вот код для _prep_msg():

#button.py

def _prep_msg(self, msg):
    """Turn msg into a rendered image and center text on the button."""

    self.msg_image = self.font.render(msg, True, self.text_color,
    self.button_color)
    self.msg_image_rect = self.msg_image.get_rect()
    self.msg_image_rect.center = self.rect.center
Вход в полноэкранный режим Выход из полноэкранного режима

Для метода _prep_msg() требуется параметр self и текст, который будет пере-
в виде изображения (msg). Вызов функции font.render() превращает текст, хранящийся в файле
msg в изображение, которое мы затем сохраняем в self.msg_image. Метод font.render()
также принимает булево значение для включения или выключения сглаживания (сглаживание
делает края текста более плавными). Остальными аргументами являются
заданные цвет шрифта и цвет фона. Мы устанавливаем сглаживание в True
и установим фон текста того же цвета, что и кнопка. (Если вы не
включить цвет фона, Pygame попытается отобразить шрифт с переходящим
родительский фон.)
Затем мы центрируем изображение текста на кнопке, создав прямоугольник из
и установим его атрибут center в соответствии с атрибутом кнопки.
Наконец, мы создаем метод draw_button(), который можно вызвать для отображения кнопки на экране.
кнопку на экране:

#button.py
def draw_button(self):
    # Draw blank button and then draw message.
    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 в AlienInvasion. Сначала мы
обновим операторы импорта:

#alien_invasion.py
--snip--
from game_stats import GameStats
from button import Button
Вход в полноэкранный режим Выход из полноэкранного режима

Поскольку нам нужна только одна кнопка Play, мы создадим ее в методе
init() метода AlienInvasion. Мы можем поместить этот код в самом конце
init():

#alien_invasion.py

def __init__(self):

    --snip--
    self._create_fleet()

    # Make the Play button.
    self.play_button = Button(self, "Play")
Вход в полноэкранный режим Выйти из полноэкранного режима

Этот код создает экземпляр кнопки Button с меткой Play, но он не
рисует кнопку на экране. Мы вызовем метод draw_button() кнопки
в _update_screen():

#alien_invasion.py

def _update_screen(self):

    --snip--
    self.aliens.draw(self.screen)

    # Draw the play button if the game is inactive.
    if not self.stats.game_active:
        self.play_button.draw_button()

    pygame.display.flip()
Вход в полноэкранный режим Выход из полноэкранного режима

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

Запуск игры

Чтобы начать новую игру, когда игрок нажимает кнопку Play, добавьте следующий блок elif
в конец блока _check_events(), чтобы отслеживать события мыши при нажатии на кнопку:

#alien_invasion.py

def _check_events(self):
    """Respond to keypresses and mouse events."""

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            --snip--
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_pos = pygame.mouse.get_pos()
            self._check_play_button(mouse_pos)
Вход в полноэкранный режим Выход из полноэкранного режима

Pygame обнаруживает событие MOUSEBUTTONDOWN, когда игрок щелкает в любом месте
на экране, но мы хотим ограничить нашу игру, чтобы она реагировала на щелчки мыши
только на кнопку Play. Для этого мы используем pygame.mouse.get_pos(),
который возвращает кортеж, содержащий координаты x- и y курсора мыши.
при нажатии на кнопку мыши. Мы отправляем эти значения в новый
метод _check_play_button().
Вот _check_play_button(), который я решил разместить после _check_events():

#alien_invasion.py

def _check_play_button(self, mouse_pos):
    """Start a new game when the player clicks Play."""

    if self.play_button.rect.collidepoint(mouse_pos):
        self.stats.game_active = True
Вход в полноэкранный режим Выход из полноэкранного режима

Мы используем метод rect collidepoint() для проверки того, перекрывает ли точка
перекрывает ли точка щелчка мыши область, определенную прямоугольником кнопки Play. Если да, то мы
устанавливаем game_active в True, и игра начинается!
На этом этапе вы должны иметь возможность начать и играть в полноценную игру. Когда
игра закончится, значение game_active должно стать False, а кнопка Play
должна снова появиться кнопка Play.

Сброс игры

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

#alien_invasion.py

def _check_play_button(self, mouse_pos):
    """Start a new game when the player clicks Play."""

    if self.play_button.rect.collidepoint(mouse_pos):
        # Reset the game statistics.
        self.stats.reset_stats()
        self.stats.game_active = True

        # Get rid of any remaining aliens and bullets.
        self.aliens.empty()
        self.bullets.empty()

        # Create a new fleet and center the ship.
        self._create_fleet()
        self.ship.center_ship()
Вход в полноэкранный режим Выход из полноэкранного режима

Мы сбрасываем статистику игры, что дает игроку три новых
корабля. Затем мы устанавливаем game_active в True, чтобы игра началась, как только
код в этой функции завершит выполнение. Мы опустошаем группы «Пришельцы» и «Пули
группы, а затем создаем новый флот и центрируем корабль.
Теперь игра будет правильно перезагружаться каждый раз, когда вы нажимаете кнопку Play, что позволит вам
играть в нее столько раз, сколько захотите!

Отключение кнопки Play

Одна из проблем с кнопкой Play заключается в том, что область кнопки на экране
продолжает реагировать на нажатия, даже когда кнопка Play не видна. Если вы
случайно нажать на область кнопки Play после начала игры, игра будет
перезапустится!
Чтобы исправить это, настройте игру так, чтобы она запускалась только тогда, когда game_active равно False:

#alien_invasion.py

def _check_play_button(self, mouse_pos):
    """Start a new game when the player clicks Play."""

    button_clicked = self.play_button.rect.collidepoint(mouse_pos)

    if button_clicked and not self.stats.game_active:
        # Reset the game statistics.
        self.stats.reset_stats()
        --snip--
Вход в полноэкранный режим Выход из полноэкранного режима

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

Скрытие курсора мыши

Мы хотим, чтобы курсор мыши был виден для начала игры, но как только игра начинается, он
он просто мешает. Чтобы исправить это, мы сделаем его невидимым, когда игра становится
активной. Мы можем сделать это в конце блока if в _check_play_button():

#alien_invasion.py

def _check_play_button(self, mouse_pos):
    """Start a new game when the player clicks Play."""

    button_clicked = self.play_button.rect.collidepoint(mouse_pos)
    if button_clicked and not self.stats.game_active:
        --snip--
        # Hide the mouse cursor.
        pygame.mouse.set_visible(False)
Войти в полноэкранный режим Выйти из полноэкранного режима

Передача False в set_visible() говорит Pygame, что курсор будет скрыт, когда
когда мышь находится над игровым окном.
Мы заставим курсор снова появиться после окончания игры, чтобы игрок мог
снова нажать Play, чтобы начать новую игру. Вот код для этого:

#alien_invasion.py
def _ship_hit(self):
    """Respond to ship being hit by alien."""

    if self.stats.ships_left > 0:
        --snip--
    else:
        self.stats.game_active = False
        pygame.mouse.set_visible(True)
Войти в полноэкранный режим Выход из полноэкранного режима

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

Повышение уровня

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

Изменение настроек скорости

Сначала мы реорганизуем класс Settings, чтобы сгруппировать настройки игры на
статические и изменяемые. Мы также убедимся, что настройки, изменяемые
во время игры, сбрасываются, когда мы начинаем новую игру. Вот метод init()
метод для файла settings.py:

#settings.py

def __init__(self):
    """Initialize the game's static settings."""

    # Screen settings
    self.screen_width = 1200
    self.screen_height = 800
    self.bg_color = (230, 230, 230)

    # Ship settings
    self.ship_limit = 3

    # Bullet settings
    self.bullet_width = 3
    self.bullet_height = 15
    self.bullet_color = 60, 60, 60
    self.bullets_allowed = 3

    # Alien settings
    self.fleet_drop_speed = 10

    # How quickly the game speeds up
    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):
    """Initialize settings that change throughout the game."""

    self.ship_speed = 1.5
    self.bullet_speed = 3.0
    self.alien_speed = 1.0

    # fleet_direction of 1 represents right; -1 represents left.
    self.fleet_direction = 1
Вход в полноэкранный режим Выйти из полноэкранного режима

Этот метод устанавливает начальные значения для скорости корабля, пули и пришельца.
скоростей. Мы будем увеличивать эти скорости по мере продвижения игрока в игре
и сбрасывать их каждый раз, когда игрок начинает новую игру. Мы включаем флот
_direction в этот метод, чтобы пришельцы всегда двигались вправо в начале
новой игры. Нам не нужно увеличивать значение параметра fleet_drop_speed,
потому что когда пришельцы будут двигаться быстрее по экрану, они также будут спускаться
вниз по экрану быстрее.

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

#settings.py

def increase_speed(self):
    """Increase speed settings."""

    self.ship_speed *= self.speedup_scale
    self.bullet_speed *= self.speedup_scale
    self.alien_speed *= self.speedup_scale
Вход в полноэкранный режим Выход из полноэкранного режима

Чтобы увеличить скорость этих игровых элементов, мы умножим каждую скорость
на значение параметра speedup_scale.
Мы увеличиваем темп игры, вызывая функцию increase_speed() в _check
_bullet_alien_collisions(), когда последний пришелец во флоте был сбит:

#alien_invasion.py

def _check_bullet_alien_collisions(self):

    --snip--

    if not self.aliens:
        # Destroy existing bullets and create new fleet.
        self.bullets.empty()
        self._create_fleet()
        self.settings.increase_speed()
Войти в полноэкранный режим Выход из полноэкранного режима

Изменение значений параметров скорости корабля (ship_speed), скорости пришельца (alien_speed) и
bullet_speed достаточно, чтобы ускорить всю игру!

Сброс скорости

Теперь нам нужно возвращать все измененные настройки к их начальным значениям каждый
когда игрок начинает новую игру; иначе каждая новая игра будет начинаться
с увеличенными настройками скорости предыдущей игры:

#alien_invasion.py

def _check_play_button(self, mouse_pos):
    """Start a new game when the player clicks Play."""

    button_clicked = self.play_button.rect.collidepoint(mouse_pos)

    if button_clicked and not self.stats.game_active:
        # Reset the game settings.
        self.settings.initialize_dynamic_settings()
        --snip--
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь игра в Alien Invasion должна стать более увлекательной и сложной. Каждый
каждый раз, когда вы очищаете экран, игра ускоряется и становится немного
сложнее. Если игра становится слишком сложной слишком быстро, уменьшите значение параметра
значение параметра settings.speedup_scale. Или если игра недостаточно сложна,
немного увеличьте значение. Найдите оптимальный вариант, повышая сложность в течение
разумное количество времени. Первые несколько экранов должны быть легкими, следующие
следующие несколько — сложными, но выполнимыми, а последующие — почти до невозможности
сложными.

Подсчет баллов

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

#game_stats.py
class GameStats:

    --snip--
    def reset_stats(self):
        """Initialize statistics that can change during the game."""

        self.ships_left = self.ai_settings.ship_limit
        self.score = 0
Войти в полноэкранный режим Выйти из полноэкранного режима

Чтобы сбрасывать счет при каждом запуске новой игры, мы инициализируем счет в функции
reset_stats(), а не init().

Отображение счета

Чтобы отобразить счет на экране, мы сначала создадим новый класс Scoreboard. Для
сейчас этот класс будет просто отображать текущий счет, но со временем мы будем использовать его для
его для отображения высокого результата, уровня и количества оставшихся кораблей.
Вот первая часть класса; сохраните ее как scoreboard.py:

#scoreboard.py
import pygame.font
class Scoreboard:
    """A class to report scoring information."""

    def __init__(self, ai_game):
        """Initialize scorekeeping attributes."""

        self.screen = ai_game.screen
        self.screen_rect = self.screen.get_rect()
        self.settings = ai_game.settings
        self.stats = ai_game.stats

        # Font settings for scoring information.
        self.text_color = (30, 30, 30)
        self.font = pygame.font.SysFont(None, 48)

        # Prepare the initial score image.
        self.prep_score()
Вход в полноэкранный режим Выход из полноэкранного режима

Поскольку Scoreboard пишет текст на экран, мы начинаем с импорта модуля
pygame.font. Затем мы передаем init() параметр ai_game, чтобы он мог
получить доступ к объектам settings, screen и stats, которые понадобятся ему для отчета о
значения, которые мы отслеживаем. Затем мы задаем цвет текста и создаем объект шрифта
объект.
Чтобы превратить текст для отображения в изображение, мы вызываем prep_score(),
которую мы определяем здесь:

#scoreboard.py

def prep_score(self):
    """Turn the score into a rendered image."""

    score_str = str(self.stats.score)
    self.score_image = self.font.render(score_str, True,
    self.text_color, self.settings.bg_color)

    # Display the score at the top right of the screen.
    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().
Мы расположим счет в правом верхнем углу экрана и
чтобы он расширялся влево по мере увеличения счета и ширины числа.
увеличивается. Для того чтобы счет всегда располагался в правом углу экрана, создадим прямоугольник под названием
экрана, мы создадим прямоугольник под названием score_rect и установим его правый край на расстоянии 20 пикселей
от правого края экрана. Затем мы разместим верхний край на расстоянии 20 пикселей
вниз от верхней границы экрана.
Затем мы создаем метод show_score(), чтобы отобразить визуализированный счет
изображение:

#scoreboard.py

def show_score(self):
    """Draw score to the screen."""

    self.screen.blit(self.score_image, self.score_rect)
Вход в полноэкранный режим Выход из полноэкранного режима

Этот метод рисует изображение счета на экране в месте, которое указывает score_rect
указывает.

Создание табло

Для отображения счета мы создадим экземпляр табло в AlienInvasion. Во-первых,
давайте обновим операторы импорта:

#alien_invasion.py

--snip--
from game_stats import GameStats
from scoreboard import Scoreboard
--snip--
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем в init() мы создадим экземпляр Scoreboard:

#alien_invasion.py

def __init__(self):
    --snip--
    pygame.display.set_caption("Alien Invasion")
    # Create an instance to store game statistics,
    #and create a scoreboard.

    self.stats = GameStats(self)
    self.sb = Scoreboard(self)
    --snip--
Вход в полноэкранный режим Выход из полноэкранного режима

Затем мы рисуем табло на экране в _update_screen():

#alien_invasion.py

def _update_screen(self):
    --snip--
    self.aliens.draw(self.screen)

    # Draw the score information.
    self.sb.show_score()

    # Draw the play button if the game is inactive.
    --snip--
Войти в полноэкранный режим Выход из полноэкранного режима

Мы вызываем show_score() непосредственно перед тем, как нарисовать кнопку Play.
Когда вы запустите игру Alien Invasion, в правом верхнем углу экрана должна появиться цифра 0.
экрана. (На данном этапе мы просто хотим убедиться, что счет появится в нужном месте, прежде чем разрабатывать систему подсчета очков.
правильном месте перед дальнейшей разработкой системы подсчета очков).

Далее мы присвоим очки каждому пришельцу!

Обновление счета по мере уничтожения пришельцев

Чтобы вывести на экран живой счет, мы обновляем значение stats.score всякий раз, когда
пришельца, а затем вызываем prep_score(), чтобы обновить изображение счета. Но
сначала давайте определим, сколько очков получает игрок каждый раз, когда он сбивает
сбивает инопланетянина:

#settings.py

def initialize_dynamic_settings(self):
    --snip--
    # Scoring
    self.alien_points = 50
Войти в полноэкранный режим Выйти из полноэкранного режима

По мере прохождения игры мы будем увеличивать количество очков каждого пришельца. Для того чтобы
чтобы это значение очков сбрасывалось каждый раз, когда начинается новая игра, мы устанавливаем значение в функции
initialize_dynamic_settings().
Давайте обновлять счет каждый раз, когда инопланетянин сбит в _check_bullet
_alien_collisions():

#alien_invasion.py

def _check_bullet_alien_collisions(self):
    """Respond to bullet-alien collisions."""

    # Remove any bullets and aliens that have collided.
    collisions = pygame.sprite.groupcollide(
    self.bullets, self.aliens, True, True)

    if collisions:
        self.stats.score += self.settings.alien_points
        self.sb.prep_score()
    --snip--
Вход в полноэкранный режим Выход из полноэкранного режима

Когда пуля попадает в инопланетянина, Pygame возвращает словарь столкновений.
Мы проверяем, существует ли этот словарь, и если да, то значение инопланетянина
добавляется к результату. Затем мы вызываем prep_score(), чтобы создать новое изображение для
обновленного результата.
Теперь, когда вы играете в Alien Invasion, вы должны иметь возможность набирать очки!

Сброс очков

Сейчас мы готовим новый счет только после поражения пришельца,
что работает на протяжении большей части игры. Но мы все еще видим старый счет, когда начинается новая
до тех пор, пока в новой игре не будет поражен первый пришелец.
Мы можем исправить эту ситуацию, добавляя счет при начале новой игры:

#alien_invasion.py

def _check_play_button(self, mouse_pos):
    --snip--

    if button_clicked and not self.stats.game_active:
        --snip--
        # Reset the game statistics.
        self.stats.reset_stats()
        self.stats.game_active = True
        self.sb.prep_score()
        --snip--
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Мы вызываем prep_score() после сброса игровой статистики при начале новой игры.
игру. При этом на табло будет установлен счет 0.

Уверенность в том, что все удары засчитаны

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

#alien_invasion.py

def _check_bullet_alien_collisions(self):
    --snip--
    if collisions:
        for aliens in collisions.values():
            self.stats.score += self.settings.alien_points * len(aliens)

        self.sb.prep_score()
    --snip--
Войти в полноэкранный режим Выход из полноэкранного режима

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

Увеличение количества очков

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

settings.py
class Settings:
    """A class to store all settings for Alien Invasion."""

    def __init__(self):
        --snip--

        # How quickly the game speeds up
        self.speedup_scale = 1.1

        # How quickly the alien point values increase
        self.score_scale = 1.5
        self.initialize_dynamic_settings()

    def initialize_dynamic_settings(self):
        --snip--

    def increase_speed(self):
        """Increase speed settings and alien point values."""

        self.ship_speed *= self.speedup_scale
        self.bullet_speed *= self.speedup_scale
        self.alien_speed *= self.speedup_scale
        self.alien_points = int(self.alien_points * self.score_scale)
Вход в полноэкранный режим Выход из полноэкранного режима

Мы определим скорость увеличения очков, которую назовем score_scale.
Небольшое увеличение скорости (1,1) быстро делает игру более сложной.
Но чтобы увидеть более заметную разницу в начислении очков, нам нужно изменить
значение очков пришельцев на большую величину (1,5). Теперь, когда мы увеличиваем скорость игры
скорость игры, мы также увеличиваем значение очков каждого попадания. Мы используем функцию int()
для увеличения значения очков на целые числа.
Чтобы увидеть значение каждого пришельца, добавьте вызов print() к методу increase_speed()
в Настройках:

#settings.py

def increase_speed(self):
    --snip--
    self.alien_points = int(self.alien_points * self.score_scale)
    print(self.alien_points)
Войти в полноэкранный режим Выйти из полноэкранного режима

Новое значение очков должно появляться в терминале каждый раз, когда вы
достижении нового уровня.

Округление очков

В большинстве аркадных игр-стрелялок баллы выводятся в виде кратных 10, поэтому давайте
последуем этому примеру с нашими баллами. Кроме того, давайте отформатируем счет так.
запятые в больших числах. Это изменение мы сделаем в разделе Scoreboard:

#scoreboard.py

def prep_score(self):
    """Turn the score into a rendered image."""

    rounded_score = round(self.stats.score, -1)
    score_str = "{:,}".format(rounded_score)
    self.score_image = self.font.render(score_str, True,
    self.text_color, self.settings.bg_color)
    --snip--
Войти в полноэкранный режим Выйти из полноэкранного режима

Функция round() обычно округляет десятичное число до заданного количества знаков после запятой, указанного в качестве второго аргумента. Однако, когда вы
передать отрицательное число в качестве второго аргумента, round() округлит значение
значение до ближайших 10, 100, 1000 и так далее. Приведенный выше код указывает Python
округлить значение stats.score до ближайших 10 и сохранить его в rounded_score.
Затем директива форматирования строки указывает Python вставить запятые в
при преобразовании числового значения в строку: например, чтобы
вывести 1,000,000 вместо 1000000. Теперь, когда вы запустите игру, вы должны
вы увидите аккуратно отформатированный округленный счет, даже когда наберете много очков.

Высокие показатели

Каждый игрок хочет побить максимальное количество очков в игре, поэтому давайте отслеживать и сообщать о высоком количестве очков.
чтобы игрокам было к чему стремиться. Мы будем хранить высокие баллы в
GameStats:

#game_stats.py

def __init__(self, ai_game):
    --snip--

    # High score should never be reset.
    self.high_score = 0
Войти в полноэкранный режим Выход из полноэкранного режима

Поскольку высокий результат никогда не должен сбрасываться, мы инициализируем high_score в функции
init(), а не в reset_stats().

Далее мы модифицируем Scoreboard для отображения высокого результата. Начнем с
метода init():

#scoreboard.py

def __init__(self, ai_game):
    --snip--

    # Prepare the initial score images.
    self.prep_score()
    self.prep_high_score()
Вход в полноэкранный режим Выйти из полноэкранного режима

Высокий счет будет отображаться отдельно от счета, поэтому нам нужен
новый метод, prep_high_score(), для подготовки изображения высокого результата.
Вот метод prep_high_score():

#scoreboard.py

def prep_high_score(self):
    """Turn the high score into a rendered image."""

    high_score = 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.settings.bg_color)

    # Center the high score at the top of the screen.
    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
Вход в полноэкранный режим Выход из полноэкранного режима

Мы округляем высокий балл до ближайших 10 и форматируем его запятыми.
Затем мы генерируем изображение из высокого результата, центрируем прямоугольник высокого результата
прямоугольник по горизонтали и устанавливаем его атрибут top так, чтобы он совпадал с верхом изображения с высоким результатом
изображение.
Теперь метод show_score() рисует текущий счет в правом верхнем углу
а высокий результат — в верхней центральной части экрана:

#scoreboard.py
def show_score(self):
    """Draw score to the screen."""

    self.screen.blit(self.score_image, self.score_rect)
    self.screen.blit(self.high_score_image, self.high_score_rect)
Вход в полноэкранный режим Выход из полноэкранного режима

Для проверки высоких результатов мы напишем новый метод check_high_score(),
в Scoreboard:

#scoreboard.py

def check_high_score(self):
    """Check to see if there's a new high score."""

    if self.stats.score > self.stats.high_score:
        self.stats.high_score = self.stats.score
        self.prep_high_score()
Войти в полноэкранный режим Выход из полноэкранного режима

Метод check_high_score() проверяет текущий счет по сравнению с
высоким результатом. Если текущий результат больше, мы обновляем значение high_score
и вызываем prep_high_score() для обновления изображения высокого балла.

Нам нужно вызывать check_high_score() каждый раз, когда инопланетянин попадает в цель после обновления
счет в _check_bullet_alien_collisions():

#alien_invasion.py

def _check_bullet_alien_collisions(self):
    --snip--
    if collisions:
        for aliens in collisions.values():
            self.stats.score += self.settings.alien_points * len(aliens)
        self.sb.prep_score()
        self.sb.check_high_score()
    --snip--
Вход в полноэкранный режим Выход из полноэкранного режима

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

Отображение уровня

Чтобы отобразить уровень игрока в игре, нам сначала нужен атрибут в
GameStats, представляющий текущий уровень. Чтобы сбрасывать уровень в начале
каждой новой игры, инициализируйте его в функции reset_stats():

#game_stats.py

def reset_stats(self):
"""Initialize statistics that can change during the game."""

    self.ships_left = self.settings.ship_limit
    self.score = 0
    self.level = 1
Вход в полноэкранный режим Выйти из полноэкранного режима

Чтобы Scoreboard отображал текущий уровень, мы вызываем новый метод prep
level() из __init_():

#scoreboard.py

def __init__(self, ai_game):
    --snip--
    self.prep_high_score()
    self.prep_level()
Войти в полноэкранный режим Выход из полноэкранного режима

Вот prep_level():

#scoreboard.py

def prep_level(self):
"""Turn the level into a rendered image."""

    level_str = str(self.stats.level)
    self.level_image = self.font.render(level_str, True,
    self.text_color, self.settings.bg_color)

    # Position the level below the score.
    self.level_rect = self.level_image.get_rect()
    self.level_rect.right = self.score_rect.right
    self.level_rect.top = self.score_rect.bottom + 10
Войти в полноэкранный режим Выход из полноэкранного режима

Метод prep_level() создает изображение из значения, хранящегося в файле
stats.level и устанавливает правый атрибут изображения в соответствие с правым атрибутом оценки
атрибуту. Затем он устанавливает верхний атрибут на 10 пикселей ниже нижней части
изображения результата, чтобы оставить пространство между результатом и уровнем.
Также необходимо обновить функцию show_score():

#scoreboard.py
def show_score(self):
"""Draw scores and level to the screen."""

    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():

#alien_invasion.py

def _check_bullet_alien_collisions(self):
    --snip--
    if not self.aliens:

        # Destroy existing bullets and create new fleet.
        self.bullets.empty()
        self._create_fleet()
        self.settings.increase_speed()

        # Increase level.
        self.stats.level += 1
        self.sb.prep_level()
Вход в полноэкранный режим Выход из полноэкранного режима

Если флот уничтожен, мы увеличиваем значение stats.level и вызываем функцию
prep_level(), чтобы убедиться, что новый уровень отображается правильно.
Чтобы изображение уровня правильно обновлялось в начале новой игры,
мы также вызываем prep_level(), когда игрок нажимает кнопку Play:

#alien_invasion.py

def _check_play_button(self, mouse_pos):
    --snip--
    if button_clicked and not self.stats.game_active:
        --snip--
        self.sb.prep_score()
        self.sb.prep_level()
        --snip--
Вход в полноэкранный режим Выход из полноэкранного режима

Мы вызываем prep_level() сразу после вызова prep_score().
Теперь вы увидите, сколько уровней вы прошли.

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

Наконец, давайте отобразим количество кораблей, оставшихся у игрока, но на этот раз
воспользуемся графикой. Для этого нарисуем корабли в верхнем левом углу
экрана, чтобы показать, сколько кораблей осталось, как во многих классических аркадных
игры.
Во-первых, нам нужно сделать Ship наследником от Sprite, чтобы мы могли создать группу
кораблей:

#ship.py

import pygame
from pygame.sprite import Sprite

class Ship(Sprite):
    """A class to manage the ship."""

    def __init__(self, ai_game):
    """Initialize the ship and set its starting position."""

        super().__init__()
        --snip--
Войдите в полноэкранный режим Выход из полноэкранного режима

Здесь мы импортируем Sprite, убедимся, что Ship наследует от Sprite, и вызовем
super() в начале init().
Далее нам нужно изменить Scoreboard, чтобы создать группу кораблей, которые мы можем
отобразить. Вот утверждения импорта для Scoreboard:

#scoreboard.py

import pygame.font
from pygame.sprite import Group
from ship import Ship

Войти в полноэкранный режим Выйти из полноэкранного режима

Поскольку мы создаем группу кораблей, мы импортируем классы Group и Ship
классы.
Вот init():

#scoreboard.py
def __init__(self, ai_game):
    """Initialize scorekeeping attributes."""

    self.ai_game = ai_game
    self.screen = ai_game.screen
    --snip--
    self.prep_level()
    self.prep_ships()
Вход в полноэкранный режим Выход из полноэкранного режима

Мы присваиваем экземпляру игры атрибут, потому что он понадобится нам для
создания кораблей. Мы вызываем prep_ships() после вызова prep_level().
Вот prep_ships():

#scoreboard.py

def prep_ships(self):
    """Show how many ships are left."""

    self.ships = Group()
    for ship_number in range(self.stats.ships_left):
        ship = Ship(self.ai_game)
        ship.rect.x = 10 + ship_number * ship.rect.width
        ship.rect.y = 10
        self.ships.add(ship)
Вход в полноэкранный режим Выход из полноэкранного режима

Метод prep_ships() создает пустую группу self.ships для хранения
экземпляров кораблей. Чтобы заполнить эту группу, цикл запускается один раз для каждого корабля, который
который покинул игрок. Внутри цикла мы создаем новый корабль и устанавливаем для каждого корабля значение
x-координату, чтобы корабли отображались рядом друг с другом с 10-пиксельным
с отступом в 10 пикселей от левой стороны группы кораблей. Мы устанавливаем значение координаты y
на 10 пикселей вниз от верхней части экрана, чтобы корабли появились в
левом верхнем углу экрана. Затем мы добавляем каждый новый корабль в группу
ships .
Теперь нам нужно нарисовать корабли на экране:

#scoreboard.py

def show_score(self):
"""Draw scores, level, and ships to the screen."""

    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)
    self.ships.draw(self.screen)
Войдите в полноэкранный режим Выйти из полноэкранного режима

Чтобы вывести корабли на экран, мы вызываем функцию draw() для группы, и
Pygame рисует каждый корабль.
Чтобы показать игроку, сколько кораблей у него есть для начала, мы вызываем функцию
prep_ships(), когда начинается новая игра. Мы делаем это в _check_play_button() в программе
AlienInvasion:

#alien_invasion.py

def _check_play_button(self, mouse_pos):
    --snip--
    if button_clicked and not self.stats.game_active:
        --snip--
        self.sb.prep_score()
        self.sb.prep_level()
        self.sb.prep_ships()
        --snip--
Вход в полноэкранный режим Выйти из полноэкранного режима

Мы также вызываем prep_ships(), когда корабль подбит, чтобы обновить отображение кораблей
когда игрок теряет корабль:

#alien_invasion.py

def _ship_hit(self):
"""Respond to ship being hit by alien."""

    if self.stats.ships_left > 0:

        # Decrement ships_left, and update scoreboard.
        self.stats.ships_left -= 1
        self.sb.prep_ships()
        --snip--
Вход в полноэкранный режим Выход из полноэкранного режима

Мы вызываем prep_ships() после уменьшения значения ships_left, поэтому правильное количество кораблей отображается каждый раз, когда игрок теряет корабль.
правильное количество кораблей отображается каждый раз, когда корабль уничтожается.

Чтобы получить доступ к полному исходному коду проекта и файлам, посетите репозиторий на Github.

Вот и все для игры Alien Invasion. Далее я буду создавать игру flappy bird, используя pygame.

Оцените статью
devanswers.ru
Добавить комментарий