Прокачиваем игру на Python c уворачиванием от предметов
hard

Прокачиваем игру на Python c уворачиванием от предметов

Добавляем главный экран, подсчёт очков и перезапуск

Мы прошли два этапа создания игры с pygame — Python-библиотекой для создания 2D-игр:

Сегодня мы ещё немного улучшим существующий проект. В статье объединим две готовые части кода и добавим новые возможности: очки и возможность проиграть — и оптимизируем анимацию. Получится такое — классика аркадных игр:

import pygame
from sys import exit

pygame.init()

# объявляем ширину и высоту экрана
width = 800
height = 400

# создаём экран игры
screen = pygame.display.set_mode((width, height))

# устанавливаем количество кадров в секунду
fps = 60
# создаём объект таймера
clock = pygame.time.Clock()

# создаём объекты шрифта: в скобках указываем шрифт и размер через запятую
text_font = pygame.font.Font('prstartk.ttf', 15)
text_font_collide = pygame.font.Font('prstartk.ttf', 50)

# создаём новую поверхность земли отдельным объектом surface
# в финальном коде не используется
# 1 — задаём размеры:
width_ts = 800
height_ts = 200
# 2 — создаём поверхность по размерам
test_surface = pygame.Surface((width_ts, height_ts))
# 3 — добавляем цвет
test_surface.fill('Brown')

# загружаем в переменные картинки из папки с нашим файлом
back = pygame.image.load('code_game_back_floor.jpg').convert()
hero = pygame.image.load('detective.png').convert_alpha()
pot = pygame.image.load('teapot.png').convert_alpha()
candle = pygame.image.load('candlestick.png').convert_alpha()
box = pygame.image.load('wooden_box.png').convert_alpha()

# объявляем переменные с начальными координатами для всех анимаций
hero_x_pos = 75
hero_y_pos = 180
candle_x_pos = 900
candle_y_pos = 70
box_x_pos = 900
box_y_pos = 200
pot_x_pos = 900
pot_y_pos = 345

# помещаем изображения в рамки прямоугольника
# в скобках задаём точку привязки и координаты для неё
hero_rect = hero.get_rect(center=(hero_x_pos, hero_y_pos))
pot_rect = pot.get_rect(center=(pot_x_pos, pot_y_pos))
candle_rect = candle.get_rect(center=(candle_x_pos, candle_y_pos))
box_rect = box.get_rect(center=(box_x_pos, box_y_pos))

# создаём сигнальные переменные
pot_flag = False
box_flag = False

# создаём объект текста: в скобках указываем текст, сглаживание и цвет
text_surface = text_font.render('Detective CODE Game', False, 'White')
text_collide = text_font_collide.render('CoLLiDE!!', False, 'Red')

# даём название окну игры
pygame.display.set_caption('Detective CODE Game')

# объявляем переменную-флаг для цикла игры
game = True

# запускаем бесконечный цикл
while game:
   # получаем список возможных действий игрока
   for event in pygame.event.get():
       # если пользователь нажал на крестик закрытия окна
       if event.type == pygame.QUIT:
           # выключаем цикл
           pygame.quit()
           # добавляем корректное завершение работы
           exit()

       # получаем список всех нажатых клавиш
       keys = pygame.key.get_pressed()

       # если нажата клавиша вверх, двигаем картинку вверх
       if keys[pygame.K_UP]:
           hero_rect.top -= 20
       # если нажата клавиша вниз, двигаем картинку вниз
       if keys[pygame.K_DOWN]:
           hero_rect.top += 20

   # размещаем все поверхности на нашем экране
   screen.blit(back, (0, 0))
   screen.blit(hero, hero_rect)
   screen.blit(candle, candle_rect)
   screen.blit(box, box_rect)
   screen.blit(pot, pot_rect)
   back.blit(text_surface, (250, 15))

   # запускаем движение всех предметов
   candle_rect.left -= 4
   # когда подсвечник пересёк половину экрана,
   # меняем сигнальную переменную для чайника и начинаем его движение
   if candle_rect.left <= 400:
       pot_flag = True
   if pot_flag:
       pot_rect.left -= 4

   # когда чайник пересёк половину экрана,
   # меняем сигнальную переменную для ящика и начинаем его движение
   if pot_rect.left <= 400:
       box_flag = True
   if box_flag:
       box_rect.left -= 4

   # обнуляем начальные координаты, когда правая грань
   # скрылась за границей экрана
   if candle_rect.right <= 0:
       candle_rect.left = 800
   if pot_rect.right <= 0:
       pot_rect.left = 800
   if box_rect.right <= 0:
       box_rect.left = 1000

# выводим сообщения о столкновении
if hero_rect.colliderect(candle_rect) or hero_rect.colliderect(pot_rect) or hero_rect.colliderect(box_rect):
   screen.blit(text_collide, (200, 165))

   # обновляем экран игры
   pygame.display.update()
   # добавляем к таймеру количество fps для частоты обновления основного цикла
   clock.tick(fps)

Что мы сделали в прошлый раз

Чтобы было проще разобраться в том, что будет дальше, напомним логику работы:

  • Добавляем все зависимости и устанавливаем первоначальные настройки: включаем модуль pygame, устанавливаем размеры главного окна и частоту обновления кадров.
  • Подгружаем ассеты: файлы изображений и шрифтов.
  • Расставляем ассеты по координатам. 
  • Запускаем бесконечный цикл, внутри которого происходит весь игровой процесс.
  • Начинаем бесконечное движение предметов справа налево, а игрок может передвигаться вверх и вниз стрелками на клавиатуре.
  • Если на игрока налетает предмет, появляется сообщение о столкновении, но игра всё равно продолжается.

Дальше мы будем работать с этим же кодом — допиливать его и добавлять всё, что нам нужно для полноценной игры.

Что нужно для запуска проекта

Кроме самого Python, нам понадобится модуль pygame. Его можно установить такой командой в терминале:

pip install pygame

Ещё нам понадобятся файлы изображений и шрифта. Их нужно положить в ту же папку, где лежит python-файл. Мы для проекта брали ассеты, которые распространяются по свободной лицензии:

teapot.png

candlestick.png

detective.png

wooden_box.png

code_game_back_floor.jpg

code_game_back.jpg

Шрифт press-start

Что сделаем сегодня

Чтобы наш проект превратится в полноценную игру, добавим в него несколько новых механик:

Главное окно игры. Сделаем главное меню, с которого начинается игра, а при проигрыше игрок будет возвращаться на него.

Можно будет проиграть. В предыдущей части даже при столкновении с предметом можно было играть дальше, теперь на этом игра будет заканчиваться. Всё как в жизни. 

Добавим возможность перезапуска, чтобы можно было начать игру сначала.

Игра будет считать наш прогресс — как далеко мы продвинулись. За очки возьмём количество секунд, которое удастся провести без столкновения.

Движения героя будут более плавными. Сейчас он перемещается за одно нажатие клавиши на 20 пикселей вверх или вниз, и неважно, зажата клавишу одну секунду или одну минуту. Сделаем так, чтобы движение при зажатых клавишах было постоянным.

У игрока будут границы передвижения. В старом коде модель игрока могла уйти за границы экрана, в этой версии мы это поправим.

Теперь пройдём по всему коду сверху вниз. Те места, которые не изменились с прошлой части, кратко напомним, а на всех нововведениях остановимся подробнее.

Устанавливаем начальные настройки

Сначала импортируем зависимости: нам понадобится pygame, системный модуль sys для возможности окончания игры и модуль time для остановки времени на несколько секунд:

# импортируем зависимости и дополнительные модули
import pygame
from sys import exit
import time

Подключаем pygame:

# включаем модуль pygame
pygame.init()

Задаём основные настройки: создаём основное окно игры, устанавливаем количество кадров в секунду и создаём счётчик времени:

# объявляем ширину и высоту экрана
width = 800
height = 400
# создаём экран игры
screen = pygame.display.set_mode((width, height))
# устанавливаем количество кадров в секунду
fps = 60
# создаём объект таймера
clock = pygame.time.Clock()

👉 Теперь нам понадобятся новые счётчики: 

  • start_time, переменная для сохранения общего количества времени начиная со старта скрипта;
  • final_score, куда мы будем передавать количество секунд, которое удалось продержаться игроку в последнем раунде.

# добавляем счётчики для подсчёта времени в игре - это будут наши очки
start_time = 0
final_score = 0

Загружаем все изображения в переменные и делаем так, чтобы pygame конвертировал их в удобный для себя формат:

# загружаем в переменные картинки из папки с нашим файлом
back_main_screen = pygame.image.load('code_game_back.jpg').convert()
back = pygame.image.load('code_game_back_floor.jpg').convert()
hero = pygame.image.load('detective.png').convert_alpha()
pot = pygame.image.load('teapot.png').convert_alpha()
candle = pygame.image.load('candlestick.png').convert_alpha()
box = pygame.image.load('wooden_box.png').convert_alpha()

Задаём название окну игры:

# даём название окну игры
pygame.display.set_caption('Detective CODE Game')

👉 Ещё мы добавим новую переменную-флаг для отслеживания состояния игры. Когда игра активна, в игрока летят предметы, а когда неактивна — показывается главное меню.

# объявляем переменную-флаг для цикла игры
game = False

Создаём текстовые объекты

Любой текст в pygame создаётся в 4 этапа.

Раз: указываем в переменной шрифт и размер:

text_font = pygame.font.Font('prstartk.ttf', 15)

Два: используя первую переменную, пишем нужный текст с указанием сглаживания и цвета:

text_surface = text_font.render('Detective CODE Game', False, 'White')

Три: размещаем текст в прямоугольной рамке и указываем координаты этой рамки относительно точки привязки. Прямоугольники — один из основных инструментов в pygame. Любое изображение можно поместить в прямоугольник, описанный по границам картинки. После этого изображение отслеживается через этот прямоугольник по одной из 9 точек, включая центр:

Например, так мы говорим программе, что центр прямоугольника с нашим текстом находится по координатам 400 пикселей от левого края экрана и 30 пикселей сверху:

text_surface = text_font.render('Detective CODE Game', False, 'White')

Четыре: размещаем эту рамку на главном экране или на другом объекте, который уже показывается на главном экране:

back.blit(text_surface, text_name_rect)

Нам понадобится выводить текст на экран в нескольких местах:

  • в главном меню, чтобы предложить начать игру;
  • название игры в игровой сцене;
  • при подсчёте очков в игре;
  • при выводе очков после окончания игры;
  • в сообщении об окончании игры при столкновении с предметом.

Создаём объекты для каждого текста:

# создаём объекты текста: в первой строчке задаём настройки шрифта,
# во второй сам текст и его цвет, в третьей — помещаем текст
# в прямоугольную рамку и размещаем на заданных координатах

# текст с названием игры
text_font = pygame.font.Font('prstartk.ttf', 15)
text_surface = text_font.render('Detective CODE Game', False, 'White')
text_name_rect = text_surface.get_rect(center=(400, 30))

# текст с сообщением о столкновении
text_font_collide = pygame.font.Font('prstartk.ttf', 50)
text_collide = text_font_collide.render('CoLLiDE!!', False, 'Red')
text_collide_rect = text_collide.get_rect(center=(400, 200))

# текст главного меню
text_font_new_game = pygame.font.Font('prstartk.ttf', 20)
text_newgame_str1 = text_font_new_game.render('If you want to start,', False, 'Green')
text_newgame_rect1 = text_newgame_str1.get_rect(center=(400, 325))
text_newgame_str2 = text_font_new_game.render('press space', False, 'Green')
text_newgame_rect2 = text_newgame_str2.get_rect(center=(400, 350))

# текст для подсчёта очков
text_font_score = pygame.font.Font('prstartk.ttf', 15)
# текст для вывода очков при окончании игры
text_ts_font = pygame.font.Font('prstartk.ttf', 20)

Для подсчёта очков пока что задаём только настройки шрифта, а с самим текстом поработаем ниже. 

Пишем функцию подсчёта очков

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

При перезапуске игры время должно обнуляться, поэтому нам нужно где-то сохранять общее время в игре с момента первого запуска — используем для этого переменную start_time. А после этого отдельно посчитаем, сколько времени прошло за последнюю игру — и сделаем это с помощью переменной current_time. Чтобы посчитать время последнего раунда, нужно будет из общего времени вычесть отыгранное за предыдущие попытки.

Теперь, когда со всем разобрались, создаём объект текста, размещаем его в прямоугольнике и отрисовываем этот прямоугольник на главном экране:

# функция подсчёта очков
def display_score():
   # получаем время текущей игры: от общего времени в игре мы
   # отнимаем время, сыгранное за время запуска скрипта
   current_time = pygame.time.get_ticks() - start_time
   # создаём объект текста количества очков — сыгранное время
   score_surface = text_font_score.render(f'{current_time}', False, 'Purple')
   # помещаем текст с количеством очков в прямоугольник
   score_rect = score_surface.get_rect(bottomright=(795, 395))
   # размещаем прямоугольник на поверхности
   screen.blit(score_surface, score_rect)

Добавляем перезапуск игры

В этот раз мы добавим в игру возможность проигрыша и повторной игры. А значит, нужно продумать, как можно осуществить перезапуск.

Если игру просто останавливать в момент столкновения, а потом возвращать в активное состояние, всё продолжится с момента остановки, как будто мы просто поставили её на паузу. Поэтому нам понадобится функция обнуления:

# Функция для сброса начальных параметров
def reset_game():

Ещё нужен доступ к координатам моделей игрока и предметов и флагам состояния игры. А значит, их нужно добавить в область видимости функции:

# делаем видимыми переменные: объекты всех видимых моделей,
# отметки о движении предметов и главный флаг запущенной игры
global hero_rect, pot_rect, candle_rect, box_rect, pot_flag, box_flag, game

Устанавливаем начальные координаты для всех движущихся изображений:

# объявляем начальные координаты для всех объектов
hero_x_pos = 75
hero_y_pos = 180
candle_x_pos = 900
candle_y_pos = 70
box_x_pos = 900
box_y_pos = 200
pot_x_pos = 900
pot_y_pos = 345

Теперь используем эти координаты и положим все изображения в прямоугольные рамки:

# создаём рамки прямоугольников
hero_rect = hero.get_rect(center=(hero_x_pos, hero_y_pos))
pot_rect = pot.get_rect(center=(pot_x_pos, pot_y_pos))
candle_rect = candle.get_rect(center=(candle_x_pos, candle_y_pos))
box_rect = box.get_rect(center=(box_x_pos, box_y_pos))

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

# сбрасываем флаги и состояние игры
pot_flag = False
box_flag = False

Чтобы расставить всех по местам и установить начальное состояние игры, запускаем готовую функцию:

# запускаем функцию начального состояния игры
reset_game()

Пока что при запуске скрипта мы увидим только сообщение в консоли, которое означает, что pygame установлен и работает нормально:

Hello from the pygame community. https://www.pygame.org/contribute.html

Всё дело в том, что мы ещё не написали ни одной команды с главным экраном. Этим займёмся в основном цикле игры.

Запускаем игру и добавляем перезапуск

Чтобы игра работала, нам нужен бесконечный цикл, в котором будет работать основная игровая логика: управление персонажем, движение персонажа и предметов, подсчёт очков, окончание игры.

Запускаем цикл:

# запускаем бесконечный цикл
while True:

Первым делом добавляем проверку условия if, чтобы можно было закрыть главное окно, нажав на крестик в углу:

    # получаем список возможных действий игрока
    for event in pygame.event.get():
        # если пользователь нажал на крестик закрытия окна
        if event.type == pygame.QUIT:
            # выключаем цикл
            pygame.quit()
            # добавляем корректное завершение работы
            exit()

Добавим перезапуск из основного меню. Для этого должны одновременно выполняться два условия:

  • игра неактивна, то есть переменная game равна False;
  • нажат пробел — сигнал о том, что игрок хочет начать заново.

Сначала пишем проверку условий, а главный экран отрисуем в самом конце:

# если флаг игры неактивен (игра окончена), при этом нажата клавиша пробела:
if not game and event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
   # то сбрасываем состояние игры до начального состояния...
   reset_game()
   # устанавливаем флаг игры на активный (запущена)...
   game = True
   # и получаем общее отыгранное время
   start_time = pygame.time.get_ticks()

Делаем плавные перемещения героя в границах экрана

В прошлой статье мы сделали управление персонажем, но у этого механизма было два недостатка:

  • Герой мог скрыться за границами экрана.
  • По одному нажатию на кнопку герой делал одно движение и останавливался. Чтобы увернуться от предмета, приходилось нажимать на клавишу несколько раз.

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

Обновлённая версия движения будет работать только в активной игре. Для этого всю механику запускаем по условию if game — оно сработает, только если переменная-флаг game равна True.

Когда изображение помещено в прямоугольник, его можно отслеживать не только по точкам привязки, но и по сторонам-границам. Мы будем отслеживать координаты верхней и нижней границ — это hero_rect.top и hero_rect.bottom:

# если флаг игры активен (запущена)
if game:
   # получаем список всех зажатых клавиш
   keys = pygame.key.get_pressed()
   # если зажата клавиша вверх, двигаем игрока вверх
   if keys[pygame.K_UP]:
       hero_rect.top -= 5
       # если модель игрока достигла верхней границы, останавливаем движение
       if hero_rect.top <= 0:
           hero_rect.top = 0
   # если зажата клавиша вниз, двигаем игрока вниз
   if keys[pygame.K_DOWN]:
       hero_rect.top += 5
       # если модель игрока достигла нижней границы, останавливаем движение
       if hero_rect.bottom >= 400:
           hero_rect.bottom = 400

Включаем движение предметов

Движущиеся предметы нужны только в активной игре, поэтому эта часть будет продолжением условия if game.

Схема запуска предметов такая:

  • отрисовываем все картинки на главном экране командой-методом blit;
  • используя переменные-флаги pot_flag и box_flag, заставляем предметы поочерёдно лететь в главного героя;
  • когда предметы скрываются за левой границей экрана, мы возвращаем их за правую границу, создавая ощущение, что они появляются за кадром постоянно.

# размещаем все поверхности на главном экране
screen.blit(back, (0, 0))
screen.blit(hero, hero_rect)
screen.blit(candle, candle_rect)
screen.blit(box, box_rect)
screen.blit(pot, pot_rect)
back.blit(text_surface, text_name_rect)

# запускаем движение всех предметов
candle_rect.left -= 4
# когда подсвечник пересёк половину экрана,
# меняем сигнальную переменную для чайника и начинаем его движение
if candle_rect.left <= 400:
   pot_flag = True
if pot_flag:
   pot_rect.left -= 4

# когда чайник пересёк половину экрана,
# меняем сигнальную переменную для ящика и начинаем его движение
if pot_rect.left <= 400:
   box_flag = True
if box_flag:
   box_rect.left -= 4

# обнуляем начальные координаты, когда правая грань
# скрылась за границей экрана
if candle_rect.right <= 0:
   candle_rect.left = 800
if pot_rect.right <= 0:
   pot_rect.left = 800
if box_rect.right <= 0:
   box_rect.left = 1000

Окончание игры

Раньше игра была бесконечной, а в этот раз мы сделаем так, чтобы попадание предмета в нашего персонажа приводило к проигрышу.

Внутри проверки if game добавляем ещё одну проверку — на столкновение рамок изображения главного героя с любым из трёх предметов:

# если модель игрока столкнулась с одним из предметов...
if (hero_rect.colliderect(candle_rect) or
   hero_rect.colliderect(pot_rect) or
   hero_rect.colliderect(box_rect)):

После столкновения нужно сообщить игроку, что в него попали. Сообщение мы уже подготовили заранее, нам нужно только вывести его на экран такой командой:

# то выводим сообщение о столкновении
screen.blit(text_collide, text_collide_rect)

Вместе с текстом о столкновении выведем на экран количество очков. 

Сначала подсчитываем, сколько полных секунд прошло с момента последнего запуска игры из главного меню. Pygame считает время в миллисекундах, поэтому для получения секунд нам понадобится разделить это число на 1000, используя оператор целочисленного деления //:

# вычисляем общее время последней игры в секундах
final_score = (pygame.time.get_ticks() - start_time) // 1000

Теперь значение переменной final_score нужно вывести на экран, а для этого понадобится новый текст. У нас уже есть готовые настройки шрифта, поэтому для вывода финальных очков осталось сделать три шага: написать текст, положить его в прямоугольник с координатами и разместить на поверхности главного экрана screen:

# создаём объект текста с выводом количества очков
text_ts_text = text_ts_font.render(f'Total time: {final_score} sec', False, 'White')
# помещаем текст в прямоугольник
text_ts_rect = text_ts_text.get_rect(center=(400, 250))
# размещаем прямоугольник текста на главном экране
screen.blit(text_ts_text, text_ts_rect)

Чтобы игрок увидел сообщение, экран игры нужно обновить. Обычно экран обновляется в конце цикла, но мы до него ещё не дошли, поэтому обновляем отрисовку всех новых объектов командой pygame.display.flip(), а командой time.sleep(3) дадим пользователю 3 секунды посмотреть на набранные очки.

В конце поменяем флаг активной игры на False:

# обновляем экран, чтобы появилось сообщение
# об окончании игры после столкновения
pygame.display.flip()
# делаем паузу в 3 секунды
time.sleep(3)
# переставляем флаг игры в неактивный (окончена)
game = False

Последней строкой блока активной игры, который начался с if game, будет постоянный вывод текущих очков на экран. Запускаем готовую функцию командой:

# выводим на экран финальные очки
display_score()

Показываем главный экран

Эта часть будет показываться всегда, если флаг игры game установлен на False. Мы предлагаем запустить игру нажатием на кнопку пробела.

Сейчас мы всё ещё внутри основного цикла while True., поэтому добавим проверку после else — что если game не равен True — и в этой ветке делаем такое:

  • Выводим на экран заставку.
  • Рисуем залитый чёрным цветом прямоугольник, сверху которого расположим текст для лучшей читаемости.
  • Выводим две строки текста, настройки для которого мы уже подготовили в начале скрипта. В тексте предлагаем нажать пробел для начала игры.

# если флаг игры не активен (окончена):
else:
   # выводим на экран фон главного окна игры
   screen.blit(back_main_screen, (0, 0))
   # и чёрную плашку-прямоугольник, поверх которой разместим текст
   pygame.draw.rect(back_main_screen, 'Black', (100, 300, 600, 80))
   # выводим на экран две строки текста с предложением начать игру
   screen.blit(text_newgame_str1, text_newgame_rect1)
   screen.blit(text_newgame_str2, text_newgame_rect2)

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

# обновляем экран игры
pygame.display.update()
# добавляем к таймеру количество fps для частоты обновления основного цикла
clock.tick(fps)

Всё, можно запускать и играть. Мы молодцы.

Что сделаем в следующий раз

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

А ещё на нашей механике уже вполне можно сделать клон несложной казуальной игры уровня «Зомби против растений» или Flappy Bird. В следующий раз этим и займёмся.

# импортируем зависимости и дополнительные модули
import pygame
from sys import exit
import time

# включаем модуль pygame
pygame.init()

# объявляем ширину и высоту экрана
width = 800
height = 400
# создаём экран игры
screen = pygame.display.set_mode((width, height))
# устанавливаем количество кадров в секунду
fps = 60
# создаём объект таймера
clock = pygame.time.Clock()

# добавляем счётчики для подсчёта времени в игре — это будут наши очки
start_time = 0
final_score = 0

# загружаем в переменные картинки из папки с нашим файлом
back_main_screen = pygame.image.load('code_game_back.jpg').convert()
back = pygame.image.load('code_game_back_floor.jpg').convert()
hero = pygame.image.load('detective.png').convert_alpha()
pot = pygame.image.load('teapot.png').convert_alpha()
candle = pygame.image.load('candlestick.png').convert_alpha()
box = pygame.image.load('wooden_box.png').convert_alpha()

# даём название окну игры
pygame.display.set_caption('Detective CODE Game')

# объявляем переменную-флаг для цикла игры
game = False

# создаём объекты текста: в первой строчке задаём настройки шрифта,
# во второй сам текст и его цвет, в третьей — помещаем текст
# в прямоугольную рамку и размещаем на заданных координатах

# текст с названием игры
text_font = pygame.font.Font('prstartk.ttf', 15)
text_surface = text_font.render('Detective CODE Game', False, 'White')
text_name_rect = text_surface.get_rect(center=(400, 30))

# текст с сообщением о столкновении
text_font_collide = pygame.font.Font('prstartk.ttf', 50)
text_collide = text_font_collide.render('CoLLiDE!!', False, 'Red')
text_collide_rect = text_collide.get_rect(center=(400, 200))

# текст главного меню
text_font_new_game = pygame.font.Font('prstartk.ttf', 20)
text_newgame_str1 = text_font_new_game.render('If you want to start,', False, 'Green')
text_newgame_rect1 = text_newgame_str1.get_rect(center=(400, 325))
text_newgame_str2 = text_font_new_game.render('press space', False, 'Green')
text_newgame_rect2 = text_newgame_str2.get_rect(center=(400, 350))

# текст для подсчёта очков
text_font_score = pygame.font.Font('prstartk.ttf', 15)
# текст для вывода очков при окончании игры
text_ts_font = pygame.font.Font('prstartk.ttf', 20)


# функция подсчёта очков
def display_score():
   # получаем время текущей игры: от общего времени в игре мы
   # отнимаем время, сыгранное за время запуска скрипта
   current_time = pygame.time.get_ticks() - start_time
   # создаём объект текста количества очков — сыгранное время
   score_surface = text_font_score.render(f'{current_time}', False, 'Purple')
   # помещаем текст с количеством очков в прямоугольник
   score_rect = score_surface.get_rect(bottomright=(795, 395))
   # размещаем прямоугольник на поверхности
   screen.blit(score_surface, score_rect)


# Функция для сброса начальных параметров
def reset_game():
   # делаем видимыми переменные: объекты всех видимых моделей,
   # отметки о движении предметов и главный флаг запущенной игры
   global hero_rect, pot_rect, candle_rect, box_rect, pot_flag, box_flag, game

   # объявляем начальные координаты для всех объектов
   hero_x_pos = 75
   hero_y_pos = 180
   candle_x_pos = 900
   candle_y_pos = 70
   box_x_pos = 900
   box_y_pos = 200
   pot_x_pos = 900
   pot_y_pos = 345

   # создаём рамки прямоугольников
   hero_rect = hero.get_rect(center=(hero_x_pos, hero_y_pos))
   pot_rect = pot.get_rect(center=(pot_x_pos, pot_y_pos))
   candle_rect = candle.get_rect(center=(candle_x_pos, candle_y_pos))
   box_rect = box.get_rect(center=(box_x_pos, box_y_pos))

   # сбрасываем флаги и состояние игры
   pot_flag = False
   box_flag = False


# запускаем функцию начального состояния игры
reset_game()

# запускаем бесконечный цикл
while True:
   # получаем список возможных действий игрока
   for event in pygame.event.get():
       # если пользователь нажал на крестик закрытия окна
       if event.type == pygame.QUIT:
           # выключаем цикл
           pygame.quit()
           # добавляем корректное завершение работы
           exit()

       # если флаг игры неактивен (игра окончена), при этом нажата клавиша пробела:
       if not game and event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
           # то сбрасываем состояние игры до начального состояния...
           reset_game()
           # устанавливаем флаг игры на активный (запущена)...
           game = True
           # и получаем общее отыгранное время
           start_time = pygame.time.get_ticks()

   # если флаг игры активен (запущена)
   if game:
       # получаем список всех зажатых клавиш
       keys = pygame.key.get_pressed()
       # если зажата клавиша вверх, двигаем игрока вверх
       if keys[pygame.K_UP]:
           hero_rect.top -= 5
           # если модель игрока достигла верхней границы, останавливаем движение
           if hero_rect.top <= 0:
               hero_rect.top = 0
       # если зажата клавиша вниз, двигаем игрока вниз
       if keys[pygame.K_DOWN]:
           hero_rect.top += 5
           # если модель игрока достигла нижней границы, останавливаем движение
           if hero_rect.bottom >= 400:
               hero_rect.bottom = 400

       # размещаем все поверхности на главном экране
       screen.blit(back, (0, 0))
       screen.blit(hero, hero_rect)
       screen.blit(candle, candle_rect)
       screen.blit(box, box_rect)
       screen.blit(pot, pot_rect)
       back.blit(text_surface, text_name_rect)

       # запускаем движение всех предметов
       candle_rect.left -= 4
       # когда подсвечник пересёк половину экрана,
       # меняем сигнальную переменную для чайника и начинаем его движение
       if candle_rect.left <= 400:
           pot_flag = True
       if pot_flag:
           pot_rect.left -= 4

       # когда чайник пересёк половину экрана,
       # меняем сигнальную переменную для ящика и начинаем его движение
       if pot_rect.left <= 400:
           box_flag = True
       if box_flag:
           box_rect.left -= 4

       # обнуляем начальные координаты, когда правая грань
       # скрылась за границей экрана
       if candle_rect.right <= 0:
           candle_rect.left = 800
       if pot_rect.right <= 0:
           pot_rect.left = 800
       if box_rect.right <= 0:
           box_rect.left = 1000

       # если модель игрока столкнулась с одним из предметов...
       if (hero_rect.colliderect(candle_rect) or
           hero_rect.colliderect(pot_rect) or
           hero_rect.colliderect(box_rect)):

           # то выводим сообщение о столкновении
           screen.blit(text_collide, text_collide_rect)
           # вычисляем общее время последней игры в секундах
           final_score = (pygame.time.get_ticks() - start_time) // 1000
           # создаём объект текста с выводом количества очков
           text_ts_text = text_ts_font.render(f'Total time: {final_score} sec', False, 'White')
           # помещаем текст в прямоугольник
           text_ts_rect = text_ts_text.get_rect(center=(400, 250))
           # размещаем прямоугольник текста на главном экране
           screen.blit(text_ts_text, text_ts_rect)

           # обновляем экран, чтобы появилось сообщение
           # об окончании игры после столкновения
           pygame.display.flip()
           # делаем паузу в 3 секунды
           time.sleep(3)
           # переставляем флаг игры в неактивный (окончена)
           game = False

       # выводим на экран финальные очки
       display_score()

   # если флаг игры не активен (окончена):
   else:
       # выводим на экран фон главного окна игры
       screen.blit(back_main_screen, (0, 0))
       # и чёрную плашку-прямоугольник, поверх которой разместим текст
       pygame.draw.rect(back_main_screen, 'Black', (100, 300, 600, 80))
       # выводим на экран две строки текста с предложением начать игру
       screen.blit(text_newgame_str1, text_newgame_rect1)
       screen.blit(text_newgame_str2, text_newgame_rect2)

   # обновляем экран игры
   pygame.display.update()
   # добавляем к таймеру количество fps для частоты обновления основного цикла
   clock.tick(fps)

Обложка:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Кирилл Климентьев

Соцсети:

Юлия Зубарева

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Еще по теме
hard
[anycomment]
Exit mobile version