Изучаем Python Эрик Метиз. Crash course2 n d e d i t i o na h a n d s o n, p r o j e c t b a s e d i n t r o d u c t i o n t o p r o g r a m m i n g
Скачать 6.19 Mb.
|
273 пришельца, равно его удвоенной ширине: одна ширина для самого пришельца и еще одна для пустого интервала справа. Чтобы определить количество пришельцев на экране, разделим доступное пространство на удвоенную ширину пришельца. При этом будет использоваться целочисленное деление // с потерей остатка, чтобы полу- ченное количество пришельцев было целым: number_aliens_x = available_space_x // (2 * alien_width) Эти вычисления будут включены в программу при создании флота. ПРИМЕЧАНИЕ У вычислений в программировании есть одна замечательная особен- ность: не обязательно быть полностью уверенными в правильности формулы, когда вы ее пишете . Вы можете опробовать формулу на практике и посмотреть, что из этого полу- чится . В худшем случае получится экран, до отказа забитый пришельцами или, наоборот, пустой . В этом случае вы пересмотрите формулу на основании полученных результатов . Создание ряда Все готово к тому, чтобы сгенерировать полный ряд пришельцев. Так как наш код создания одного пришельца работает правильно, мы перепишем _create_fleet() для создания ряда пришельцев: alien_invasion.py def _create_fleet(self): """Создает флот пришельцев.""" # Создание пришельца и вычисление количества пришельцев в ряду # Интервал между соседними пришельцами равен ширине пришельца. ❶ 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) # Создание первого ряда пришельцев. ❹ for alien_number in range(number_aliens_x): # Создание пришельца и размещение его в ряду. alien = Alien(self) ❺ alien.x = alien_width + 2 * alien_width * alien_number alien.rect.x = alien.x self.aliens.add(alien) Большая часть этого кода уже была описана ранее. Для размещения пришельцев необходимо знать ширину и высоту одного пришельца, и мы создаем его в точке перед выполнением вычислений. Этот пришелец не войдет во флот, поэтому он не включается в группу aliens . В точке ширина пришельца определяется по его атрибуту rect , а полученное значение сохраняется в alien_width , чтобы избежать лишних обращений к атрибуту rect . В точке вычисляется доступное горизон- тальное пространство и количество пришельцев, которые в нем поместятся. Затем создается цикл от 0 до количества создаваемых пришельцев . В теле цикла создается новый пришелец, после чего задается его координата x для размещения 274 Глава 13 • Осторожно, пришельцы! его в ряду . Каждый пришелец сдвигается вправо на одну ширину от левого поля. Затем ширина пришельца умножается на 2, чтобы учесть полное пространство, вы- деленное для одного пришельца, включая пустой интервал справа, а полученная величина умножается на позицию пришельца в ряду. Атрибут x пришельца исполь- зуется для назначения позиции его прямоугольника. После этого новый пришелец добавляется в группу aliens Запустив программу Alien Invasion, вы увидите, что на экране появился первый ряд пришельцев (рис. 13.3). Рис. 13.3. Первый ряд пришельцев Первый ряд сдвинут влево, и это полезно для игрового процесса, потому что флот пришельцев должен двигаться вправо, пока не дойдет до края экрана, затем немно- го опуститься вниз, затем двигаться влево и т. д. Как и в классической игре Space Invaders, такое перемещение интереснее, чем постепенное снижение по прямой. Движение будет продолжаться до тех пор, пока все пришельцы не будут сбиты или пока пришелец не столкнется с кораблем либо нижним краем экрана. ПРИМЕЧАНИЕ В зависимости от выбранной ширины экрана расположение первого ряда пришельцев в вашей системе может выглядеть немного иначе . Рефакторинг _create_fleet() Если бы создание флота на этом было завершено, то функцию _create_fleet() , пожалуй, можно было бы оставить в таком виде, но работа еще не закончена, по- Построение флота 275 этому мы немного подчистим код функции. Добавим новый вспомогательный метод _create_alien() и вызовем его из _create_fleet() : alien_invasion.py def _create_fleet(self): # Создание первого ряда пришельцев. for alien_number in range(number_aliens_x): self._create_alien(alien_number) def _create_alien(self, alien_number): """Создание пришельца и размещение его в ряду.""" 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): 276 Глава 13 • Осторожно, пришельцы! 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) """Определяет количество рядов, помещающихся на экране.""" 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) # Создание флота вторжения. ❸ 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): """Создание пришельца и размещение его в ряду.""" 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 . Чтобы вычислить количество рядов, помещающихся на экране, мы включаем вычисление available_space_y сразу же после вычис- ления available_space_x . Вычисления заключаются в круглые скобки, чтобы их можно было разбить на две строки длиной 79 символов и менее, как указано в рекомендациях. Чтобы создать несколько рядов, мы используем два вложенных цикла: внешний и внутренний . Внутренний цикл создает один ряд пришельцев. Внешний цикл считает от 0 до количества рядов; Python использует код создания одного ряда и повторяет его number_rows раз. Чтобы создать вложенный цикл, напишите новый цикл for и снабдите повторя- емый код отступом. (В большинстве текстовых редакторов операции создания и удаления блоков кода выполняются просто, но если вам понадобится помощь, обращайтесь к приложению Б.) Затем при вызове _create_alien() передается ар- гумент с номером ряда, чтобы каждый ряд находился на экране ниже предыдущих. Определению _create_alien() необходим параметр с номером ряда. В _create_ alien() мы изменяем координату y пришельца, если он не находится в первом ряду . Сначала прибавляется одна высота пришельца, чтобы создать пустое место у верхнего края экрана. Каждый новый ряд начинается на две высоты пришельца ниже последнего ряда, поэтому мы умножаем высоту пришельца на 2, а затем на но- мер ряда. Номер первого ряда равен 0, так что вертикальное расположение первого ряда остается неизменным. Все последующие ряды размещаются ниже на экране. Перемещение флота 277 Если теперь запустить игру, вы увидите целый флот пришельцев (рис. 13.4). Рис. 13.4. На экране появился весь флот пришельцев В следующем разделе мы приведем флот в движение. УПРАЖНЕНИЯ 13.1. Звезды: найдите изображение звезды. Создайте на экране сетку из звезд. 13.2. Звезды-2: чтобы звезды выглядели более реалистично, следует внести случайное от- клонение при размещении звезд. Вспомните, что случайные числа генерируются следую- щим образом: from random import randint random_number = randint(-10,10) Этот код возвращает случайное целое число в диапазоне от −10 до 10. Используя свой код из упражнения 13.1, измените позицию каждой звезды на случайную величину. Перемещение флота Флот пришельцев должен двигаться вправо по экрану, пока не дойдет до края; тогда флот опускается на заданную величину и начинает двигаться в обратном направлении. Это продолжается до тех пор, пока все пришельцы не будут сбиты, один из них столкнется с кораблем или не достигнет низа экрана. Начнем с пере- мещения флота вправо. 278 Глава 13 • Осторожно, пришельцы! Перемещение вправо Чтобы корабли пришельцев перемещались по экрану, мы воспользуемся методом update() из alien .py , который будет вызываться для каждого пришельца в группе. Сначала добавим настройку для управления скоростью каждого пришельца: settings.py def __init__(self): # Настройки пришельцев self.alien_speed = 1.0 Настройка используется в реализации update() : alien.py def __init__(self, ai_game): """Инициализирует пришельца и задает его начальную позицию.""" super().__init__() self.screen = ai_game.screen self.settings = ai_game.settings def update(self): """Перемещает пришельца вправо.""" ❶ self.x += self.settings.alien_speed ❷ self.rect.x = self.x Параметр settings создается в __init__() , чтобы к скорости пришельца можно было обратиться в 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() . Позиции пришельцев обновляются после об- новления снарядов, потому что скоро мы будем проверять, попали ли какие-либо снаряды в пришельцев. Перемещение флота 279 Местоположение этого метода в модуле некритично. Но для улучшения структуры кода мы разместим его сразу же после _update_bullets() в соответствии с поряд- ком вызова методов в цикле while . Первая версия _update_aliens() выглядит так: alien_invasion.py def _update_aliens(self): """Обновляет позиции всех пришельцев во флоте.""" self.aliens.update() Мы используем метод update() для группы aliens , что приводит к автоматиче- скому вызову метода update() каждого пришельца. Если запустить Alien Invasion сейчас, вы увидите, как флот двигается вправо и исчезает за краем экрана. Создание настроек для направления флота Теперь мы создадим настройки, которые перемещают флот вниз по экрану, а потом влево при достижении правого края экрана. Вот как реализуется это поведение: settings.py # Настройки пришельцев self.alien_speed = 1.0 self.fleet_drop_speed = 10 # fleet_direction = 1 обозначает движение вправо; а -1 - влево. self.fleet_direction = 1 Настройка fleet_drop_speed управляет величиной снижения флота при дости- жении им края. Эту скорость полезно отделить от горизонтальной скорости при- шельцев, чтобы эти две скорости можно было изменять независимо. Для настройки fleet_direction можно использовать текстовое значение (напри- мер, 'left' или 'right' ), но скорее всего, в итоге придется использовать набор команд if - elif для проверки направления. Так как в данном случае направлений всего два, мы используем значения 1 и –1 и будем переключаться между ними при каждом изменении направления флота. (Числа в данном случае особенно удобны, потому что при движении вправо координата x каждого пришельца должна увели- чиваться, а при перемещении влево — уменьшаться.) Проверка достижения края Также нам понадобится метод для проверки того, достиг ли пришелец одного из двух краев. Для этого необходимо внести в метод update() изменение, позволяющее каждому пришельцу двигаться в соответствующем направлении. Этот код является частью класса Alien : alien.py def check_edges(self): """Возвращает True, если пришелец находится у края экрана.""" 280 Глава 13 • Осторожно, пришельцы! screen_rect = self.screen.get_rect() ❶ if self.rect.right >= screen_rect.right or self.rect.left <= 0: return True def update(self): """Перемещает пришельца влево или вправо.""" ❷ self.x += (self.settings.alien_speed * self.settings.fleet_direction) self.rect.x = self.x Вызов нового метода check_edges() для любого пришельца позволяет проверить, достиг ли он левого или правого края. У пришельца, находящегося у правого края, атрибут right его атрибута rect больше или равен атрибуту right атрибута rect экра- на. У пришельца, находящегося у левого края, значение left меньше либо равно 0 . В метод update() будут внесены изменения, обеспечивающие перемещение влево и вправо; для этого скорость пришельца умножается на значение fleet_ direction . Если значение fleet_direction равно 1, то значение alien_speed прибавляется к текущей позиции пришельца и пришелец перемещается вправо; если же значение fleet_direction равно –1, то значение вычитается из позиции пришельца (который перемещается влево). Снижение флота и смена направления Когда пришелец доходит до края, весь флот должен опуститься вниз и изменить направление движения. Это означает, что в AlienInvasion придется внести из- менения, потому что именно здесь программа проверяет, достиг ли какой-либо пришелец левого или правого края. Для этого мы напишем функции _check_fleet_ edges() и _change_fleet_direction() , а затем изменим _update_aliens() . Новые методы будут располагаться после _create_alien() , но я еще раз подчеркну, что конкретное размещение этих методов в классе несущественно. alien_invasion.py def _check_fleet_edges(self): """Реагирует на достижение пришельцем края экрана.""" ❶ for alien in self.aliens.sprites(): if alien.check_edges(): ❷ self.change_fleet_direction() break def _change_fleet_direction(self): """Опускает весь флот и меняет направление флота.""" 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 , значит, пришелец на- ходится у края и весь флот должен сменить направление, поэтому вызывается Уничтожение пришельцев 281 функция _change_fleet_direction() и происходит выход из цикла . Функция _change_fleet_direction() перебирает пришельцев и уменьшает высоту каждо- го из них с использованием настройки fleet_drop_speed ; затем направление fleet_direction меняется на противоположное, для чего текущее значение умно- жается на –1. Строка, изменяющая направление, не является частью цикла for Вертикальная позиция должна изменяться для каждого пришельца, но направление всего флота должно измениться однократно. Изменения в _update_aliens() : alien_invasion.py def _update_aliens(self): """ Проверяет, достиг ли флот края экрана, с последующим обновлением позиций всех пришельцев во флоте. """ self._check_fleet_edges() self.aliens.update() Перед обновлением позиции каждого пришельца будет вызываться метод _check_ fleet_edges() Если запустить игру сейчас, флот будет двигаться влево-вправо между краями экрана и опускаться каждый раз, когда он доберется до края. Теперь можно пере- ходить к реализации уничтожения пришельцев и отслеживания пришельцев, стал- кивающихся с кораблем или достигающих нижнего края экрана. УПРАЖНЕНИЯ 13.3. Капли: найдите изображение дождевой капли и создайте сетку из капель. Капли должны постепенно опускаться вниз и исчезать у нижнего края экрана. 13.4. Дождь: измените свой код в упражнении 13.3, чтобы при исчезновении ряда капель у нижнего края экрана новый ряд появлялся у верхнего края и начинал свое падение. Уничтожение пришельцев Итак, мы создали корабль и флот пришельцев, но когда снаряды достигают при- шельцев, они просто проходят насквозь, потому что программа не проверяет кол- лизии. В игровом программировании коллизией называется перекрытие игровых элементов. Чтобы снаряды сбивали пришельцев, метод sprite.groupcollide() используется для выявления коллизий между элементами двух групп. Выявление коллизий Когда снаряд попадает в пришельца, программа должна немедленно узнать об этом, чтобы сбитый пришелец исчез с экрана. Для этого мы будем проверять коллизии сразу же после обновления позиции снаряда. |