Книга Изучаем Python
Скачать 4.68 Mb.
|
264 Глава 13 • Осторожно, пришельцы! def create_alien(ai_settings, screen, aliens, alien_number): """Создает пришельца и размещает его в ряду.""" alien = Alien(ai_settings, screen) alien_width = alien.rect.width alien.x = alien_width + 2 * alien_width * alien_number alien.rect.x = alien.x aliens.add(alien) def create_fleet(ai_settings, screen, aliens): """Создает флот пришельцев.""" # Создание пришельца и вычисление количества пришельцев в ряду. alien = Alien(ai_settings, screen) number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width) # Создание первого ряда пришельцев. for alien_number in range(number_aliens_x): create_alien(ai_settings, screen, aliens, alien_number) Код get_number_aliens_x() нисколько не изменился по сравнению с create_ fleet() . Код create_alien() почти не изменился, разве что для определения ши- рины используется только что созданный пришелец . В точке код вычисления горизонтальных интервалов заменяется вызовом get_number_aliens_x() , а строка с alien_width удалена, потому что теперь задача решается в create_alien() . В точ- ке вызывается функция create_alien() . Рефакторинг упрощает добавление новых строк и создание всего флота. Добавление рядов Чтобы завершить построение флота, определите количество рядов на экране и повторите цикл (создания пришельцев одного ряда) полученное количество раз. Чтобы определить количество рядов, мы вычисляем доступное вертикаль- ное пространство, вычитая высоту пришельца (сверху), высоту корабля (снизу) и удвоенную высоту пришельца (снизу): available_space_y = ai_settings.screen_height — 3 * alien_height — ship_height В результате вокруг корабля образуется пустое пространство, чтобы у игрока было время начать стрельбу по пришельцам в начале каждого уровня. Под каждым рядом должно быть пустое место, равное высоте пришельца. Чтобы вычислить количество строк, мы делим свободное пространство на удвоенную высоту пришельца (как и прежде, если формула содержит ошибку, мы это немедленно увидим и внесем изменения, пока не получим нужные интервалы): number_rows = available_height_y / (2 * alien_height) Зная количество рядов во флоте, мы можем повторить код создания ряда: game_functions.py def get_number_rows(ai_settings, ship_height, alien_height): """Определяет количество рядов, помещающихся на экране.""" available_space_y = (ai_settings.screen_height - Построение флота 265 (3 * alien_height) - ship_height) number_rows = int(available_space_y / (2 * alien_height)) return number_rows def create_alien(ai_settings, screen, aliens, alien_number, row_number): 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 aliens.add(alien) def create_fleet(ai_settings, screen, ship, aliens): number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width) number_rows = get_number_rows(ai_settings, ship.rect.height, alien.rect.height) # Создание флота пришельцев. for row_number in range(number_rows): for alien_number in range(number_aliens_x): create_alien(ai_settings, screen, aliens, alien_number, row_number) Чтобы вычислить количество рядов, помещающихся на экране, мы включаем вы- числения available_space_y и number_rows в функцию get_number_rows() , похо- жую на get_number_aliens_x() . Вычисления заключаются в круглые скобки, чтобы их можно было разбить на две строки длиной 79 символов и менее, как указано в рекомендациях . Функция int() используется для того, чтобы предотвратить создание неполного ряда пришельцев. Чтобы создать несколько строк, мы используем два вложенных цикла: внешний и внутренний . Внутренний цикл создает один ряд пришельцев. Внешний цикл считает от 0 до количества рядов; Python использует код создания одного ряда и повторяет его number_rows раз. Чтобы создать вложенный цикл, напишите новый цикл for и снабдите повторя- емый код отступом. (В большинстве текстовых редакторов операции создания и удаления блоков кода выполняются просто, но, если вам понадобится помощь, обращайтесь к приложению Б.) Затем при вызове create_alien() передается аргумент с номером ряда, чтобы каждый ряд находился на экране ниже преды- дущих. Определению create_alien() необходим параметр с номером ряда. В create_ alien() мы изменяем координату y пришельца. Сначала прибавляется одна высота пришельца, чтобы создать пустое место у верхнего края экрана. Каждый новый ряд начинается на две высоты пришельца ниже последнего ряда, поэтому мы умножаем высоту пришельца на 2, а затем на номер ряда. Номер первого ряда равен 0, так что вертикальное расположение первого ряда остается неизменным. Все последующие ряды размещаются ниже на экране. Определение create_fleet() также содержит новый параметр для объекта ship ; следовательно, в вызов create_fleet() в alien_invasion .py необходимо добавить аргумент ship : 266 Глава 13 • Осторожно, пришельцы! Рис. 13.4. На экране появился весь флот пришельцев alien_invasion.py # Создание флота пришельцев. gf.create_fleet(ai_settings, screen, ship, aliens) Если теперь запустить игру, вы увидите целый флот пришельцев (рис. 13.4). В следующем разделе мы приведем флот в движение. УПРАЖНЕНИЯ 13-1 . Звезды: найдите изображение звезды . Создайте на экране сетку из звезд . 13-2 . Звезды-2: чтобы звезды выглядели более реалистично, следует внести случайное отклонение при размещении звезд . Вспомните, что случайные числа генерируются следу- ющим образом: from random import randint random_number = randint(-10,10) Этот код возвращает случайное целое число в диапазоне от −10 до 10 . Используя свой код из упражнения 13-1, измените позицию каждой звезды на случайную величину . Перемещение флота Флот пришельцев должен двигаться вправо по экрану, пока не дойдет до края; тогда флот опускается на заданную величину и начинает двигаться в обратном Перемещение флота 267 направлении. Это продолжается до тех пор, пока все пришельцы не будут сбиты, один из них не столкнется с кораблем или не достигнет низа экрана. Начнем с пере- мещения флота вправо. Перемещение вправо Чтобы корабли пришельцев перемещались по экрану, мы воспользуемся методом update() из alien .py , который будет вызываться для каждого пришельца в группе. Сначала добавим настройку для управления скоростью каждого пришельца: settings.py def __init__(self): # Настройки пришельцев self.alien_speed_factor = 1 Настройка используется в реализации update() : alien.py def update(self): """Перемещает пришельца вправо.""" self.x += self.ai_settings.alien_speed_factor self.rect.x = self.x При каждом обновлении позиции пришельца мы смещаем его вправо на величину, хранящуюся в alien_speed_factor . Точная позиция пришельца хранится в атрибу- те self.x , который может принимать вещественные значения . Затем значение self.x используется для обновления позиции прямоугольника пришельца . В основном цикле while уже содержатся вызовы обновления корабля и пуль. Те- перь необходимо также обновить позицию каждого пришельца: alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() gf.update_bullets(bullets) gf.update_aliens(aliens) gf.update_screen(ai_settings, screen, ship, aliens, bullets) Позиции пришельцев обновляются после обновления пуль, потому что скоро мы будем проверять, попали ли какие-либо пули в пришельцев. Наконец, добавьте новую функцию update_aliens() в конец файла game_functions .py : game_functions.py def update_aliens(aliens): """Обновляет позиции всех пришельцев во флоте.""" aliens.update() 268 Глава 13 • Осторожно, пришельцы! Мы используем метод update() для группы aliens , что приводит к автоматиче- скому вызову метода update() каждого пришельца. Если запустить Alien Invasion сейчас, вы увидите, как флот двигается вправо и исчезает за краем экрана. Создание настроек для направления флота Теперь мы создадим настройки, которые перемещают флот вниз по экрану, а потом влево при достижении правого края экрана. Вот как реализуется это поведение: settings.py # Настройки пришельцев self.alien_speed_factor = 1 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.py def check_edges(self): """Возвращает True, если пришелец находится у края экрана.""" screen_rect = self.screen.get_rect() if self.rect.right >= screen_rect.right: return True elif self.rect.left <= 0: return True def update(self): """Перемещает пришельца влево или вправо.""" self.x += (self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction) self.rect.x = self.x Перемещение флота 269 Вызов нового метода check_edges() для любого пришельца позволяет проверить, достиг ли он левого или правого края. У пришельца, находящегося у правого края, атрибут right его атрибута rect больше или равен атрибуту right атрибута rect экрана . У пришельца, находящегося у левого края, значение left меньше либо равно 0 . В метод update() будут внесены изменения, обеспечивающие перемещение вле- во и вправо ; для этого скорость пришельца умножается на значение fleet_ direction . Если значение fleet_direction равно 1, то значение alien_speed_factor прибавляется к текущей позиции пришельца; если же значение fleet_direction равно –1, то значение вычитается из позиции пришельца (который перемещается влево). Снижение флота и смена направления Когда пришелец доходит до края, весь флот должен опуститься вниз и изме- нить направление движения. Это означает, что в game_functions .py необходимо внести серьезные изменения, потому что именно здесь программа проверяет, достиг ли какой-либо пришелец левого или правого края. Для этого мы напишем функции check_fleet_edges() и change_fleet_direction() , а затем изменим update_aliens() : game_functions.py def check_fleet_edges(ai_settings, aliens): """Реагирует на достижение пришельцем края экрана.""" for alien in aliens.sprites(): if alien.check_edges(): change_fleet_direction(ai_settings, aliens) break def change_fleet_direction(ai_settings, aliens): """Опускает весь флот и меняет направление флота.""" for alien in aliens.sprites(): alien.rect.y += ai_settings.fleet_drop_speed ai_settings.fleet_direction *= -1 def update_aliens(ai_settings, aliens): """ Проверяет, достиг ли флот края экрана, после чего обновляет позиции всех пришельцев во флоте. """ check_fleet_edges(ai_settings, aliens) aliens.update() Функция check_fleet_edges() перебирает флот и вызывает check_edges() для каждого пришельца . Если check_edges() возвращает True , значит, пришелец находится у края и весь флот должен сменить направление, поэтому вызывает- ся функция change_fleet_direction() и происходит выход из цикла. Функция change_fleet_direction() перебирает пришельцев и уменьшает высоту каждого из них с использованием настройки fleet_drop_speed ; затем направление 270 Глава 13 • Осторожно, пришельцы! fleet_direction меняется на противоположное, для чего текущее значение умно- жается на –1. Мы изменили функцию update_aliens() и включили в нее проверку нахождения пришельцев у края вызовом check_fleet_edges() . Функция должна получать параметр ai_settings , поэтому аргумент ai_settings включается в вызов update_ aliens() : alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() gf.update_bullets(bullets) gf.update_aliens(ai_settings, aliens) gf.update_screen(ai_settings, screen, ship, aliens, bullets) Если запустить игру сейчас, флот будет двигаться влево-вправо между краями экрана и опускаться каждый раз, когда он доберется до края. Теперь можно пере- ходить к реализации уничтожения и отслеживания пришельцев, сталкивающихся с кораблем или достигающих нижнего края экрана. УПРАЖНЕНИЯ 13-3 . Капли: найдите изображение дождевой капли и создайте сетку из капель . Капли должны постепенно опускаться вниз и исчезать у нижнего края экрана . 13-4 . Дождь: измените свой код в упражнении 13-3, чтобы при исчезновении ряда капель у нижнего края экрана новый ряд появлялся у верхнего края и начинал падение . Уничтожение пришельцев Итак, мы создали корабль и флот пришельцев — но, когда пули достигают пришель- цев, они просто проходят насквозь, потому что программа не проверяет коллизии. В игровом программировании коллизией называется перекрытие игровых элемен- тов. Чтобы пули сбивали пришельцев, метод sprite.groupcollide() используется для выявления коллизий между элементами двух групп. Выявление коллизий Когда пуля попадает в пришельца, программа должна немедленно узнать об этом, чтобы сбитый пришелец исчез с экрана. Для этого мы будем проверять коллизии сразу же после обновления позиции пули. Метод sprite.groupcollide() сравнивает прямоугольник rect каждой пули с пря- моугольником rect каждого пришельца и возвращает словарь с пулями и при- шельцами, между которыми обнаружены коллизии. Каждый ключ в словаре представляет пулю, а ассоциированное с ним значение — пришельца, в которого попала пуля. (Этот словарь будет использоваться в реализации системы подсчета очков счета в главе 14.) Уничтожение пришельцев 271 Для проверки коллизий в функции update_bullets() используется следующий код: game_functions.py def update_bullets(aliens, bullets): """Обновляет позиции пуль и удаляет старые пули.""" # Проверка попаданий в пришельцев. # При обнаружении попадания удалить пулю и пришельца. collisions = pygame.sprite.groupcollide(bullets, aliens, True, True) Новая строка сначала перебирает все пули в группе bullets , а затем перебирает всех пришельцев в группе aliens . Каждый раз, когда между прямоугольником пули и пришельца обнаруживается перекрытие, groupcollide() добавляет пару «ключ—значение» в возвращаемый словарь. Два аргумента True сообщают Pygame, нужно ли удалять столкнувшиеся объекты: пулю и пришельца. (Чтобы создать сверхмощную пулю, которая будет уничтожать всех пришельцев на своем пути, можно передать в первом аргументе False , а во втором True . Пришельцы, в которых попадает пуля, будут исчезать, но все пули будут оставаться активными до верхнего края экрана.) При вызове update_bullets() передается аргумент aliens : alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() gf.update_bullets(aliens, bullets) gf.update_aliens(ai_settings, aliens) gf.update_screen(ai_settings, screen, ship, aliens, bullets) Рис. 13.5. Пули уничтожают пришельцев! 272 Глава 13 • Осторожно, пришельцы! Если запустить Alien Invasion сейчас, пришельцы, в которых попадает пуля, будут исчезать с экрана. На рис. 13.5 изображен частично уничтоженный флот. Создание больших пуль для тестирования Многие игровые возможности тестируются простым запуском игры, но не- которые аспекты слишком утомительно тестировать в обычной версии игры. Например, чтобы проверить, правильно ли обрабатывается уничтожение по- следнего пришельца, нам пришлось бы несколько раз сбивать всех пришельцев на экране. Для тестирования конкретных аспектов игры можно изменить игровые настройки так, чтобы упростить конкретную область. Например, можно уменьшить экран, чтобы на нем было меньше пришельцев, или увеличить скорость пули и количество пуль, одновременно находящихся на экране. Мое любимое изменение при тестировании Alien Invasion — использование сверхшироких пуль, которые остаются активными даже после попадания в при- шельца (рис. 13.6). Попробуйте задать настройке bullet_width значение 300 и посмотрите, сколько времени вам понадобится для уничтожения флота при- шельцев! Такие изменения повышают эффективность тестирования, а заодно могут подска- зать идеи для всевозможных игровых бонусов. (Только не забудьте восстановить нормальное состояние настроек после завершения тестирования.) Рис. 13.6. Сверхмощные пули упрощают тестирование некоторых аспектов игры Уничтожение пришельцев 273 Восстановление флота Одна из ключевых особенностей Alien Invasion — бесконечные орды пришельцев: каждый раз, когда вы уничтожаете один флот, на его месте появляется другой. Чтобы после уничтожения одного флота появлялся другой, сначала нужно убе- диться в том, что группа aliens пуста. Если она пуста, вызывается функция create_ fleet() . Проверка будет выполняться в функции update_bullets() , потому что именно здесь уничтожаются отдельные пришельцы: game_functions.py def update_bullets(ai_settings, screen, ship, aliens, bullets): # Проверка попаданий в пришельцев. # При обнаружении попадания удалить пулю и пришельца. collisions = pygame.sprite.groupcollide(bullets, aliens, True, True) if len(aliens) == 0: # Уничтожение существующих пуль и создание нового флота. bullets.empty() create_fleet(ai_settings, screen, ship, aliens) В точке программа проверяет, пуста ли группа aliens . Если она пуста, то все существующие пули удаляются методом empty() , который удаляет все существу- ющие спрайты из группы . Вызов метода create_fleet() снова заполняет экран пришельцами. В определении update_bullets() теперь появились дополнительные параметры ai_settings , screen и ship , поэтому вызов update_bullets() в alien_invasion .py не- обходимо обновить: alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() gf.update_bullets(ai_settings, screen, ship, aliens, bullets) gf.update_aliens(ai_settings, aliens) gf.update_screen(ai_settings, screen, ship, aliens, bullets) Новый флот появляется сразу же после уничтожения текущего флота. Ускорение пуль Попытавшись стрелять по пришельцам в текущем состоянии игры, вы заметите, что движение пуль немного замедлилось. Дело в том, что Pygame теперь выполняет больший объем работы при каждом проходе цикла. Скорость пуль можно увели- чить настройкой bullet_speed_factor в settings .py . Если увеличить это значение (например, до 3), пули снова будут двигаться по экрану с разумной скоростью: settings.py # Настройки пуль |