Книга Изучаем Python
Скачать 4.68 Mb.
|
245 def update(self): """Обновляет позицию корабля с учетом флагов.""" # Обновляется атрибут center, не rect. if self.moving_right: self.center += self.ai_settings.ship_speed_factor if self.moving_left: self.center -= self.ai_settings.ship_speed_factor # Обновление атрибута rect на основании self.center. self.rect.centerx = self.center def blitme(self): В точке в список параметров __init__() добавляется параметр ai_settings , что- бы для корабля была доступна величина его скорости. Затем параметр ai_settings преобразуется в атрибут для использования в update() . Так как позиция корабля изменяется с нецелым приращением пикселов, она должна храниться в перемен- ной, способной хранить дробные значения. Формально атрибутам rect можно присвоить дробные значения, но rect сохранит только целую часть этого значения. Для точного хранения позиции корабля определяется новый атрибут self.center , способный хранить дробные значения . Функция float() используется для пре- образования значения self.rect.centerx в вещественный формат и сохранения этого значения в self.center После изменения позиции корабля в update() значение self.center изменяется на величину, хранящуюся в ai_settings.ship_speed_factor . После обновле- ния self.center новое значение используется для обновления атрибута self. rect.centerx , управляющего позицией корабля . В self.rect.centerx будет сохранена только целая часть self.center , но для отображения корабля этого достаточно. Значение ai_settings должно передаваться в аргументе при создании экземпляра Ship в alien_invasion .py : alien_invasion.py def run_game(): # Создание корабля. ship = Ship(ai_settings, screen) Теперь с любым значением ship_speed_factor , бульшим 1, корабль будет двигаться быстрее. Эта возможность ускорит реакцию корабля на действия игрока, а также позволит нам изменить темп игры с течением времени. Ограничение перемещений Если удерживать какую-нибудь клавишу со стрелкой достаточно долго, корабль выйдет за край экрана. Давайте сделаем так, чтобы корабль останавливался при до- стижении края экрана. Задача решается изменением метода update() в классе Ship : 246 Глава 12 • Стреляющий корабль ship.py def update(self): """Обновляет позицию корабля с учетом флагов.""" # Обновляется атрибут center, не rect. if self.moving_right and self.rect.right < self.screen_rect.right: self.center += self.ai_settings.ship_speed_factor if self.moving_left and self.rect.left > 0: self.center -= self.ai_settings.ship_speed_factor # Обновление атрибута rect на основании self.center self.rect.centerx = self.center Этот код проверяет позицию корабля перед изменением значения self.center Выражение self.rect.right возвращает координату x правого края прямо- угольника корабля. Если это значение меньше значения, возвращаемого self. screen_rect.right , значит, корабль еще не достиг правого края экрана . То же относится и к левому краю: если координата x левой стороны прямоугольника больше 0, значит, корабль еще не достиг левого края экрана . Проверка гаранти- рует, что корабль будет оставаться в пределах экрана, перед изменением значения self.center Если вы запустите alien_invasion .py сейчас, то движение корабля будет останавли- ваться у края экрана. Рефакторинг check_events() В ходе разработки функция check_events() будет становиться все длиннее, поэтому мы выделим из check_events() еще две функции: для обработки событий KEYDOWN и для обработки событий KEYUP : game_functions.py def check_keydown_events(event, ship): """Реагирует на нажатие клавиш.""" if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True def check_keyup_events(event, ship): """Реагирует на отпускание клавиш.""" if event.key == pygame.K_RIGHT: ship.moving_right = False elif event.key == pygame.K_LEFT: ship.moving_left = False def check_events(ship): """Обрабатывает нажатия клавиш и события мыши.""" for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type == pygame.KEYDOWN: check_keydown_events(event, ship) В двух словах 247 elif event.type == pygame.KEYUP: check_keyup_events(event, ship) В программе появились две новые функции: check_keydown_events() и check_ keyup_events() . Каждая функция получает параметр event и параметр ship . Тела двух функций скопированы из check_events() , а старый код заменен вызовами новых функций. Новая структура кода упрощает функцию check_events() и об- легчает последующее программирование реакции на действия игрока. В двух словах В следующем разделе мы реализуем стрельбу, для чего нам потребуется но- вый файл с именем bullet .py и изменения в некоторых уже имеющихся файлах. В настоящее время программа состоит из четырех файлов с разными классами, функциями и методами. Чтобы вы четко представляли себе структуру проекта, кратко проанализируем каждый из этих файлов перед добавлением новой функ- циональности. alien_invasion .py Главный файл программы alien_invasion .py создает ряд важных объектов, исполь- зуемых ходе игры: настройки хранятся в ai_settings , основная поверхность для вывода изображения хранится в screen , а экземпляр ship тоже создается в этом файле. Также в alien_invasion .py содержится главный цикл игры — цикл while с вы- зовами check_events() , ship.update() и update_screen() Файл alien_invasion .py — единственный файл, который должен запускаться для игры в Alien Invasion. Все остальные файлы — settings .py , game_functions .py , ship .py — со- держат код, который импортируется (прямо или косвенно) в этот файл. settings .py Файл settings .py содержит класс Settings . Этот класс содержит только метод __init__() , инициализирующий атрибуты, которые управляют внешним видом и скоростью игры. game_functions .py Файл game_functions .py содержит набор функций, выполняющих основную работу в игре. Функция check_events() обнаруживает события, представ- ляющие интерес для игры (например, нажатия и отпускания клавиш), и об- рабатывает все эти типы событий при помощи вспомогательных функций check_keydown_events() и check_keyup_events() . Пока эти функции управляют только движением корабля. Модуль game_functions также содержит функцию update_screen() , которая перерисовывает экран при каждом проходе основного цикла. 248 Глава 12 • Стреляющий корабль ship .py Файл ship .py содержит класс Ship . В этом классе определен метод __init__() , метод update() для управления позицией корабля и метод blitme() для вывода изображения корабля на экран. Изображение корабля хранится в файле ship .bmp , который находится в папке images УПРАЖНЕНИЯ 12-3 . Ракета: создайте игру, у которой в исходном состоянии в центре экрана находится ракета . Игрок может перемещать ракету вверх, вниз, вправо и влево четырьмя клавишами со стрелками . Проследите за тем, чтобы ракета не выходила за края экрана . 12-4 . Клавиши: создайте файл Pygame, который создает пустой экран . В цикле событий выводите значение атрибута event .key при обнаружении события pygame .KEYDOWN . За- пустите программу, нажимайте различные клавиши и понаблюдайте за реакцией Pygame . Стрельба А теперь добавим в игру возможность стрельбы. Мы напишем код, который выпу- скает пулю (маленький прямоугольник) при нажатии игроком клавиши «пробел». Пули летят вертикально вверх, пока не исчезнут у верхнего края экрана. Добавление настроек Сначала добавим в settings .py новые настройки для значений, управляющих по- ведением класса Bullet . Эти настройки добавляются в конец метода __init__() : settings.py def __init__(self): # Параметры пули self.bullet_speed_factor = 1 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = 60, 60, 60 Эти настройки создают темно-серые пули с шириной 3 пиксела и высотой 15 пик- селов. Пули двигаются немного медленнее, чем корабль. Создание класса Bullet Теперь создадим файл bullet .py для хранения класса Bullet . Первая часть файла bullet .py выглядит так: bullet.py import pygame from pygame.sprite import Sprite class Bullet(Sprite): Стрельба 249 """Класс для управления пулями, выпущенными кораблем.""" def __init__(self, ai_settings, screen, ship): """Создает объект пули в текущей позиции корабля.""" super(Bullet, self).__init__() self.screen = screen # Создание пули в позиции (0,0) и назначение правильной позиции. self.rect = pygame.Rect(0, 0, ai_settings.bullet_width, ai_settings.bullet_height) self.rect.centerx = ship.rect.centerx self.rect.top = ship.rect.top # Позиция пули хранится в вещественном формате. self.y = float(self.rect.y) self.color = ai_settings.bullet_color self.speed_factor = ai_settings.bullet_speed_factor Класс Bullet наследует от класса Sprite , импортируемого из модуля pygame.sprite Работая со спрайтами (sprite), разработчик группирует связанные элементы в своей игре и выполняет операцию со всеми сгруппированными элементами одно- временно. Чтобы создать экземпляр пули, методу __init__() необходимо передать экземпляры ai_settings , screen и ship , а вызов super() необходим для правильной реализации наследования от Sprite ПРИМЕЧАНИЕ Вызов super(Bullet, self) .__init__() использует синтаксис Python 2 .7 . В Python 3 этот синтаксис тоже работает, хотя вызов также можно записать в более простой форме super() .__init__() . В точке создается атрибут rect пули. Пуля не создается на основе готового изображения, поэтому прямоугольник приходится строить «с нуля» при по- мощи класса pygame.Rect() . При создании экземпляра этого класса необходимо задать координаты левого верхнего угла прямоугольника, его ширину и высоту. Прямоугольник инициализируется в точке (0, 0), но в следующих двух строках он перемещается в нужное место, так как позиция пули зависит от позиции корабля. Ширина и высота пули определяются значениями, хранящимися в ai_settings В точке атрибуту centerx пули присваивается значение rect.centerx корабля. Пуля должна появляться у верхнего края корабля, поэтому верхний край пули совмещается с верхним краем прямоугольника корабля для имитации «выстрела» из корабля . Координата y пули хранится в вещественной форме для внесения более точных изменений в скорость пули . В точке настройки цвета и скорости пули сохра- няются в self.color и self.speed_factor А вот как выглядит вторая часть bullet .py , update() и draw_bullet() : bullet.py def update(self): """Перемещает пулю вверх по экрану.""" 250 Глава 12 • Стреляющий корабль # Обновление позиции пули в вещественном формате. self.y -= self.speed_factor # Обновление позиции прямоугольника. self.rect.y = self.y def draw_bullet(self): """Вывод пули на экран.""" pygame.draw.rect(self.screen, self.color, self.rect) Метод update() управляет позицией пули. Когда происходит выстрел, пуля двига- ется вверх по экрану, что соответствует уменьшению координаты y; следовательно, для обновления позиции пули следует вычесть величину, хранящуюся в self. speed_factor , из self.y . Затем значение self.y используется для изменения значения self.rect.y . Атрибут speed_factor позволяет увеличить скорость пуль по ходу игры или при изменении ее поведения. Координата x пули после выстрела не изменяется, поэтому пуля летит вертикально по прямой линии. Для вывода пули на экран вызывается функция draw_bullet() . Она заполняет часть экрана, определяемую прямоугольником пули, цветом из self.color . Группировка пуль Класс Bullet и все необходимые настройки готовы; можно переходить к на- писанию кода, который будет выпускать пулю каждый раз, когда игрок на- жимает клавишу «пробел». Сначала мы создадим в alien_invasion .py группу для хранения всех летящих пуль, чтобы программа могла управлять их полетом. Эта группа будет представлена экземпляром класса pygame.sprite.Group , ко- торый напоминает список с расширенной функциональностью, которая может быть полезна при построении игр. Мы воспользуемся группой для прорисовки пуль на экране при каждом проходе основного цикла и обновления текущей позиции каждой пули: alien_invasion.py import pygame from pygame.sprite import Group def run_game(): # Создание корабля. ship = Ship(ai_settings, screen) # Создание группы для хранения пуль. bullets = Group() # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() bullets.update() gf.update_screen(ai_settings, screen, ship, bullets) run_game() Стрельба 251 Класс Group импортируется из pygame.sprite . В точке создается экземпляр Group с именем bullets . Эта группа создается за пределами цикла while , чтобы новая группа пуль не создавалась при каждом проходе цикла. ПРИМЕЧАНИЕ Если группа будет создаваться в цикле, в результате программа создает тысячи групп, и скорость игры упадет до минимума . Если ваша игра со временем начинает заметно «тормозить», вниматель- но проверьте, что происходит в основном цикле while . Объект bullets передается методам check_events() и update_screen() . В check_ events() он используется при обработке клавиши «пробел», а в update_screen() необходимо перерисовать выводимые на экран пули. Вызов update() для группы приводит к автоматическому вызову update() для каждого спрайта в группе. Строка bullets.update() вызывает bullet.update() для каждой пули, включенной в группу bullets Обработка выстрелов В файле game_functions .py необходимо внести изменения в метод check_keydown_ events() , чтобы при нажатии клавиши «пробел» происходил выстрел. Изменять check_keyup_events() не нужно, потому что при отпускании клавиши ничего не происходит. Также необходимо изменить update_screen() и вывести каждую пулю на экран перед вызовом flip() . Ниже представлены соответствующие изменения в game_functions .py : game_functions.py from bullet import Bullet def check_keydown_events(event, ai_settings, screen, ship, bullets): elif event.key == pygame.K_SPACE: # Создание новой пули и включение ее в группу bullets. new_bullet = Bullet(ai_settings, screen, ship) bullets.add(new_bullet) def check_events(ai_settings, screen, ship, bullets): """Обрабатывает нажатия клавиш и события мыши.""" for event in pygame.event.get(): elif event.type == pygame.KEYDOWN: check_keydown_events(event, ai_settings, screen, ship, bullets) def update_screen(ai_settings, screen, ship, bullets): # Все пули выводятся позади изображений корабля и пришельцев. for bullet in bullets.sprites(): bullet.draw_bullet() ship.blitme() 252 Глава 12 • Стреляющий корабль Рис. 12.3. Экран игры после серии выстрелов Группа bullets передается check_keydown_events() . Когда игрок нажимает про- бел, создается новая пуля (экземпляр Bullet с именем new_bullet ), которая добав- ляется в группу bullets методом add() ; код bullets.add(new_bullet) сохраняет новую пулю в группе bullets Группу bullets необходимо добавить в число параметров в определении check_events() , а также передать в аргументе при вызове check_keydown_ events() Параметр bullets передается функции update_screen() , которая рисует пули на экране. Метод bullets.sprites() возвращает список всех спрайтов в группе bullets . Чтобы нарисовать все выпущенные пули на экране, программа перебирает спрайты в bullets и вызывает для каждого draw_bullet() . Если запустить alien_invasion .py сейчас, вы сможете двигать корабль влево и вправо и выпускать сколько угодно пуль. Пули перемещаются вверх по экрану и исчезают при достижении верхнего края (рис. 12.3). Размер, цвет и скорость пуль можно из- менить при помощи настроек в settings .py Удаление старых пуль На данный момент пули исчезают по достижении верхнего края, но только потому, что Pygame не может нарисовать их выше края экрана. На самом деле пули про- должают существовать; их координата y продолжает уменьшаться. И это создает проблему, потому что пули продолжают потреблять память и вычислительные мощности. Стрельба 253 От старых пуль необходимо избавиться, иначе игра замедлится из-за большого объема лишней работы. Для этого необходимо определить момент, когда атрибут bottom прямоугольника пули достигнет 0, — это означает, что пуля вышла за верх- ний край экрана: alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() bullets.update() # Удаление пуль, вышедших за край экрана. for bullet in bullets.copy(): if bullet.rect.bottom <= 0: bullets.remove(bullet) print(len(bullets)) gf.update_screen(ai_settings, screen, ship, bullets) Удалять элементы из списка или группы в цикле for не следует, поэтому переби- рать нужно копию группы. Метод copy() используется для создания цикла for , в котором возможно изменять содержимое bullets . Программа проверяет каждую пулю и определяет, вышла ли она за верхний край экрана . Если пуля пересекла границу, она удаляется из bullets . В точке добавляется команда print , которая сообщает, сколько пуль сейчас существует в игре; по выведенному значению можно убедиться в том, что пули действительно были удалены. Если код работает правильно, вы можете понаблюдать за выводом на терминале и убедиться в том, что количество пуль уменьшается до 0 после того, как очередной залп уходит за верхний край экрана. После того как вы запустите игру и убедитесь в том, что пули правильно удаляются из группы, удалите команду print . Если ко- манда останется в программе, она существенно замедлит игру, потому что вывод на терминал занимает больше времени, чем отображение графики в игровом окне. Ограничение количества пуль Многие игры-«стрелялки» ограничивают количество пуль, одновременно находя- щихся на экране, чтобы у игроков появился стимул стрелять более метко. То же самое будет сделано и в игре Alien Invasion. Сначала сохраним максимально допустимое количество пуль в settings .py : settings.py # Параметры пули self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = 60, 60, 60 self.bullets_allowed = 3 В любой момент времени на экране может находиться не более трех пуль. Эта на- стройка будет использоваться в game_functions .py для проверки количества суще- ствующих пуль перед созданием новой пули в check_keydown_events() : |