Книга Изучаем Python
Скачать 4.68 Mb.
|
Рис. 12.1. Корабль для игры Alien Invasion 236 Глава 12 • Стреляющий корабль Создание класса Ship После того как изображение корабля выбрано, его необходимо вывести на экран. Для работы с кораблем мы напишем модуль ship , содержащий класс Ship . Этот класс реализует бульшую часть поведения корабля. ship.py import pygame class Ship(): def __init__(self, screen): """Инициализирует корабль и задает его начальную позицию.""" self.screen = screen # Загрузка изображения корабля и получение прямоугольника. self.image = pygame.image.load('images/ship.bmp') self.rect = self.image.get_rect() self.screen_rect = screen.get_rect() # Каждый новый корабль появляется у нижнего края экрана. self.rect.centerx = self.screen_rect.centerx self.rect.bottom = self.screen_rect.bottom def blitme(self): """Рисует корабль в текущей позиции.""" self.screen.blit(self.image, self.rect) Сначала программа импортирует модуль pygame . Метод __init__() класса Ship по- лучает два параметра: ссылку self и объект screen , на котором выводится корабль. Загрузка изображения выполняется вызовом pygame.image.load() . Функция возвращает поверхность, представляющую корабль; полученный объект сохраня- ется в self.image После того как изображение будет загружено, метод get_rect() используется для получения атрибута rect поверхности . Один из факторов эффективности Pygame заключается в том, что программист может выполнять операции с игровы- ми элементами как с прямоугольниками даже в том случае, если они имеют другую форму. Операции с прямоугольниками эффективны, потому что прямоугольник — простая геометрическая фигура. Обычно этот подход работает достаточно хорошо и игроки не замечают, что программа не отслеживает точную геометрическую форму каждого игрового элемента. При работе с объектом rect для вас доступны координаты x и y верхней, нижней, левой и правой сторон, а также центра. Присваивая любые из этих значений, вы задаете текущую позицию прямоугольника. Местонахождение центра игрового элемента определяется атрибутами center , centerx или centery прямоугольника. Стороны определяются атрибутами top , bottom , left и right . Для изменения горизонтального или вертикального рас- положения прямоугольника достаточно задать атрибуты x и y , содержащие координаты левого верхнего угла. Эти атрибуты избавляют вас от вычислений, которые раньше разработчикам игр приходилось выполнять вручную, притом достаточно часто. Добавление изображения корабля 237 ПРИМЕЧАНИЕ В Pygame начало координат (0, 0) находится в левом верхнем углу экрана, а оси направлены сверху вниз и слева направо . На экране с размерами 1200 на 800 начало координат располагается в левом верхнем углу, а правый нижний угол имеет координаты (1200, 800) . Корабль будет расположен в середине нижней стороны экрана. Для этого мы сна- чала сохраняем прямоугольник экрана в self.screen_rect , а затем присваиваем self.rect.centerx (координата x центра корабля) значение атрибута centerx пря- моугольника экрана . Атрибуту self.rect.bottom (координата y низа корабля) присваивается значение атрибута bottom прямоугольника экрана. Pygame исполь- зует эти атрибуты rect для позиционирования изображения, чтобы корабль был выровнен по центру, а его нижний край совпадал с нижним краем экрана. В точке определяется метод blitme() , который выводит изображение на экран в позиции, заданной self.rect Вывод корабля на экран Изменим программу alien_invasion .py , чтобы в ней создавался корабль (экземпляр Ship ) и вызывался метод blitme() класса Ship : alien_invasion.py from settings import Settings from ship import Ship def run_game(): pygame.display.set_caption("Alien Invasion") # Создание корабля. ship = Ship(screen) # Start the main loop for the game. while True: # При каждом проходе цикла перерисовывается экран. screen.fill(ai_settings.bg_color) ship.blitme() # Отображение последнего прорисованного экрана. pygame.display.flip() run_game() После создания экрана программа импортирует класс Ship и создает его экземпляр (с именем ship ). Это должно происходить до начала основного цикла while , чтобы при каждом проходе цикла не создавался новый экземпляр корабля. Чтобы перерисовать корабль на экране, мы вызываем ship.blitme() после заполнения фона, так что корабль выводится поверх фона . Если вы запустите alien_invasion .py сейчас, вы увидите пустой игровой экран, в цен- тре нижней стороны которого находится корабль (рис. 12.2). 238 Глава 12 • Стреляющий корабль Рис. 12.2. Корабль в середине нижней стороны экрана Рефакторинг: модуль game_functions В больших проектах перед добавлением нового кода часто проводится рефакто- ринг уже написанного кода. Рефакторинг упрощает структуру существующего кода и дальнейшее развитие проекта. В этом разделе мы создадим новый модуль game_functions для хранения функций, обеспечивающих работу игры. Модуль game_functions предотвратит чрезмерное разрастание alien_invasion .py и сделает логику alien_invasion .py более простой и понятной. Функция check_events() Начнем с перемещения кода управления событиями в отдельную функцию check_ events() . Тем самым вы упростите run_game() и изолируете цикл управления событиями от остального кода. Изоляция цикла событий позволит организовать управление событиями отдельно от других аспектов игры (например, обновления экрана). Поместим check_events() в отдельный модуль с именем game_functions : game_functions.py import sys import pygame def check_events(): Рефакторинг: модуль game_functions 239 """Обрабатывает нажатия клавиш и события мыши.""" for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() Этот модуль импортирует модули sys и pygame , используемые в цикле обработки событий. На данный момент эта функция не получает параметров, а ее тело копи- руется из цикла событий в alien_invasion .py Теперь изменим код alien_invasion .py , чтобы он импортировал модуль game_ functions , и мы заменим цикл событий вызовом check_events() : alien_invasion.py import pygame from settings import Settings from ship import Ship import game_functions as gf def run_game(): # Запуск основного цикла игры. while True: gf.check_events() # При каждом проходе цикла перерисовывается экран. Импортировать модуль sys прямо в главный файл в программы уже не нужно, по- тому что он сейчас используется только в модуле game_functions . Импортируемому модулю game_functions для удобства присваивается псевдоним gf Функция update_screen() Для дальнейшего упрощения run_game() выделим код обновления экрана в от- дельную функцию update_screen() в game_functions .py : game_functions.py def check_events(): def update_screen(ai_settings, screen, ship): """Обновляет изображения на экране и отображает новый экран.""" # При каждом проходе цикла перерисовывается экран. screen.fill(ai_settings.bg_color) ship.blitme() # Отображение последнего прорисованного экрана. pygame.display.flip() Новая функция update_screen() получает три параметра: ai_settings , screen и ship . Теперь необходимо заменить цикл while из alien_invasion .py вызовом update_ sc reen() : 240 Глава 12 • Стреляющий корабль alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events() gf.update_screen(ai_settings, screen, ship) run_game() Эти две функции упрощают цикл while и процесс дальнейшей разработки. Буль- шая часть работы будет выполняться не в run_game() , а в модуле game_functions Так как мы решили начать работу с кодом c одного файла, мы не стали вводить мо- дуль game_functions с самого начала. Эта последовательность дает представление о реальном процессе разработки: сначала вы пишете свой код в самом простом виде, а потом подвергаете его рефакторингу по мере роста сложности проекта. Теперь, когда мы изменили структуру кода и упростили его расширение, можно переходить к динамическим аспектам игры! УПРАЖНЕНИЯ 12-1 . Синее небо: создайте окно Pygame с синим фоном . 12-2 . Игровой персонаж: найдите изображение игрового персонажа, который вам нравится, в формате .bmp (или преобразуйте существующее изображение) . Создайте класс, который рисует персонажа в центре экрана, и приведите цвет фона изображения в соответствие с цветом фона экрана (или наоборот) . Управление кораблем Реализуем возможность перемещения корабля по горизонтали. Для этого мы на- пишем код, реагирующий на нажатие клавиш → или ←. Начнем с движения впра- во, а потом применим те же принципы к движению влево. Заодно вы научитесь управлять перемещением изображений на экране. Обработка нажатия клавиши Каждый раз, когда пользователь нажимает клавишу, это нажатие регистрируется в Pygame как событие. Каждое событие идентифицируется методом pygame.event. get() , поэтому в функции check_events() необходимо указать, какие события должны отслеживаться. Каждое нажатие клавиши регистрируется как событие KEYDOWN При обнаружении события KEYDOWN необходимо проверить, была ли нажата кла- виша, инициирующая некоторое игровое событие. Например, при нажатии кла- виши → значение rect.centerx корабля увеличивается для перемещения корабля вправо: game_functions.py def check_events(ship): """Обрабатывает нажатия клавиш и события мыши.""" Управление кораблем 241 for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT: # Переместить корабль вправо. ship.rect.centerx += 1 Функции check_events() передается параметр ship , потому что корабль должен двигаться вправо при нажатии клавиши →. Внутри check_events() в цикл событий добавляется блок elif для выполнения кода при обнаружении события KEYDOWN . Чтобы проверить, является ли нажатая клавиша клавишей → ( pygame.K_RIGHT ), мы читаем атрибут event.key . Если нажата клавиша →, корабль перемещается вправо, для чего значение ship.rect.centerx увеличивается на 1 . Вызов check_events() в alien_invasion .py необходимо изменить, чтобы в аргументе передавался объект ship : alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ship) gf.update_screen(ai_settings, screen, ship) Если запустить программу 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(): 242 Глава 12 • Стреляющий корабль def __init__(self, screen): # Каждый новый корабль появляется у нижнего края экрана. self.rect.centerx = self.screen_rect.centerx self.rect.bottom = self.screen_rect.bottom # Флаг перемещения self.moving_right = False def update(self): """Обновляет позицию корабля с учетом флага.""" if self.moving_right: self.rect.centerx += 1 def blitme(self): Мы добавляем атрибут self.moving_right в методе __init__() и инициализируем его значением False . Затем вызывается метод update() , который перемещает корабль вправо, если флаг равен True . Теперь внесем изменения в check_events() , чтобы при нажатии клавиши → moving_ right присваивалось значение True , а при ее отпускании — False : game_functions.py def check_events(ship): """Обрабатывает нажатия клавиш и события мыши.""" for event in pygame.event.get(): elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.type == pygame.KEYUP: if event.key == pygame.K_RIGHT: ship.moving_right = False В точке изменяется реакция игры при нажатии клавиши →; вместо непосред- ственного изменения позиции корабля программа просто присваивает moving_right значение True . В точке добавляется новый блок elif , реагирующий на события KEYUP . Когда игрок отпускает клавишу → ( K_RIGHT ), moving_right присваивается значение False Остается изменить цикл while в alien_invasion .py , чтобы при каждом проходе цикла вызывался метод update() корабля: alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ship) ship.update() gf.update_screen(ai_settings, screen, ship) Позиция корабля будет обновляться после проверки событий клавиатуры, но перед обновлением экрана. Таким образом, позиция корабля обновляется в от- Управление кораблем 243 вет на действия пользователя и будет использоваться при перерисовке корабля на экране. Если запустить alien_invasion .py и удерживать клавишу →, корабль непрерывно двигается вправо, пока клавиша не будет отпущена. Перемещение влево и вправо Теперь, когда мы реализовали непрерывное движение вправо, добавить движе- ние влево относительно несложно. Для этого нужно снова изменить класс Ship и функцию check_events() . Ниже приведены необходимые изменения в __init__() и update() в классе Ship : ship.py def __init__(self, screen): # Флаги перемещения self.moving_right = False self.moving_left = False def update(self): """Обновляет позицию корабля с учетом флагов.""" if self.moving_right: self.rect.centerx += 1 if self.moving_left: self.rect.centerx -= 1 В методе __init__() добавляется флаг self.moving_left . В update() используются два отдельных блока if вместо elif , чтобы при нажатии обеих клавиш со стрелками атрибут rect.centerx сначала увеличивался, а потом уменьшался. В результате корабль остается на месте. Если бы для движения влево использовался блок elif , то клавиша → всегда имела бы приоритет. Такая реализация повышает точность перемещения при переключении направления, когда игрок может ненадолго удер- живать нажатыми обе клавиши. В check_events() необходимо внести два изменения: game_functions.py def check_events(ship): """Обрабатывает нажатия клавиш и события мыши.""" for event in pygame.event.get(): elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True elif event.type == pygame.KEYUP: if event.key == pygame.K_RIGHT: ship.moving_right = False elif event.key == pygame.K_LEFT: ship.moving_left = False 244 Глава 12 • Стреляющий корабль Если событие KEYDOWN происходит для события K_LEFT , то moving_left присваи- вается True . Если событие KEYUP происходит для события K_LEFT , то moving_left присваивается False . Здесь возможно использовать блоки elif , потому что каждое событие связано только с одной клавишей. Если же игрок нажимает обе клавиши одновременно, то программа обнаруживает два разных события. Если вы запустите alien_invasion .py , то увидите, что корабль может непрерывно двигаться влево и вправо. Если же нажать обе клавиши, корабль останавливается. Следующий шаг — доработка движения корабля. Внесем изменения в скорость и ограничим величину перемещения, чтобы корабль не выходил за края экрана. Регулировка скорости корабля В настоящий момент корабль смещается на один пиксел за каждый проход цикла while , но для повышения точности управления скоростью можно добавить в класс Settings атрибут ship_speed_factor . Этот атрибут определяет величину смещения корабля при каждом проходе цикла. Новый атрибут settings .py выглядит так: settings.py class Settings(): """Класс для хранения всех настроек игры Alien Invasion.""" def __init__(self): # Настройки корабля Переменной ship_speed_factor присваивается значение 1.5. При перемещении ко- рабля его позиция изменяется на 1,5 пиксела вместо 1. Дробные значения скорости позволят лучше управлять скоростью корабля при последующем повышении темпа игры. Однако атрибуты прямоугольников (такие, как centerx ) принимают только целочисленные значения, поэтому в Ship необходимо внести ряд изменений: ship.py class Ship(): def __init__(self, ai_settings, screen): """Инициализирует корабль и задает его начальную позицию.""" self.screen = screen self.ai_settings = ai_settings # Каждый новый корабль появляется у нижнего края экрана # Сохранение вещественной координаты центра корабля. self.center = float(self.rect.centerx) # Флаги перемещения self.moving_right = False self.moving_left = False |