Изучаем 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.
|
alien_invasion.py def __init__(self): pygame.display.set_caption("Alien Invasion") 244 Глава 12 • Инопланетное вторжение # Назначение цвета фона. ❶ self.bg_color = (230, 230, 230) def run_game(self): for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() # При каждом проходе цикла перерисовывается экран. ❷ self.screen.fill(self.bg_color) # Отображение последнего прорисованного экрана. pygame.display.flip() Цвета в Pygame задаются в схеме RGB: тройками интенсивности красной, зеленой и синей составляющих цвета. Значение каждой составляющей лежит в диапазоне от 0 до 255. Цветовое значение (255, 0, 0) соответствует красному цвету, (0, 255, 0) — зеленому и (0, 0, 255) — синему. Разные сочетания составляющих RGB позволяют создать до 16 миллионов цветов. В цветовом значении (230, 230, 230) красная, синяя и зеленая составляющие смешиваются в равных долях, давая светло-серый цвет фона. Этот цвет сохраняется в переменной self.bg_color . В точке экран заполняется цветом фона. Для этого вызывается метод fill() , получающий всего один аргумент: цвет фона. Создание класса Settings Каждый раз, когда в нашу игру добавляется новая функциональность, также в нее обычно добавляются новые настройки (параметры конфигурации). Вместо того чтобы добавлять настройки в коде, мы напишем модуль с именем settings ; этот модуль содержит класс с именем Settings для хранения всех настроек. Такое реше- ние позволит передавать один объект вместо множества отдельных настроек. Кроме того, оно упрощает вызовы функций и изменение внешнего вида игры с ростом проекта. Чтобы внести изменения в игру, достаточно будет изменить некоторые значения в settings .py вместо того, чтобы искать разные настройки в файлах. Создайте новый файл с именем settings .py в папке alien_invasion . Исходная версия класса Settings выглядит так: settings.py class Settings(): """Класс для хранения всех настроек игры Alien Invasion.""" def __init__(self): """Инициализирует настройки игры.""" # Параметры экрана self.screen_width = 1200 self.screen_height = 800 self.bg_color = (230, 230, 230) Добавление изображения корабля 245 Чтобы создать экземпляр Settings и использовать его для обращения к настройкам, внесите изменения в alien_invasion .py : alien_invasion.py import pygame from settings import Settings class AlienInvasion: """Класс для управления ресурсами и поведением игры.""" def __init__(self): """Инициализирует игру и создает игровые ресурсы.""" pygame.init() ❶ self.settings = Settings() ❷ self.screen = pygame.display.set_mode( (self.settings.screen_width, self.settings.screen_height)) pygame.display.set_caption("Alien Invasion") def run_game(self): # При каждом проходе цикла перерисовывается экран. ❸ self.screen.fill(self.settings.bg_color) # Отображение последнего прорисованного экрана. pygame.display.flip() Класс Settings импортируется в основной файл программы, после чего программа создает экземпляр Settings и сохраняет его в self.settings после вызова pygame. init() . При создании экрана используются атрибуты screen_width и screen_ height объекта self.settings , после чего объект self.settings также используется для получения цвета фона при заполнении экрана . Запустив файл alien_invasion .py , вы не заметите никаких изменений, потому что в этом разделе мы всего лишь переместили настройки, уже использованные в дру- гом месте. Теперь можно переходить к добавлению новых элементов на экран. Добавление изображения корабля А теперь добавим в игру космический корабль, которым управляет игрок. Чтобы вывести его на экран, мы загрузим изображение, после чего воспользуемся методом Pygame blit() для вывода изображения. Выбирая графику для своих игр, обязательно обращайте внимание на условия ли- цензирования. Самый безопасный и дешевый начальный вариант — использование бесплатной графики с таких сайтов, как http://pixabay .com/ 246 Глава 12 • Инопланетное вторжение В игре можно использовать практически любые графические форматы, но проще всего использовать файлы в формате .bmp , потому что этот формат Pygame загру- жает по умолчанию. И хотя Pygame можно настроить для других типов файлов, некоторые типы зависят от установки на компьютере определенных графических библиотек. (Большинство изображений, которые вы найдете, имеют формат .jpg , .png или .gif , но их можно преобразовать в формат .bmp при помощи таких про- грамм, как Photoshop, GIMP или Paint.) Обратите особое внимание на цвет фона вашего изображения. Попробуйте найти файл с прозрачным фоном, который можно заменить любым цветом фона в гра- фическом редакторе. Чтобы ваша игра хорошо смотрелась, цвет фона изображения должен соответствовать цвету фона игры. Также можно подобрать цвет фона игры под цвет фона изображения. В игре Alien Invasion используется файл ship .bmp (рис. 12.1), который можно загру- зить в числе ресурсов книги по адресу https://www .nostarch .com/pythoncrashcourse2e/ Цвет фона файла соответствует настройкам, используемым в проекте. Создайте в главном каталоге проекта ( alien_invasion ) каталог с именем images . Сохраните файл ship .bmp в каталоге images Рис. 12.1. Корабль для игры Alien Invasion Создание класса Ship После того как изображение корабля будет выбрано, его необходимо вывести на экран. Для работы с кораблем мы напишем модуль ship , содержащий класс Ship Этот класс реализует большую часть поведения корабля. ship.py import pygame class Ship(): """Класс для управления кораблем.""" Добавление изображения корабля 247 def __init__(self, ai_game): """Инициализирует корабль и задает его начальную позицию.""" ❶ self.screen = ai_game.screen ❷ self.screen_rect = ai_game.screen.get_rect() # Загружает изображение корабля и получает прямоугольник. ❸ self.image = pygame.image.load('images/ship.bmp') self.rect = self.image.get_rect() # Каждый новый корабль появляется у нижнего края экрана. ❹ self.rect.midbottom = self.screen_rect.midbottom ❺ def blitme(self): """Рисует корабль в текущей позиции.""" self.screen.blit(self.image, self.rect) Один из факторов эффективности Pygame заключается в том, что программист может выполнять операции с игровыми элементами как с прямоугольниками даже в том случае, если они имеют другую форму. Операции с прямоугольниками эф- фективны, потому что прямоугольник — простая геометрическая фигура. Обычно этот подход работает достаточно хорошо, и игроки не замечают, что программа не отслеживает точную геометрическую форму каждого игрового элемента. В этом классе корабль и экран будут рассматриваться как прямоугольные объекты. Перед определением класса программа импортирует модуль pygame . Метод __init__() класса Ship получает два параметра: ссылку self и ссылку на текущий экземпляр класса AlienInvasion , так класс Ship получает доступ ко всем игровым ресурсам, определенным в AlienInvasion . В точке экран присваивается атрибуту Ship , чтобы к нему можно было легко обращаться во всех модулях класса. В точ- ке программа обращается к атрибуту rect объекта экрана при помощи метода get_rect() и присваивает его self.screen_rect . Это позволяет разместить корабль в нужной позиции экрана. Чтобы загрузить изображение, мы вызываем метод pygame.image.load() и пере- даем ему местоположение изображения корабля. Функция возвращает поверх- ность, представляющую корабль, которая присваивается self.image Когда изображение будет загружено, программа вызывает get_rect() для полу- чения атрибута rect поверхности корабля, чтобы позднее использовать ее для позиционирования корабля. При работе с объектом rect вам доступны координаты x и y верхней, нижней, левой и правой сторон, а также центра. Присваивая любые из этих значений, вы задаете текущую позицию прямоугольника. Местонахождение центра игрового элемента определяется атрибутами center , centerx или centery прямоугольника. Стороны определяются атрибутами top , bottom , left и right . Также имеются атрибуты, ко- торые являются комбинацией этих свойств, например midbottom , midtop , midleft и midright . Для изменения горизонтального или вертикального расположения прямоугольника достаточно задать атрибуты x и y , содержащие координаты левого верхнего угла. Эти атрибуты избавляют вас от вычислений, которые раньше раз- работчикам игр приходилось выполнять вручную, притом достаточно часто. 248 Глава 12 • Инопланетное вторжение ПРИМЕЧАНИЕ В Pygame начало координат (0, 0) находится в левом верхнем углу экрана, а оси направлены сверху вниз и слева направо . На экране размером 1200 × 800 начало координат располагается в левом верхнем углу, а правый нижний угол имеет координаты (1200, 800) . Эти координаты относятся к игровому окну, а не к физическому экрану . Корабль будет расположен в середине нижней стороны экрана. Для этого значение self.rect.midbottom выравнивается по атрибуту midbottom прямоугольника экра- на . Pygame использует эти атрибуты rect для позиционирования изображения, чтобы корабль был выровнен по центру, а его нижний край совпадал с нижним краем экрана. В точке определяется метод blitme() , который выводит изображение на экран в позиции, заданной self.rect Вывод корабля на экран Изменим программу alien_invasion .py , чтобы в ней создавался корабль и вызывался метод blitme() класса Ship : alien_invasion.py from settings import Settings from ship import Ship class AlienInvasion: """Класс для управления ресурсами и поведением игры.""" def __init__(self): pygame.display.set_caption("Alien Invasion") ❶ self.ship = Ship(screen) def run_game(self): # При каждом проходе цикла перерисовывается экран. self.screen.fill(self.settings.bg_color) ❷ self.ship.blitme() # Отображение последнего прорисованного экрана. pygame.display.flip() После создания экрана программа импортирует класс Ship и создает его экземп- ляр . При вызове Ship передается один аргумент — экземпляр AlienInvasion Аргумент self относится к текущему экземпляру AlienInvasion . Этот параметр предоставляет Ship доступ к ресурсам игры, например к объекту screen . Экземпляр Ship присваивается self.ship Рефакторинг: методы _check_events() и _update_screen() 249 После заполнения фона корабль рисуется на экране вызовом ship.blitme() , так что корабль выводится поверх фона . Если запустить alien_invasion .py сейчас, вы увидите пустой игровой экран, в центре нижней стороны которого находится корабль (рис. 12.2). Рис. 12.2. Корабль в середине нижней стороны экрана Рефакторинг: методы _check_events() и _update_screen() В больших проектах перед добавлением нового кода часто проводится рефакто- ринг уже написанного кода. Рефакторинг упрощает структуру существующего кода и дальнейшее развитие проекта. В этом разделе метод run_game() , который становится слишком длинным, будет разбит на два вспомогательных метода. Вспо- могательный метод работает во внутренней реализации класса, но не должен вы- зываться через экземпляр. В Python имена вспомогательных методов обозначаются начальным символом подчеркивания ( _ ). Метод _check_events() Начнем с перемещения кода управления событиями в отдельный метод _ check_ events() . Тем самым вы упростите run_game() и изолируете цикл управления событиями от остального кода. Изоляция цикла событий позволит организовать 250 Глава 12 • Инопланетное вторжение управление событиями отдельно от других аспектов игры (например, обновления экрана). Ниже приведен класс AlienInvasion с новым методом _check_events() , который используется только в коде run_game() : alien_invasion.py def run_game(self): """Запуск основного цикла игры.""" while True: ❶ self._check_events() # При каждом проходе цикла перерисовывается экран. ❷ def _check_events(self): """Обрабатывает нажатия клавиш и события мыши.""" for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() Мы определяем новый метод _ check_events() и перемещаем строки, которые проверяют, не закрыл ли игрок окно щелчком кнопки мыши, в этот новый метод. Для вызова метода внутри класса используется точечный синтаксис с переменной self и именем метода . Затем метод вызывается в цикле while метода run_game() Метод _update_screen() Для дальнейшего упрощения run_game() выделим код обновления экрана в от- дельный метод _ update_screen() : alien_invasion.py def run_game(self): """Запуск основного цикла игры.""" while True: self._check_events() self._update_screen() def _check_events(self): def _update_screen(self): """Обновляет изображения на экране и отображает новый экран.""" self.screen.fill(self.settings.bg_color) self.ship.blitme() pygame.display.flip() Код прорисовки фона и переключения экрана перемещен в _update_screen() . Тело основного цикла в run_game() серьезно упростилось. С первого взгляда видно, что программа отслеживает новые события и обновляет экран при каждом проходе цикла. Управление кораблем 251 Если вы уже написали несколько игр, вероятно, вы с самого начала начнете раз- бивать свой код на такие методы. Но если вы никогда не брались за подобный про- ект, вероятно, вы не знаете, как структурировать этот код. Эта последовательность дает представление о реальном процессе разработки: сначала вы пишете свой код в самом простом виде, а потом подвергаете его рефакторингу по мере роста слож- ности проекта. Теперь, когда мы изменили структуру кода и упростили его расширение, можно переходить к динамическим аспектам игры. УПРАЖНЕНИЯ 12.1. Синее небо: создайте окно Pygame с синим фоном. 12.2. Игровой персонаж: найдите изображение игрового персонажа, который вам нравит- ся, в формате .bmp (или преобразуйте существующее изображение). Создайте класс, кото- рый рисует персонажа в центре экрана, и приведите цвет фона изображения в соответствие с цветом фона экрана (или наоборот). Управление кораблем Реализуем возможность перемещения корабля по горизонтали. Для этого мы на- пишем код, реагирующий на нажатие клавиш ← или →. Начнем с движения впра- во, а потом применим те же принципы к движению влево. Заодно вы научитесь управлять перемещением изображений на экране. Обработка нажатия клавиши Каждый раз, когда пользователь нажимает клавишу, это нажатие регистрирует- ся в Pygame как событие. Каждое событие идентифицируется методом pygame. event.get() , поэтому в методе _ check_events() необходимо указать, какие со- бытия должны отслеживаться. Каждое нажатие клавиши регистрируется как событие KEYDOWN При обнаружении события KEYDOWN необходимо проверить, была ли нажата кла- виша, инициирующая некоторое игровое событие. Например, при нажатии клави- ши → значение rect.x корабля увеличивается для перемещения корабля вправо: 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: ❷ if event.key == pygame.K_RIGHT: # Переместить корабль вправо. ❸ self.ship.rect.x += 1 252 Глава 12 • Инопланетное вторжение Внутри _check_events() в цикл событий добавляется блок elif для выполнения кода при обнаружении события KEYDOWN . Чтобы проверить, является ли нажатая клавиша клавишей → ( pygame.K_RIGHT ), мы читаем атрибут event.key . Если нажата клавиша →, корабль перемещается вправо, для чего значение self.ship. rect.x увеличивается на 1 . Если запустить программу alien_invasion .py сейчас, вы увидите, что корабль переме- щается вправо на 1 пиксел при каждом нажатии клавиши →. Неплохо для начала, но это не лучший способ управления кораблем. Чтобы управление было более удобным, следует реализовать возможность непрерывного перемещения. Непрерывное перемещение Если игрок удерживает клавишу →, корабль должен двигаться вправо до тех пор, пока клавиша не будет отпущена. Чтобы узнать, когда клавиша → будет отпущена, наша игра отслеживает событие pygame.KEYUP ; таким образом, реализация непре- рывного движения будет основана на отслеживании событий KEYDOWN и KEYUP в со- четании с флагом moving_right В неподвижном состоянии корабля флаг moving_right равен False . При нажатии клавиши → флагу присваивается значение True , а когда клавиша будет отпущена, флаг возвращается в состояние False Класс Ship управляет всеми атрибутами корабля, и мы добавим в него атрибут с именем moving_right и метод update() для проверки состояния флага moving_ right . Метод update() изменяет позицию корабля, если флаг содержит значение True . Этот метод будет вызываться каждый раз, когда вы хотите обновить позицию корабля. Ниже приведены изменения в классе Ship : ship.py class Ship(): """ Класс для управления кораблем""" def __init__(self, ai_game): # Каждый новый корабль появляется у нижнего края экрана. self.rect.midbottom = self.screen_rect.midbottom # Флаг перемещения ❶ self.moving_right = False ❷ def update(self): """Обновляет позицию корабля с учетом флага.""" if self.moving_right: self.rect.x += 1 def blitme(self): Управление кораблем 253 Мы добавляем атрибут self.moving_right в метод __init__() и инициализируем его значением False . Затем вызывается метод update() , который перемещает корабль вправо, если флаг равен True . Метод update() будет вызываться через экземпляр Ship , поэтому он не считается вспомогательным методом. Теперь внесем изменения в run_game() , чтобы при нажатии клавиши → moving_ right присваивалось значение True , а при ее отпускании — False : |