- ALIENS
- Создание класса пришельцев
- Создание экземпляра пришельца
- Создание флота пришельцев
- Определение количества инопланетян в ряду
- Создание ряда пришельцев
- Рефакторинг _create_fleet()
- Добавление рядов
- Заставляем флот двигаться
- Перемещение пришельцев вправо
- Создание настроек для направления флота
- Проверка того, попал ли инопланетянин на край
- Сброс флота и изменение направления движения
- Стрельба по пришельцам
- Обнаружение столкновений пуль
- Заселение флота
- Ускорение пуль
- Рефакторинг _update_bullets()
- Завершение игры
- Обнаружение столкновений пришельцев и кораблей
- Реагирование на столкновения пришельцев и кораблей
- Пришельцы, которые достигают нижней части экрана
- Игра окончена!
- Определение времени выполнения частей игры
ALIENS
Смотрите часть 1
См. часть 2
В предыдущих частях мы рассмотрели, как начать использовать pygame, как работать с классами, создали класс Alien Invasion Game, который отвечал за запуск игры, добавили изображение корабля в нашу игру и добавили некоторую функциональность, позволяющую управлять нашим кораблем. Затем мы добавили пули на наш корабль, добавили функциональность стрельбы пулями и т.д. В сегодняшнем уроке мы добавим пришельцев, позволим пришельцам двигаться и добавим другие функции, которые сделают нашу игру более реалистичной, например, сбивать пришельцев и т.д.
Размещение одного пришельца на экране будет подобно размещению корабля на экране. Каждый
поведением каждого пришельца управляет класс под названием Alien, который мы структурируем
как класс «Корабль». Выберите изображение, которое вы будете использовать в своем проекте, и сохраните его в папке images.
Создание класса пришельцев
Теперь мы напишем класс Alien и сохраним его под именем alien.py:
#Aliens.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""A class to represent a single alien in the fleet."""
def __init__(self, ai_game):
"""Initialize the alien and set its starting position."""
super().__init__()
self.screen = ai_game.screen
# Load the alien image and set its rect attribute.
self.image = pygame.image.load('images/alien.bmp')
self.rect = self.image.get_rect()
# Start each new alien near the top left of the screen.
self.rect.x = self.rect.width
self.rect.y = self.rect.height
# Store the alien's exact horizontal position.
self.x = float(self.rect.x)
В основном этот класс похож на класс «Корабль», за исключением расположения пришельцев
на экране. Изначально мы размещаем каждого пришельца в левом верхнем углу экрана.
экрана; мы добавляем слева от него пространство, равное ширине пришельца
и пространство над ним, равное его высоте, чтобы его было легко увидеть. Нас в основном
интересует горизонтальная скорость пришельцев, поэтому мы будем отслеживать горизонтальное
положение каждого пришельца.
Класс Alien не нуждается в методе для рисования на экране;
Вместо этого мы будем использовать групповой метод Pygame, который автоматически рисует все
элементы группы на экран.
Создание экземпляра пришельца
Мы хотим создать экземпляр Alien, чтобы мы могли увидеть первого пришельца на
экране. Поскольку это часть нашей работы по настройке, мы добавим код для этого
экземпляра в конце метода init() в AlienInvasion. В конце концов,
мы создадим целый флот инопланетян, что будет довольно сложной задачей,
поэтому мы создадим новый вспомогательный метод _create_fleet().
Я помещу _create_fleet() непосредственно перед
_update_screen(), но подойдет любое место в AlienInvasion. Сначала мы
импортируем класс Alien.
Вот обновленные утверждения импорта для alien_invasion.py:
#alien_invasion.py
--snip--
from bullet import Bullet
from alien import Alien
Обновленный метод init:
# alien_invasion.py
def __init__(self):
--snip--
self.ship = Ship(self)
self.bullets = pygame.sprite.Group()
self.aliens = pygame.sprite.Group()
self._create_fleet()
Мы создаем группу для хранения флота пришельцев и вызываем _create_fleet(),
что следует ниже.
# alien_invasion.py
def _create_fleet(self):
"""Create the fleet of aliens."""
# Make an alien.
alien = Alien(self)
self.aliens.add(alien)
В этом методе мы создаем один экземпляр Alien, а затем добавляем его
в группу, которая будет содержать флот. Чужой будет помещен в стандартную
левой верхней области экрана, что идеально подходит для первого пришельца.
Чтобы пришелец появился, нам нужно вызвать метод draw() группы в функции
_update_screen():
# alien_invasion.py
def _update_screen(self):
--snip--
for bullet in self.bullets.sprites():
bullet.draw_bullet()
self.aliens.draw(self.screen)
pygame.display.flip()
Когда вы вызываете метод draw() для группы, Pygame рисует каждый элемент группы в позиции, определенной его прямоугольником.
группы в позиции, определенной атрибутом rect. Метод draw()
требует один аргумент: поверхность, на которой нужно нарисовать элементы из группы
группы.
Когда вы запустите игру в файле alien_invasion.py, на экране появится первый пришелец.
Создание флота пришельцев
Чтобы нарисовать флот, нам нужно выяснить, сколько инопланетян может поместиться на экране, и сколько рядов инопланетян может поместиться на поверхности
экрана и сколько рядов инопланетян может поместиться на экране. Сначала мы рассчитаем
определим расстояние между пришельцами по горизонтали и создадим ряд; затем определим расстояние по вертикали и создадим ряд.
определим расстояние по вертикали и создадим целый флот.
Определение количества инопланетян в ряду
Чтобы определить, сколько инопланетян помещается в ряд, давайте посмотрим, сколько горизонтального
пространства. Ширина экрана хранится в settings.screen_width, но нам
необходимо свободное пространство по обе стороны экрана. Мы сделаем это поле
шириной с одного инопланетянина. Поскольку у нас есть два поля, доступное пространство для
пришельцев — это ширина экрана минус две ширины пришельцев:
available_space_x = settings.screen_width – (2 * alien_width)
Нам также нужно задать расстояние между пришельцами; мы сделаем его равным ширине одного пришельца.
ширина. Пространство, необходимое для отображения одного пришельца, в два раза больше его ширины: одна ширина
для инопланетянина и одна ширина для пустого пространства справа от него. Чтобы найти
количество инопланетян, которые поместятся на экране, мы разделим доступное пространство на
двукратную ширину инопланетянина. Мы используем деление на пол (//), которое делит два
и отбрасывает остаток, поэтому мы получим целое число инопланетян:
number_aliens_x = available_space_x // (2 * alien_width)
Создание ряда пришельцев
Мы готовы создать полный ряд инопланетян. Поскольку наш код для создания
одного пришельца работает, мы перепишем _create_fleet() для создания целого ряда пришельцев.
пришельцев:
#alien_invasion.py
def _create_fleet(self):
"""Create the fleet of aliens."""
# Create an alien and find the number of aliens in a row.
# Spacing between each alien is equal to one alien width.
alien = Alien(self)
alien_width = alien.rect.width
available_space_x = self.settings.screen_width - (2 * alien_width)
number_aliens_x = available_space_x // (2 * alien_width)
# Create the first row of aliens.
for alien_number in range(number_aliens_x):
# Create an alien and place it in the row.
alien = Alien(self)
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
self.aliens.add(alien)
Мы уже продумали большую часть этого кода. Нам нужно знать
ширину и высоту пришельца, чтобы разместить пришельцев, поэтому мы создаем пришельца до того, как
мы выполняем вычисления. Этот пришелец не будет частью флота, поэтому не добавляйте его
его в группу пришельцев. Затем мы получим ширину пришельца из его атрибута rect
и сохранить это значение в alien_width, чтобы нам не пришлось постоянно работать через
атрибут rect. Затем мы вычисляем горизонтальное пространство, доступное для пришельцев
и количество инопланетян, которые могут поместиться в этом пространстве.
Далее мы создаем цикл, который считает от 0 до количества пришельцев, которых нам нужно
необходимо сделать. В основной части цикла мы создаем нового пришельца и
затем устанавливаем значение его координаты x, чтобы поместить его в ряд. Каждый пришелец сдвигается
вправо на ширину одного инопланетянина от левого края. Затем мы умножаем
ширину пришельца на 2, чтобы учесть пространство, которое занимает каждый пришелец, включая
пустое пространство справа от него, и умножаем эту величину на позицию пришельца
в ряду. Мы используем атрибут x пришельца, чтобы установить положение его прямоугольника. Затем
мы добавляем каждого нового пришельца в группу пришельцев.
Когда вы запустите Alien Invasion, вы увидите первый ряд пришельцев.
Первый ряд смещен влево, что на самом деле хорошо для игрового процесса.
Причина в том, что мы хотим, чтобы флот двигался вправо, пока не достигнет края
экрана, затем немного опускался вниз, затем двигался влево и так далее. Как в
классической игре Space Invaders, такое движение более интересно, чем когда флот падает прямо вниз.
флот падает прямо вниз. Мы будем продолжать это движение до тех пор, пока все пришельцы не будут
или пока инопланетянин не упадет на корабль или в нижнюю часть экрана.
Рефакторинг _create_fleet()
Если бы код, который мы написали до сих пор, был всем, что нам нужно для создания флота, мы бы, вероятно…
бы оставить _create_fleet() как есть. Но нам предстоит еще много работы, поэтому давайте немного почистим
немного почистим метод. Мы добавим новый вспомогательный метод, _create_alien(), и
и вызовем его из _create_fleet():
#alien_invasion.py
def _create_fleet(self):
--snip--
# Create the first row of aliens.
for alien_number in range(number_aliens_x):
self._create_alien(alien_number)
def _create_alien(self, alien_number):
"""Create an alien and place it in the row."""
alien = Alien(self)
alien_width = alien.rect.width
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
self.aliens.add(alien)
Метод _create_alien() требует один параметр в дополнение к self:
ему нужен номер пришельца, который создается в данный момент. Мы используем то же самое
тело, которое мы сделали для _create_fleet(), за исключением того, что мы получаем ширину пришельца
внутри метода вместо того, чтобы передавать ее в качестве аргумента. Этот рефакторинг
упростит добавление новых строк и создание целого флота.
Добавление рядов
Чтобы закончить создание флота, мы определим количество рядов, которые помещаются на экране
а затем повторим цикл создания инопланетян в одном ряду до тех пор, пока у нас не будет
нужное количество рядов. Чтобы определить количество рядов, мы находим
доступное вертикальное пространство путем вычитания высоты пришельца сверху, высоты корабля
и двух пришельцев из нижней части экрана:
available_space_y = settings.screen_height – (3 * alien_height) – ship_height
В результате образуется некоторое пустое пространство над кораблем, так что у игрока
чтобы у игрока было время начать стрелять в пришельцев в начале каждого уровня.
Каждому ряду необходимо пустое пространство под ним, которое мы сделаем равным
высоте одного пришельца. Чтобы определить количество рядов, разделим имеющееся пространство
на двукратную высоту инопланетянина. Мы используем деление на пол, потому что мы можем
сделать целое число рядов.
number_rows = available_height_y // (2 * alien_height)
Теперь, когда мы знаем, сколько рядов помещается во флоте, мы можем повторить код
для создания ряда:
#alien_invasion.py
def _create_fleet(self):
--snip--
alien = Alien(self)
alien_width, alien_height = alien.rect.size
available_space_x = self.settings.screen_width - (2 * alien_width)
number_aliens_x = available_space_x // (2 * alien_width)
# Determine the number of rows of aliens that fit on the screen.
ship_height = self.ship.rect.height
available_space_y = (self.settings.screen_height - (3 * alien_height) - ship_height)
number_rows = available_space_y // (2 * alien_height)
# Create the full fleet of aliens.
for row_number in range(number_rows):
for alien_number in range(number_aliens_x):
self._create_alien(alien_number, row_number)
def _create_alien(self, alien_number, row_number):
"""Create an alien and place it in the row."""
alien = Alien(self)
alien_width, alien_height = alien.rect.size
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
self.aliens.add(alien)
Нам нужны ширина и высота пришельца, поэтому мы используем атрибут
size, который содержит кортеж с шириной и высотой объекта rect. Чтобы
рассчитать количество строк, которые мы можем уместить на экране, мы пишем наш доступный
_пространство_y сразу после вычисления доступного_пространства_x. Вычисление
вычисление заключено в круглые скобки, поэтому результат может быть разделен на две
строк, что позволяет получить строки длиной не более 79 символов.
Для создания нескольких строк мы используем два вложенных цикла: один внешний и один
внутренний цикл. Внутренний цикл создает пришельцев в одной строке. Внешний цикл
считает от 0 до нужного нам количества рядов; Python использует код для
создания одного ряда и повторяет его количество_рядов.
Чтобы вложить циклы, напишите новый цикл for и отступите от кода, который вы хотите
повторять. Теперь, когда мы вызываем _create_alien(), мы
включаем аргумент для номера строки, чтобы каждую строку можно было разместить дальше
вниз по экрану.
В определении функции _create_alien() требуется параметр для ввода номера строки.
номер. В функции _create_alien() мы изменяем значение координаты y пришельца.
если он не находится в первом ряду, начиная с высоты одного пришельца, чтобы создать
пустое пространство в верхней части экрана. Каждый ряд начинается на две высоты пришельца ниже
предыдущего ряда, поэтому мы умножаем высоту пришельца на два, а затем на номер ряда.
номер. Номер первого ряда равен 0, поэтому вертикальное расположение первого ряда
остается неизменным. Все последующие ряды располагаются дальше по экрану.
Когда вы запустите игру, вы должны увидеть полный флот пришельцев.
Заставляем флот двигаться
Теперь давайте заставим флот пришельцев двигаться вправо по экрану до тех пор.
пока он не достигнет края, а затем заставим его опуститься на заданную величину и двигаться в другом
направлении. Мы будем продолжать это движение до тех пор, пока либо все пришельцы не будут сбиты, либо
либо один столкнется с кораблем, либо один достигнет нижней части экрана. Давайте
начнем с того, что заставим флот двигаться вправо.
Перемещение пришельцев вправо
Для перемещения пришельцев мы воспользуемся методом update() в alien.py, который мы
вызов для каждого инопланетянина в группе инопланетян. Сначала добавьте параметр для управления
скорость каждого пришельца:
#settings.py
def __init__(self):
--snip--
# Alien settings
self.alien_speed = 1.0
Затем используйте эту настройку для реализации функции update():
#alien.py
def __init__(self, ai_game):
"""Initialize the alien and set its starting position."""
super().__init__()
self.screen = ai_game.screen
self.settings = ai_game.settings
--snip--
def update(self):
"""Move the alien to the right."""
self.x += self.settings.alien_speed
self.rect.x = self.x
Мы создаем параметр настройки в init(), чтобы иметь доступ к скорости пришельца в update().
скорость в update(). Каждый раз, когда мы обновляем позицию пришельца, мы перемещаем его вправо на
вправо на величину, хранящуюся в параметре alien_speed. Мы отслеживаем точную позицию пришельца
с помощью атрибута self.x, который может хранить десятичные значения. Затем мы используем
значение self.x для обновления положения прямоугольника пришельца.
В основном цикле while у нас уже есть вызовы для обновления позиций корабля и
позиции пули. Теперь мы добавим вызов для обновления позиции каждого пришельца
также:
#alien_invasion.py
while True:
self._check_events()
self.ship.update()
self._update_bullets()
self._update_aliens()
self._update_screen()
Мы собираемся написать код для управления движением флота,
поэтому мы создадим новый метод _update_aliens(). Мы установим, что позиции пришельцев
позиции, чтобы они обновлялись после обновления пуль, потому что вскоре мы будем
проверять, попали ли пули в инопланетян.
Место расположения этого метода в модуле не критично. Но чтобы
чтобы код был организован, мы поместим его сразу после _update_bullets(), чтобы соответствовать
порядку вызова методов в цикле while. Вот первая версия
_update_aliens():
#alien_invasion.py
def _update_aliens(self):
"""Update the positions of all aliens in the fleet."""
self.aliens.update()
Мы используем метод update() для группы пришельцев, который вызывает метод каждого
метод update() каждого пришельца. Когда вы запустите игру Alien Invasion, вы должны увидеть, как
что флот движется вправо и исчезает за пределами экрана.
Создание настроек для направления флота
Теперь мы создадим настройки, которые заставят флот двигаться вниз по экрану
и влево, когда он достигнет правого края экрана. Вот как реализовать
ментировать это поведение:
#settings.py
# Alien settings
self.alien_speed = 1.0
self.fleet_drop_speed = 10
# fleet_direction of 1 represents right; -1 represents left.
self.fleet_direction = 1
Параметр fleet_drop_speed управляет тем, как быстро флот опускается вниз
экрана каждый раз, когда пришелец достигает любого края. Полезно отделить
эту скорость от горизонтальной скорости пришельцев, чтобы можно было регулировать эти два параметра
скорости независимо друг от друга.
Для реализации настройки fleet_direction мы можем использовать текстовое значение, такое как
например, ‘left’ или ‘right’, но в итоге мы получили бы операторы if-elif, проверяющие направление флота.
направление флота. Вместо этого, поскольку у нас есть только два направления,
давайте использовать значения 1 и -1, и переключаться между ними каждый раз, когда флот
меняет направление. (Использование чисел также имеет смысл, потому что движение вправо
означает прибавление к значению x-координаты каждого пришельца, а перемещение влево означает
вычитание из значения x-координаты каждого пришельца).
Проверка того, попал ли инопланетянин на край
Нам нужен метод для проверки того, находится ли инопланетянин на любом из краев, и нам нужно
модифицировать update(), чтобы позволить каждому пришельцу двигаться в соответствующем направлении.
Этот код является частью класса Alien:
#alien.py
def check_edges(self):
"""Return True if alien is at edge of screen."""
screen_rect = self.screen.get_rect()
if self.rect.right >= screen_rect.right or self.rect.left <= 0:
return True
def update(self):
"""Move the alien right or left."""
self.x += (self.settings.alien_speed *
self.settings.fleet_direction)
self.rect.x = self.x
Мы можем вызвать новый метод check_edges() для любого пришельца, чтобы узнать, находится ли он
находится ли он у левого или правого края. Пришелец находится у правого края, если правый атрибут его прямоугольника больше или равен правому атрибуту прямоугольника экрана.
Он находится у левого края, если его левое значение меньше или равно 0.
Мы модифицируем метод update(), чтобы разрешить движение влево или вправо
умножив скорость пришельца на значение параметра fleet_direction. Если fleet
_direction равно 1, значение alien_speed будет добавлено к текущему положению пришельца.
перемещая пришельца вправо; если значение параметра fleet_direction равно -1, то значение
будет вычтено из позиции пришельца, перемещая его влево.
Сброс флота и изменение направления движения
Когда пришелец достигает края, весь флот должен опуститься и
изменить направление. Поэтому нам нужно добавить некоторый код в AlienInvasion
потому что именно здесь мы будем проверять, находятся ли пришельцы на левом или правом
краю. Мы сделаем это, написав методы _check_fleet_edges()
и _change_fleet_direction(), а затем изменим _update_aliens().
#alien_invasion.py
def _check_fleet_edges(self):
"""Respond appropriately if any aliens have reached an edge."""
for alien in self.aliens.sprites():
if alien.check_edges():
self._change_fleet_direction()
break
def _change_fleet_direction(self):
"""Drop the entire fleet and change the fleet's direction."""
for alien in self.aliens.sprites():
alien.rect.y += self.settings.fleet_drop_speed
self.settings.fleet_direction *= -1
В _check_fleet_edges() мы проходим по флоту и вызываем check_edges()
для каждого пришельца. Если check_edges() возвращает True, мы знаем, что пришелец находится на
и весь флот должен изменить направление; поэтому мы вызываем _change_fleet
_direction() и выходим из цикла v. В _change_fleet_direction() мы
перебираем всех пришельцев и сбрасываем каждого из них, используя параметр fleet_drop
_speed; затем мы изменяем значение параметра fleet_direction, умножая его текущее значение на -1.
реннее значение на -1. Строка, изменяющая направление флота, не является частью цикла
цикла for. Мы хотим изменить вертикальное положение каждого инопланетянина, но мы хотим только
изменить направление флота только один раз.
меняется на _update_aliens():
#alien_invasion.py
def _update_aliens(self):
"""
Check if the fleet is at an edge,
then update the positions of all aliens in the fleet.
"""
self._check_fleet_edges()
self.aliens.update()
Мы изменили метод, вызвав _check_fleet_edges() перед тем, как
обновления позиции каждого пришельца.
Когда вы запускаете игру сейчас, флот должен двигаться вперед и назад
между краями экрана и падать вниз каждый раз, когда он ударяется о край.
Теперь мы можем начать отстреливать инопланетян и следить за всеми пришельцами, которые врезаются в корабль или достигают нижней части экрана.
корабль или достигают нижней части экрана.
Стрельба по пришельцам
Мы построили наш корабль и флот инопланетян, но когда пули достигают
пришельцев, они просто проходят мимо, потому что мы не проверяем столкновения. В
игровом программировании столкновения происходят, когда игровые элементы накладываются друг на друга. Чтобы заставить
пули сбивают пришельцев, мы используем метод sprite.groupcollide(), чтобы
для поиска столкновений между членами двух групп.
Обнаружение столкновений пуль
Мы хотим сразу же узнать, когда пуля попадает в пришельца, чтобы мы могли заставить пришельца
пришельца исчезнуть, как только в него попадет пуля. Для этого мы будем искать столкновения сразу после обновления положения всех пуль.
Функция sprite.groupcollide() сравнивает прямоугольники каждого элемента
в одной группе с прямоугольниками каждого элемента в другой группе. В данном случае
она сравнивает прямоугольник каждой пули с прямоугольником каждого инопланетянина и возвращает вычис-
арий, содержащий пули и инопланетян, которые столкнулись. Каждый ключ в
словаря будет пуля, а соответствующее значение — инопланетянин, который
был поражен.
Добавьте следующий код в конец _update_bullets() для проверки столкновений между пулями и пришельцами:
#alien_invasion.py
def _update_bullets(self):
"""Update position of bullets and get rid of old bullets."""
--snip--
# Check for any bullets that have hit aliens.
#If so, get rid of the bullet and the alien.
collisions = pygame.sprite.groupcollide(
self.bullets, self.aliens, True, True)
Новый код, который мы добавили, сравнивает положение всех пуль в файле
self.bullets и всех пришельцев в self.aliens, и определяет все, которые пересекаются.
Когда прямоугольники пули и пришельца пересекаются, groupcollide() добавляет пару ключ-значение в словарь, который возвращает.
пара значений в возвращаемый словарь. Два аргумента True указывают Pygame
удалить пули и инопланетян, которые столкнулись. (Чтобы сделать мощную
пулю, которая может долететь до верха экрана, уничтожая всех пришельцев на своем пути.
можно установить первый булев аргумент в False, а второй оставить в True.
булевский аргумент в значение True. Сбитые пришельцы исчезнут, но все пули
останутся активными, пока не исчезнут с экрана).
Теперь, когда вы запускаете Alien Invasion, подбитые вами инопланетяне должны исчезнуть.
Заселение флота
Одна из ключевых особенностей игры Alien Invasion заключается в том, что пришельцы неумолимы: каждый раз, когда
когда флот уничтожается, появляется новый.
Чтобы заставить новый флот пришельцев появиться после уничтожения флота,
мы сначала проверяем, пуста ли группа пришельцев. Если она пуста, мы вызываем
_create_fleet(). Мы выполним эту проверку в конце _update_bullets(),
потому что именно там уничтожаются отдельные пришельцы.
#alien_invasion.py
def _update_bullets(self):
--snip--
if not self.aliens:
# Destroy existing bullets and create new fleet.
self.bullets.empty()
self._create_fleet()
Мы проверяем, пуста ли группа пришельцев. Пустая группа оценивается как False, поэтому это простой способ проверить, пуста ли группа.
Если она пуста, мы избавимся от всех существующих пуль с помощью метода empty(), который
который удаляет все оставшиеся спрайты из группы. Мы также вызываем _create
_fleet(), который снова заполняет экран пришельцами.
Теперь новый флот появляется, как только вы уничтожаете текущий флот.
Ускорение пуль
Если вы пробовали стрелять по пришельцам в текущем состоянии игры, вы можете обнаружить.
что пули летят не с оптимальной для игры скоростью. Они могут
быть немного медленными на вашей системе или слишком быстрыми. В этот момент вы можете изменить
настройки, чтобы сделать игровой процесс интересным и приятным на вашей системе.
Мы изменяем скорость пуль, регулируя значение bullet_speed
в файле settings.py. На моей системе я установлю значение bullet_speed на 1,5, чтобы
пули летят немного быстрее:
#settings.py
# Bullet settings
self.bullet_speed = 1.5
self.bullet_width = 3
--snip--
Лучшее значение этого параметра зависит от скорости вашей системы, поэтому найдите значение, которое вам подходит.
значение, которое подходит именно вам. Вы можете настроить и другие параметры.
Рефакторинг _update_bullets()
Давайте рефакторим _update_bullets(), чтобы она не выполняла так много различных задач.
Мы перенесем код для обработки столкновений пуль и пришельцев в отдельный метод.
метод:
#alien_invasion.py
def _update_bullets(self):
--snip--
# Get rid of bullets that have disappeared.
for bullet in self.bullets.copy():
if bullet.rect.bottom <= 0:
self.bullets.remove(bullet)
self._check_bullet_alien_collisions()
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 not self.aliens:
# Destroy existing bullets and create new fleet.
self.bullets.empty()
self._create_fleet()
Мы создали новый метод, _check_bullet_alien_collisions(), для поиска
столкновений между пулями и инопланетянами, и реагировать соответствующим образом, если
весь флот был уничтожен. Благодаря этому функция _update_bullets() не будет
слишком длинным и упрощает дальнейшую разработку.
Завершение игры
Что интересного и сложного в игре, если вы не можете проиграть? Если игрок
не собьет флот достаточно быстро, мы заставим пришельцев уничтожить
корабль, когда они вступят в контакт. В то же время, мы ограничим количество
кораблей, которые может использовать игрок, и мы уничтожим корабль, когда пришелец достигнет
нижней части экрана. Игра закончится, когда игрок израсходует
все свои корабли.
Обнаружение столкновений пришельцев и кораблей
Начнем с проверки столкновений между пришельцами и кораблем, чтобы мы могли
чтобы правильно отреагировать на столкновение с кораблем. Мы проверим наличие инопланетян и
столкновения кораблей сразу после обновления позиции каждого пришельца в
AlienInvasion:
#alien_invasion.py
def _update_aliens(self):
--snip--
self.aliens.update()
# Look for alien-ship collisions.
if pygame.sprite.spritecollideany(self.ship, self.aliens):
print("Ship hit!!!")
Функция spritecollideany() принимает два аргумента: спрайт и
группа. Функция ищет любого члена группы, который столкнулся
со спрайтом и останавливает цикл по группе, как только находит одного
члена группы, который столкнулся со спрайтом. В данном случае она проходит по группе
aliens и возвращает первого найденного инопланетянина, который столкнулся с кораблем.
Если столкновений не произошло, spritecollideany() возвращает None, а блок if
не будет выполняться. Если функция находит инопланетянина, который столкнулся с кораблем, она
возвращает его, и блок if выполняется: выводится Ship hit!!!. Когда
инопланетянин врезается в корабль, нам нужно будет выполнить ряд задач: удалить
все оставшиеся инопланетяне и пули, изменить ориентацию корабля и создать новый флот.
Прежде чем мы напишем код для выполнения всего этого, нам нужно знать, что наш подход для
обнаружения столкновений пришельцев и кораблей работает правильно. Написание вызова print() — это
простой способ убедиться, что мы правильно определяем эти столкновения.
Теперь, когда вы запускаете Alien Invasion, сообщение Ship hit!!! должно появляться
в терминале всякий раз, когда пришелец сталкивается с кораблем. Когда вы тестируете
эту функцию, установите для параметра alien_drop_speed более высокое значение, например, 50 или 100, чтобы пришельцы быстрее
инопланетяне настигают ваш корабль быстрее.
Реагирование на столкновения пришельцев и кораблей
Теперь нам нужно выяснить, что именно произойдет, когда инопланетянин столкнется
с кораблем. Вместо того чтобы уничтожать экземпляр корабля и создавать новый
мы будем подсчитывать, сколько раз корабль столкнулся, отслеживая статистику
для игры. Статистика отслеживания также будет полезна для подсчета очков.
Давайте напишем новый класс, GameStats, для отслеживания статистики игры, и сохраним его под именем
game_stats.py:
#game_stats.py
class GameStats:
"""Track statistics for Alien Invasion."""
def __init__(self, ai_game):
"""Initialize statistics."""
self.settings = ai_game.settings
self.reset_stats()
def reset_stats(self):
"""Initialize statistics that can change during the game."""
self.ships_left = self.settings.ship_limit
Мы создадим один экземпляр GameStats на все время работы Alien Invasion
работает. Но нам нужно будет сбрасывать некоторые статистические данные каждый раз, когда игрок начинает
новую игру. Для этого мы инициализируем большинство статистических данных в методе reset
stats(), а не непосредственно в методе __init(). Мы вызовем этот метод
из __init_(), чтобы статистика была правильно установлена, когда экземпляр GameStats
создается впервые. Но мы также сможем вызывать reset_stats() в любое время, когда
игрок начинает новую игру.
Сейчас у нас есть только одна статистика, ships_left, значение которой будет
меняться на протяжении всей игры. Количество кораблей, с которыми игрок начинает игру
должно быть сохранено в settings.py как ship_limit:
#settings.py
# Ship settings
self.ship_speed = 1.5
self.ship_limit = 3
Нам также нужно сделать несколько изменений в файле alien_invasion.py для создания
экземпляр GameStats. Во-первых, мы обновим утверждения импорта в верхней части
файла:
#alien_invasion.py
import sys
from time import sleep
import pygame
from settings import Settings
from game_stats import GameStats
from ship import Ship
--snip--
Мы импортируем функцию sleep() из модуля time стандартной библиотеки Python
стандартной библиотеки, чтобы мы могли приостановить игру на мгновение, когда корабль будет
подбит. Мы также импортируем GameStats.
Мы создадим экземпляр GameStats в init():
alien_invasion.py
def __init__(self):
--snip--
self.screen = pygame.display.set_mode(
(self.settings.screen_width, self.settings.screen_height))
pygame.display.set_caption("Alien Invasion")
# Create an instance to store game statistics.
self.stats = GameStats(self)
self.ship = Ship(self)
--snip--
Мы создаем экземпляр после создания игрового окна, но до определения
других элементов игры, таких как корабль.
Когда инопланетянин попадает на корабль, мы вычитаем единицу из количества
кораблей, уничтожим всех существующих пришельцев и пули, создадим новый флот и
переместим корабль в центр экрана. Мы также приостановим игру
на мгновение, чтобы игрок мог заметить столкновение и перегруппироваться, прежде чем
появления нового флота.
Давайте поместим большую часть этого кода в новый метод _ship_hit(). Мы будем вызывать
этот метод из _update_aliens(), когда инопланетянин ударит корабль:
#alien_invasion.py
def _ship_hit(self):
"""Respond to the ship being hit by an alien."""
# Decrement ships_left.
self.stats.ships_left -= 1
# 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()
# Pause.
sleep(0.5)
Новый метод _ship_hit() координирует реакцию, когда пришелец
попадает в корабль. Внутри _ship_hit() количество оставшихся кораблей уменьшается на 1,
после чего мы очищаем группы от пришельцев и пуль.
Далее мы создаем новый флот и центрируем корабль. (Мы добавим
метод center_ship() к Ship в ближайшее время.) Затем мы добавляем паузу после того, как
обновления всех элементов игры, но до того, как все изменения
нарисованы на экране, чтобы игрок мог увидеть, что его корабль
был подбит. Вызов sleep() приостанавливает выполнение программы на полсекунды,
достаточно, чтобы игрок увидел, что инопланетянин врезался в корабль. Когда
функция sleep() завершается, выполнение кода переходит к методу _update_screen()
который рисует новый флот на экране.
В _update_aliens() мы заменяем вызов print() вызовом _ship_hit()
когда инопланетянин попадает в корабль:
#alien_invasion.py
def _update_aliens(self):
--snip--
if pygame.sprite.spritecollideany(self.ship, self.aliens):
self._ship_hit()
Вот новый метод center_ship(); добавьте его в конец файла ship.py:
#ship.py
def center_ship(self):
"""Center the ship on the screen."""
self.rect.midbottom = self.screen_rect.midbottom
self.x = float(self.rect.x)
Мы центрируем корабль так же, как и в init(). После центрирования
мы сбрасываем атрибут self.x, что позволяет нам отслеживать точную
положение.
Запустите игру, выстрелите в нескольких пришельцев и дайте пришельцу врезаться в корабль. Сайт
игра приостановится, и появится новый флот с кораблем в центре
в нижней части экрана.
Пришельцы, которые достигают нижней части экрана
Если инопланетянин достигнет нижней части экрана, мы попросим игру отреагировать так же.
так же, как это происходит, когда инопланетянин попадает в корабль. Чтобы проверить, когда это произойдет
добавьте новый метод в файле alien_invasion.py:
#alien_invasion.py
def _check_aliens_bottom(self):
"""Check if any aliens have reached the bottom of the screen."""
screen_rect = self.screen.get_rect()
for alien in self.aliens.sprites():
if alien.rect.bottom >= screen_rect.bottom:
# Treat this the same as if the ship got hit.
self._ship_hit()
break
Метод _check_aliens_bottom() проверяет, достигли ли пришельцы
достигли нижней части экрана. Пришелец достигает дна, если его
значение rect.bottom больше или равно атрибуту rect.bottom экрана.
ности. Если пришелец достиг дна, мы вызываем _ship_hit(). Если один пришелец достигнет
дно, нет необходимости проверять остальных, поэтому мы выходим из цикла
после вызова _ship_hit().
Мы будем вызывать этот метод из _update_aliens():
#alien_invasion.py
def _update_aliens(self):
--snip--
# Look for alien-ship collisions.
if pygame.sprite.spritecollideany(self.ship, self.aliens):
self._ship_hit()
# Look for aliens hitting the bottom of the screen.
self._check_aliens_bottom()
Мы вызываем _check_aliens_bottom() после обновления позиций всех
пришельцев и после поиска столкновений пришельцев и кораблей. Теперь новый флот будет
появляться каждый раз, когда корабль столкнется с пришельцем или пришелец достигнет нижней части
экрана.
Игра окончена!
Теперь Alien Invasion кажется более завершенной, но игра никогда не закончится. Значение
ships_left просто становится все более отрицательным. Давайте добавим флаг game_active в качестве
атрибута GameStats, чтобы завершить игру, когда у игрока закончатся корабли.
Мы установим этот флаг в конце метода init() в GameStats:
#game_stats.py
def __init__(self, ai_game):
--snip--
# Start Alien Invasion in an active state.
self.game_active = True
Теперь мы добавим код в _ship_hit(), который установит game_active в False, когда
игрок израсходовал все свои корабли:
#alien_invasion.py
def _ship_hit(self):
"""Respond to ship being hit by alien."""
if self.stats.ships_left > 0:
# Decrement ships_left.
self.stats.ships_left -= 1
--snip--
# Pause.
sleep(0.5)
else:
self.stats.game_active = False
Большая часть _ship_hit() осталась неизменной. Мы перенесли весь существующий код
в блок if, который проверяет, есть ли у игрока хотя бы один корабль.
остался. Если да, мы создаем новый флот, делаем паузу и двигаемся дальше. Если у игрока
не осталось ни одного корабля, мы устанавливаем game_active в False.
Определение времени выполнения частей игры
Нам нужно определить, какие части игры должны выполняться всегда, а какие
части, которые должны запускаться только при активной игре:
#alien_invasion.py
def run_game(self):
"""Start the main loop for the game."""
while True:
self._check_events()
if self.stats.game_active:
self.ship.update()
self._update_bullets()
self._update_aliens()
self._update_screen()
В главном цикле нам всегда нужно вызывать _check_events(), даже если
игра неактивна. Например, нам все равно нужно знать, нажмет ли пользователь кнопку Q, чтобы
для выхода из игры или нажал на кнопку, чтобы закрыть окно. Мы также продолжаем
обновлять экран, поэтому мы можем вносить изменения на экране, ожидая, пока
посмотреть, решит ли игрок начать новую игру. Остальные функции
должны вызываться только тогда, когда игра активна, потому что когда игра
неактивна, нам не нужно обновлять положение игровых элементов.
Теперь, когда вы играете в Alien Invasion, игра должна замирать, когда вы
когда вы израсходуете все свои корабли.
В следующей, заключительной части мы рассмотрим, как добавить в нашу игру подсчет очков.
Чтобы получить доступ к полному исходному коду проекта и файлам, посетите репозиторий на Github.