Мэтиз. Изучаем 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.2 Mb.
|
alien_invasion.py def _check_events(ship): """Обрабатывает нажатия клавиш и события мыши.""" for event in pygame.event.get(): elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT: ❶ self.ship.moving_right = True ❷ elif event.type == pygame.KEYUP: if event.key == pygame.K_RIGHT: self.ship.moving_right = False В точке изменяется реакция игры при нажатии клавиши →; вместо непосред- ственного изменения позиции корабля программа просто присваивает moving_right значение True . В точке добавляется новый блок elif , реагирующий на события KEYUP . Когда игрок отпускает клавишу → ( K_RIGHT ), moving_right присваивается значение False Остается изменить цикл while в alien_invasion .py , чтобы при каждом проходе цикла вызывался метод update() корабля: alien_invasion.py def run_game(self): # Запуск основного цикла игры. while True: self._check_events() self.ship.update() self._update_screen() Позиция корабля будет обновляться после проверки событий клавиатуры, но перед обновлением экрана. Таким образом, позиция корабля обновляется в от- вет на действия пользователя и будет использоваться при перерисовке корабля на экране. Если запустить alien_invasion .py и удерживать клавишу →, корабль непрерывно двигается вправо, пока клавиша не будет отпущена. Перемещение влево и вправо Теперь, когда мы реализовали непрерывное движение вправо, добавить движение влево относительно несложно. Для этого нужно снова изменить класс Ship и метод 254 Глава 12 • Инопланетное вторжение _check_events() . Ниже приведены необходимые изменения в __init__() и update() в классе Ship : ship.py def __init__(self, ai_game): # Флаги перемещения self.moving_right = False self.moving_left = False def update(self): """Обновляет позицию корабля с учетом флагов.""" if self.moving_right: self.rect.x += 1 if self.moving_left: self.rect.x -= 1 В методе __init__() добавляется флаг self.moving_left . В update() используются два отдельных блока if вместо elif , чтобы при нажатии обеих клавиш со стрел- ками атрибут rect.x сначала увеличивался, а потом уменьшался. В результате корабль остается на месте. Если бы для движения влево использовался блок elif , то клавиша → всегда имела бы приоритет. Такая реализация повышает точность перемещения при переключении направления, когда игрок может ненадолго удер- живать нажатыми обе клавиши. В _check_events() необходимо внести два изменения: alien_invasion.py def _check_events(self): """Обрабатывает нажатия клавиш и события мыши.""" for event in pygame.event.get(): elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT: self.ship.moving_right = True elif event.key == pygame.K_LEFT: self.ship.moving_left = True elif event.type == pygame.KEYUP: if event.key == pygame.K_RIGHT: self.ship.moving_right = False elif event.key == pygame.K_LEFT: self.ship.moving_left = False Если событие KEYDOWN происходит для события K_LEFT , то moving_left присваи- вается True . Если событие KEYUP происходит для события K_LEFT , то moving_left присваивается False . Здесь возможно использовать блоки elif , потому что каждое событие связано только с одной клавишей. Если же игрок нажимает обе клавиши одновременно, то программа обнаруживает два разных события. Если вы запустите alien_invasion .py , то увидите, что корабль может непрерывно двигаться влево и вправо. Если же нажать обе клавиши, корабль останавливается. Управление кораблем 255 Следующий шаг — доработка движения корабля. Внесем изменения в скорость и ограничим величину перемещения, чтобы корабль не выходил за края экрана. Регулировка скорости корабля В настоящий момент корабль смещается на 1 пиксел за каждый проход цикла while , но для повышения точности управления скоростью можно добавить в класс Settings атрибут ship_speed . Этот атрибут определяет величину смещения корабля при каждом проходе цикла. Новый атрибут settings .py выглядит так: settings.py class Settings(): """Класс для хранения всех настроек игры Alien Invasion.""" def __init__(self): # Настройки корабля self.ship_speed = 1.5 Переменной ship_speed_factor присваивается значение 1.5. При перемещении корабля его позиция изменяется на 1,5 пиксела вместо 1. Дробные значения скорости позволят лучше управлять скоростью корабля при последующем повышении темпа игры. Однако атрибуты прямоугольников (такие, как x ) принимают только целочисленные значения, поэтому в Ship необходимо внести ряд изменений: ship.py class Ship(): """Класс для управления кораблем.""" ❶ def __init__(self, ai_game): """Инициализирует корабль и задает его начальную позицию.""" self.screen = ai_game.screen self.settings = ai_game.settings # Каждый новый корабль появляется у нижнего края экрана # Сохранение вещественной координаты центра корабля. ❷ self.x = float(self.rect.x) # Флаги перемещения self.moving_right = False self.moving_left = False def update(self): """Обновляет позицию корабля с учетом флагов.""" # Обновляется атрибут x, не rect. if self.moving_right: 256 Глава 12 • Инопланетное вторжение ❸ self.x += self.settings.ship_speed if self.moving_left: self.x -= self.settings.ship_speed # Обновление атрибута rect на основании self.x. ❹ self.rect.x = self.x def blitme(self): В точке в Ship создается атрибут settings , чтобы он мог использоваться в update() . Так как позиция корабля изменяется с нецелым приращением пикселов, ее следует присвоить переменной, способной хранить дробные значения. Формаль- но атрибутам rect можно присвоить дробные значения, но rect сохранит только целую часть этого значения. Для точного хранения позиции корабля определяется новый атрибут self.x , способный хранить дробные значения . Функция float() используется для преобразования значения self.rect.x в вещественный формат и сохранения этого значения в self.x После изменения позиции корабля в update() значение self.x изменяется на величину, хранящуюся в settings.ship_speed . После обновления self.x новое значение используется для обновления атрибута self.rect.x , управляющего по- зицией корабля . В self.rect.x будет сохранена только целая часть self.x , но для отображения корабля этого достаточно. Теперь можно изменить значение ship_speed ; при любом значении, большем 1, корабль начинает двигаться быстрее. Эта возможность ускорит реакцию корабля на действия игрока, а также позволит нам изменить темп игры с течением времени. ПРИМЕЧАНИЕ Если вы используете macOS, может оказаться, что корабль двигается очень медленно даже при высоких значениях скорости . Проблема решается запуском игры в полноэкранном режиме, который мы вскоре реализуем . Ограничение перемещений Если удерживать какую-нибудь клавишу со стрелкой достаточно долго, корабль выйдет за край экрана. Давайте сделаем так, чтобы корабль останавливался при до- стижении края экрана. Задача решается изменением метода update() в классе Ship : ship.py def update(self): """Обновляет позицию корабля с учетом флагов.""" # Обновляется атрибут x объекта ship, не rect. ❶ if self.moving_right and self.rect.right < self.screen_rect.right: self.x += self.settings.ship_speed ❷ if self.moving_left and self.rect.left > 0: self.x -= self.settings.ship_speed # Обновление атрибута rect на основании self.x self.rect.x = self.x Управление кораблем 257 Этот код проверяет позицию корабля перед изменением значения self.x . Вы- ражение self.rect.right возвращает координату x правого края прямоугольника корабля. Если это значение меньше значения, возвращаемого self.screen_rect. right , значит, корабль еще не достиг правого края экрана . То же относится и к ле- вому краю: если координата x левой стороны прямоугольника больше 0, значит, корабль еще не достиг левого края экрана . Проверка гарантирует, что корабль будет оставаться в пределах экрана перед изменением значения self.x Если вы запустите alien_invasion .py сейчас, то движение корабля будет останавли- ваться у края экрана. Согласитесь, эффектно: мы всего лишь добавили условную проверку в команду if , но все выглядит так, словно у края экрана корабль натал- кивается на невидимую стену или силовое поле! Рефакторинг _check_events() В ходе разработки метод _check_events() будет становиться все длиннее, поэтому мы выделим из _check_events() еще два метода: для обработки событий KEYDOWN и для обработки событий KEYUP : alien_invasion.py def _check_events(self): """Реагирует на нажатие клавиш и события мыши.""" for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type == pygame.KEYDOWN: self._check_keydown_events(event) elif event.type == pygame.KEYUP: self._check_keyup_events(event) def _check_keydown_events(self, event): """Реагирует на нажатие клавиш.""" if event.key == pygame.K_RIGHT: self.ship.moving_right = True elif event.key == pygame.K_LEFT: self.ship.moving_left = True def _check_keyup_events(self, event): """Реагирует на отпускание клавиш.""" if event.key == pygame.K_RIGHT: self.ship.moving_right = False elif event.key == pygame.K_LEFT: self.ship.moving_left = False В программе появились два вспомогательных метода: _check_keydown_events() и _check_keyup_events() . Каждый метод получает параметр self и параметр event . Тела двух методов скопированы из _check_events() , а старый код заменен вызовами новых методов. Новая структура кода упрощает метод _check_events() и облегчает последующее программирование реакции на действия игрока. 258 Глава 12 • Инопланетное вторжение Нажатие клавиши Q для завершения Итак, теперь программа реагирует на нажатия клавиш, и мы можем добавить еще один способ завершения игры. Было бы утомительно щелкать на кнопке X в верхней части игрового окна каждый раз, когда в игру добавляется новая функциональ- ность, поэтому мы добавим специальную клавишу для завершения игры при на- жатии клавиши Q : alien_invasion.py def _check_keydown_events(self, event): elif event.key == pygame.K_LEFT: self.ship.moving_left = True elif event.key == pygame.K_q: sys.exit() В код _check_keydown_events() добавляется новый блок. Теперь в процессе тести- рования можно закрыть игру нажатием клавиши Q , вместо того чтобы пользоваться кнопкой закрытия окна. Запуск игры в полноэкранном режиме В Pygame поддерживается полноэкранный режим, который, возможно, понравится вам больше запуска в обычном окне. Некоторые игры лучше смотрятся в полно- экранном режиме, а у пользователей macOS в полноэкранном режиме может улучшиться быстродействие. Чтобы запустить игру в полноэкранном режиме, внесите следующие изменения в __init__() : alien_invasion.py def __init__(self): """Инициализирует игру и создает игровые ресурсы.""" pygame.init() self.settings = Settings() ❶ self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN) ❷ self.settings.screen_width = self.screen.get_rect().width self.settings.screen_height = self.screen.get_rect().height pygame.display.set_caption("Alien Invasion") При создании экранной поверхности передается размер (0, 0) и параметр pygame. FULLSCREEN . Эти значения приказывают Pygame вычислить размер окна, за- полняющего весь экран. Так как ширина и высота экрана неизвестны заранее, эти настройки обновляются после создания экрана . Атрибуты width и height пря- моугольника экрана используются для обновления объекта settings Если вам понравится, как игра выглядит или работает в полноэкранном режиме, оставьте новые настройки. Если вы предпочитаете, чтобы игра работала в отдель- В двух словах 259 ном окне, — вернитесь к исходной реализации с назначением конкретных размеров экрана. ПРИМЕЧАНИЕ Прежде чем запускать игру в полноэкранном режиме, убедитесь в том, что она закрывается при нажатии клавиши Q ; в Pygame не существует стандартных средств завершения игры в полноэкранном режиме . В двух словах В следующем разделе мы реализуем стрельбу, для чего нам потребуется новый файл с именем bullet .py и изменения в некоторых уже имеющихся файлах. В настоящее время программа состоит из трех файлов с разными классами и методами. Чтобы вы четко представляли себе структуру проекта, кратко проанализируем каждый из этих файлов перед добавлением новой функциональности. alien_invasion .py Главный файл программы alien_invasion .py содержит класс AlienInvasion . Этот класс содержит ряд важных атрибутов, используемых в процессе игры: настройки хранятся в settings , основная поверхность для вывода изображения хранится в screen , а экземпляр ship тоже создается в этом файле. Также в alien_invasion .py содержится главный цикл игры — цикл while с вызовами _check_events() , ship. update() и _update_screen() Метод _check_events() обнаруживает важные события (например, нажатия и от- пускания клавиш) и обрабатывает все эти типы событий с использованием методов _check_keydown_events() и _check_keyup_events() . Пока эти методы управляют движением корабля. Класс AlienInvasion также содержит метод _update_screen() , который перерисовывает экран при каждом проходе основного цикла. Файл alien_invasion .py — единственный файл, который должен запускаться для игры в Alien Invasion. Все остальные файлы — settings .py и ship .py — содержат код, который импортируется в этот файл. settings .py Файл settings .py содержит класс Settings . Этот класс содержит только метод __init__() , инициализирующий атрибуты, которые управляют внешним видом и скоростью игры. ship .py Файл ship .py содержит класс Ship . В этом классе определен метод __init__() , метод update() для управления позицией корабля и метод blitme() для вывода 260 Глава 12 • Инопланетное вторжение изображения корабля на экран. Изображение корабля хранится в файле ship .bmp , который находится в папке images УПРАЖНЕНИЯ 12.3. Документация Pygame: разработка игры зашла уже достаточно далеко, и вам сто- ит просмотреть документацию Pygame. Домашняя страница Pygame находится по адре- су https://www .pygame .org/ , а домашняя страница документации — по адресу https://www . pygame .org/docs/ . Пока вы можете ограничиться простым просмотром документации. Она не понадобится вам для завершения этого проекта, но пригодится, если вы захотите внести изменения в игру или займетесь созданием собственной игры. 12.4. Ракета: создайте игру, в которой в исходном состоянии в центре экрана находится ракета. Игрок может перемещать ракету вверх, вниз, вправо и влево четырьмя клавишами со стрелками. Проследите за тем, чтобы ракета не выходила за края экрана. 12.4. Клавиши: создайте файл Pygame, который создает пустой экран. В цикле событий вы- водите значение атрибута event.key при обнаружении события pygame.KEYDOWN . Запустите программу, нажимайте различные клавиши и понаблюдайте за реакцией Pygame. Стрельба А теперь добавим в игру функциональность стрельбы. Мы напишем код, который выпускает снаряд (маленький прямоугольник) при нажатии игроком клавиши «пробел». Снаряды летят вертикально вверх, пока не исчезнут у верхнего края экрана. Добавление настроек Сначала добавим в settings .py новые настройки для значений, управляющих по- ведением класса Bullet . Эти настройки добавляются в конец метода __init__() : settings.py def __init__(self): # Параметры снаряда self.bullet_speed = 1 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = (60, 60, 60) Эти настройки создают темно-серые снаряды шириной 3 пиксела и высотой 15 пик- селов. Они двигаются немного медленнее, чем корабль. Создание класса Bullet Теперь создадим файл bullet .py для хранения класса Bullet . Первая часть файла bullet .py выглядит так: Стрельба 261 bullet.py import pygame from pygame.sprite import Sprite class Bullet(Sprite): """Класс для управления снарядами, выпущенными кораблем.""" def __init__(self, ai_game): """Создает объект снарядов в текущей позиции корабля.""" super().__init__() self.screen = ai_game.screen self.settings = ai_game.settings self.color = self.settings.bullet_color # Создание снаряда в позиции (0,0) и назначение правильной позиции. ❶ self.rect = pygame.Rect(0, 0, self.settings.bullet_width, self.settings.bullet_height) ❷ self.rect.midtop = ai_game.ship.rect.midtop # Позиция снаряда хранится в вещественном формате. ❸ self.y = float(self.rect.y) Класс Bullet наследует от класса Sprite , импортируемого из модуля pygame.sprite Работая со спрайтами (sprite), разработчик группирует связанные элементы в сво- ей игре и выполняет операции со всеми сгруппированными элементами одновре- менно. Чтобы создать экземпляр снаряда, методу __init__() необходим текущий экземпляр AlienInvasion , а вызов super() необходим для правильной реализации наследования от Sprite . Также задаются атрибуты для объектов экрана и настроек, а также цвета снаряда. В точке создается атрибут rect снаряда. Снаряд не создается на основе готового изображения, поэтому прямоугольник приходится строить с нуля при помощи класса pygame.Rect() . При создании экземпляра этого класса необходимо задать координаты левого верхнего угла прямоугольника, его ширину и высоту. Прямо- угольник инициализируется в точке (0, 0), но в следующих двух строках он переме- щается в нужное место, так как позиция снаряда зависит от позиции корабля. Ши- рина и высота снаряда определяются значениями, хранящимися в self.settings В точке атрибуту midtop снаряда присваивается значение midtop корабля. Сна- ряд должен появляться у верхнего края корабля, поэтому верхний край снаряда совмещается с верхним краем прямоугольника корабля для имитации выстрела из корабля . А вот как выглядит вторая часть bullet .py , update() и draw_bullet() : |