Стрелялка с Pygame №11: жизни игрока

Одиннадцатая проекта «Стрелялка с Pygame». Если пропустили, обязательно вернитесь и начните с первой части.
В этот раз у игрока появятся дополнительные жизни и анимация взрыва при смерти.

В этой серии уроков будет создана полноценная игра на языке Python с помощью библиотеки Pygame. Она будет особенно интересна начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.

Взрыв игрока

В случае смерти игрока будет использоваться другая анимация из набора Kenny Game Art.

Скачать архив изображений можно по этой ссылке.

После этого их нужно загрузить в виде кадров, как это было с прошлыми взрывами. Добавим еще один тип взрыва 'player' и загрузим его в тот же цикл, поскольку у этой анимации столько же кадров, сколько было у предыдущей. Теперь код загрузки выглядит следующим образом:

explosion_anim = {}
explosion_anim['lg'] = []
explosion_anim['sm'] = []
explosion_anim['player'] = []
for i in range(9):
    filename = 'regularExplosion0{}.png'.format(i)
    img = pygame.image.load(path.join(img_dir, filename)).convert()
    img.set_colorkey(BLACK)
    img_lg = pygame.transform.scale(img, (75, 75))
    explosion_anim['lg'].append(img_lg)
    img_sm = pygame.transform.scale(img, (32, 32))
    explosion_anim['sm'].append(img_sm)
    filename = 'sonicExplosion0{}.png'.format(i)
    img = pygame.image.load(path.join(img_dir, filename)).convert()
    img.set_colorkey(BLACK)
    explosion_anim['player'].append(img)

В спрайте класса Explosion не нужно ничего менять — достаточно просто сделать взрыв игрока, когда его здоровье падает до нуля. Этот пункт можно добавить в игру там же, где проверяются столкновения игрока с астероидами:

#  Проверка, не ударил ли моб игрока
hits = pygame.sprite.spritecollide(player, mobs, True, pygame.sprite.collide_circle)
for hit in hits:
    player.shield -= hit.radius * 2
    expl = Explosion(hit.rect.center, 'sm')
    all_sprites.add(expl)
    newmob()
    if player.shield <= 0:
        death_explosion = Explosion(player.rect.center, 'player')
        all_sprites.add(death_explosion)
		running = False

Но если запустить программу сейчас, то будет одна проблема: после смерти игрока значение running становится False, игра заканчивается, и нет возможности увидеть анимацию.

Чтобы это исправить, нужно не заканчивать игру до тех пор, пока не завершится анимация взрыва. Так, игрока нужно удалять, но не менять значение running до завершения анимации взрыва:

    if player.shield <= 0:
        death_explosion = Explosion(player.rect.center, 'player')
        all_sprites.add(death_explosion)
		player.kill()

# Если игрок умер, игра окончена
if not player.alive() and not death_explosion.alive():
	running = False

Функция alive() просто сообщает, является ли конкретный спрайт живым. Поскольку взрыв был убит (функцией kill()), после завершения анимации игра заканчивается.

Жизни

Теперь нужно добавить несколько жизней игроку. Их можно отслеживать с помощью переменной, но также необходимо выводить их на экран. Вместо чисел также можно использовать миниатюрные изображения корабля игрока. Для начала нужно создать уменьшенное изображение:

player_img = pygame.image.load(path.join(img_dir, "playerShip1_orange.png")).convert()
player_mini_img = pygame.transform.scale(player_img, (25, 19))
player_mini_img.set_colorkey(BLACK)

Дальше — добавить несколько параметров в __init__() класса Player: счетчик жизней, флажок (переменная, которая может принимать значения True или False), чтобы показывать/скрывать игрока и таймер для определения того, сколько времени игрока нужно скрывать.

self.lives = 3
self.hidden = False
self.hide_timer = pygame.time.get_ticks()

Теперь, в случае смерти игрока, вместо использования kill() игрок будет скрываться, а от lives будет отниматься 1. Также на следующей жизни сбрасывается здоровье:

if player.shield <= 0:
        death_explosion = Explosion(player.rect.center, 'player')
        all_sprites.add(death_explosion)
        player.hide()
        player.lives -= 1
        player.shield = 100

# Если игрок умер, игра окончена
if player.lives == 0 and not death_explosion.alive():
    running = False

Дальше нужно проработать, как работает скрытие игрока. В классе Player() необходимо добавить соответствующий метод, который будет переключать флажок hidden на значение True и запускать таймер. Также нужно удостовериться в том, что пока игрок скрыт, он не может столкнуться с астероидом. Есть несколько вариантов, как этого можно добиться, но в самом простом не нужно добавлять/удалять его из групп или делать что-то подобное. Достаточно убрать игрока с нижней части экрана на короткое время:

def hide(self):
    # временно скрыть игрока
    self.hidden = True
    self.hide_timer = pygame.time.get_ticks()
    self.rect.center = (WIDTH / 2, HEIGHT + 200)

А в методе update() необходимо снова вернуть игрока, если прошло достаточно времени (начать можно с 1 секунды):

def update(self):
	# показать, если скрыто
	if self.hidden and pygame.time.get_ticks() - self.hide_timer > 1000:
	    self.hidden = False
	    self.rect.centerx = WIDTH / 2
	    self.rect.bottom = HEIGHT - 10

Отображение счетчика жизней

Для отображения жизней нужно создать функцию, похожую на draw_shield_bar(), которая позволит размещать счетчик в конкретном месте:

def draw_lives(surf, x, y, lives, img):
    for i in range(lives):
        img_rect = img.get_rect()
        img_rect.x = x + 30 * i
        img_rect.y = y
        surf.blit(img, img_rect)

Дальше необходим цикл с количеством, соответствующем количеству жизней и пространством в 30 пикселей для каждого изображения (ширина player_mini_img — 25 пикселей, так что между ними как раз останется достаточно места).

Далее нужно добавить вызов функции в раздел отрисовки игрового цикла:

draw_lives(screen, WIDTH - 100, 5, player.lives,
	   player_mini_img)

А вот и финальный результат:

Отображение счетчика жизней

Код урока — shmup-11.py

В следующем уровне добавим в игру улучшения.