Как сделать меню для игры на питоне
Перейти к содержимому

Как сделать меню для игры на питоне

  • автор:

Создание игр на Python 3 и Pygame: Часть 4

Это четвёртая из пяти частей туториала, посвящённого созданию игр с помощью Python 3 и Pygame. В третьей части мы углубились в сердце Breakout и узнали, как обрабатывать события, познакомились с основным классом Breakout и увидели, как перемещать разные игровые объекты.

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

Распознавание коллизий

В играх объекты сталкиваются друг с другом, и Breakout не является исключением. В основном с объектами сталкивается мяч. В методе handle_ball_collisions() есть встроенная функция под названием intersect() , которая используется для проверки того, ударился ли мяч об объект, и того, где он столкнулся с объектом. Она возвращает ‘left’, ‘right’, ‘top’, ‘bottom’ или None, если мяч не столкнулся с объектом.

def handle_ball_collisions(self): def intersect(obj, ball): edges = dict( left=Rect(obj.left, obj.top, 1, obj.height), right=Rect(obj.right, obj.top, 1, obj.height), top=Rect(obj.left, obj.top, obj.width, 1), bottom=Rect(obj.left, obj.bottom, obj.width, 1)) collisions = set(edge for edge, rect in edges.items() if ball.bounds.colliderect(rect)) if not collisions: return None if len(collisions) == 1: return list(collisions)[0] if 'top' in collisions: if ball.centery >= obj.top: return 'top' if ball.centerx < obj.left: return 'left' else: return 'right' if 'bottom' in collisions: if ball.centery >= obj.bottom: return 'bottom' if ball.centerx < obj.left: return 'left' else: return 'right'

Столкновение мяча с ракеткой

Когда мяч стукается об ракетку, он отскакивает. Если он ударяется о верхнюю часть ракетки, то отражается обратно вверх, но сохраняет тот же компонент горизонтальной скорости.

Но если он ударяется о боковую часть ракетки, то отскакивает в противоположную сторону (влево или вправо) и продолжает движение вниз, пока не столкнётся с полом. В коде используется функция intersect() .

# Удар об ракетку s = self.ball.speed edge = intersect(self.paddle, self.ball) if edge is not None: self.sound_effects['paddle_hit'].play() if edge == 'top': speed_x = s[0] speed_y = -s[1] if self.paddle.moving_left: speed_x -= 1 elif self.paddle.moving_left: speed_x += 1 self.ball.speed = speed_x, speed_y elif edge in ('left', 'right'): self.ball.speed = (-s[0], s[1])

Столкновение с полом

Когда ракетка пропускает мяч на пути вниз (или мяч ударяется об ракетку сбоку), то мяч продолжает падать и затем ударяется об пол. В этот момент игрок теряет жизнь и мяч создаётся заново, чтобы игра могла продолжаться. Игра завершается, когда у игрока заканчиваются жизни.

# Удар об пол if self.ball.top > c.screen_height: self.lives -= 1 if self.lives == 0: self.game_over = True else: self.create_ball()

Столкновение с потолком и стенами

Когда мяч ударяется об стены или потолок, он просто отскакивает от них.

# Удар об потолок if self.ball.top < 0: self.ball.speed = (s[0], -s[1]) # Удар об стену if self.ball.left < 0 or self.ball.right >c.screen_width: self.ball.speed = (-s[0], s[1])

Столкновение с кирпичами

Когда мяч ударяется об кирпич, это является основным событием игры Breakout — кирпич исчезает, игрок получает очко, мяч отражается назад и происходят ещё несколько событий (звуковой эффект, а иногда и спецэффект), которые я рассмотрю позже.

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

# Удар об кирпич for brick in self.bricks: edge = intersect(brick, self.ball) if not edge: continue self.bricks.remove(brick) self.objects.remove(brick) self.score += self.points_per_brick if edge in ('top', 'bottom'): self.ball.speed = (s[0], -s[1]) else: self.ball.speed = (-s[0], s[1])

Программирование игрового меню

В большинстве игр есть какой-нибудь UI. В Breakout есть простое меню с двумя кнопками, 'PLAY' и 'QUIT'. Меню отображается в начале игры и пропадает, когда игрок нажимает на 'PLAY'. Давайте посмотрим, как реализуются кнопки и меню, а также как они интегрируются в игру.

Создание кнопок

В Pygame нет встроенной библиотеки UI. Есть сторонние расширения, но для меню я решил создать свои кнопки. Кнопка — это игровой объект, имеющий три состояния: нормальное, выделенное и нажатое. Нормальное состояние — это когда мышь не находится над кнопкой, а выделенное состояние — когда мышь находится над кнопкой, но левая кнопка мыши ещё не нажата. Нажатое состояние — это когда мышь находится над кнопкой и игрок нажал на левую кнопку мыши.

Кнопка реализуется как прямоугольник с фоновым цветом и текст, отображаемый поверх него. Также кнопка получает функцию on_click (по умолчанию являющуюся пустой лямбда-функцией), которая вызывается при нажатии кнопки.

import pygame from game_object import GameObject from text_object import TextObject import config as c class Button(GameObject): def __init__(self, x, y, w, h, text, on_click=lambda x: None, padding=0): super().__init__(x, y, w, h) self.state = 'normal' self.on_click = on_click self.text = TextObject(x + padding, y + padding, lambda: text, c.button_text_color, c.font_name, c.font_size) def draw(self, surface): pygame.draw.rect(surface, self.back_color, self.bounds) self.text.draw(surface)

Кнопка обрабатывает собственные события мыши и изменяет своё внутреннее состояние на основании этих событий. Когда кнопка находится в нажатом состоянии и получает событие MOUSEBUTTONUP , это означает, что игрок нажал на кнопку, и вызывается функция on_click() .

def handle_mouse_event(self, type, pos): if type == pygame.MOUSEMOTION: self.handle_mouse_move(pos) elif type == pygame.MOUSEBUTTONDOWN: self.handle_mouse_down(pos) elif type == pygame.MOUSEBUTTONUP: self.handle_mouse_up(pos) def handle_mouse_move(self, pos): if self.bounds.collidepoint(pos): if self.state != 'pressed': self.state = 'hover' else: self.state = 'normal' def handle_mouse_down(self, pos): if self.bounds.collidepoint(pos): self.state = 'pressed' def handle_mouse_up(self, pos): if self.state == 'pressed': self.on_click(self) self.state = 'hover'

Свойство back_color , используемое для отрисовки фонового прямоугольника, всегда возвращает цвет, соответствующий текущему состоянию кнопки, чтобы игроку было ясно, что кнопка активна:

@property def back_color(self): return dict(normal=c.button_normal_back_color, hover=c.button_hover_back_color, pressed=c.button_pressed_back_color)[self.state]

Создание меню

Функция create_menu() создаёт меню с двумя кнопками с текстом 'PLAY' и 'QUIT'. Она имеет две встроенные функции, on_play() и on_quit() , которые она передаёт соответствующей кнопке. Каждая кнопка добавляется в список objects (для отрисовки), а также в поле menu_buttons .

def create_menu(self): for i, (text, handler) in enumerate((('PLAY', on_play), ('QUIT', on_quit))): b = Button(c.menu_offset_x, c.menu_offset_y + (c.menu_button_h + 5) * i, c.menu_button_w, c.menu_button_h, text, handler, padding=5) self.objects.append(b) self.menu_buttons.append(b) self.mouse_handlers.append(b.handle_mouse_event)

При нажатии кнопки PLAY вызывается функция on_play() , удаляющая кнопки из списка objects , чтобы они больше не отрисовывались. Кроме того, значения булевых полей, которые запускают начало игры — is_game_running и start_level — становятся равными True.

При нажатии кнопки QUIT is_game_running принимает значение False (фактически ставя игру на паузу), а game_over присваивается значение True, что приводит к срабатыванию последовательности завершения игры.

def on_play(button): for b in self.menu_buttons: self.objects.remove(b) self.is_game_running = True self.start_level = True def on_quit(button): self.game_over = True self.is_game_running = False

Отображение и сокрытие игрового меню

Отображение и сокрытие меню выполняются неявным образом. Когда кнопки находятся в списке objects , меню видимо; когда они удаляются, оно скрывается. Всё очень просто.

Можно создать встроенное меню с собственной поверхностью, которое рендерит свои подкомпоненты (кнопки и другие объекты), а затем просто добавлять/удалять эти компоненты меню, но для такого простого меню это не требуется.

Подводим итог

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

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

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

Как сделать меню в игре на Pygame

В своём коде я уже сделал само меню, то есть запускаеш игру, есть меню с 2 пунктами "игра" и "выход".
Но как сделать так, что бы после проиграша(0 жизней) открывалось меню, где вместо пункта "игра" был бы пункт "играть заново".
Вот код

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
import pygame, sys def Intersect(x1, x2, y1, y2, db1, db2): if (x1 > x2 - db1) and (x1  x2 + db2) and (y1 > y2 - db1) and (y1  y2 + db2): return 1 else: return 0 window = pygame.display.set_mode((800, 670)) pygame.display.set_caption('Archer') screen = pygame.Surface((800, 640)) info = pygame.Surface((800, 30)) class Sprite: def __init__(self, xpos, ypos, filename): self.x = xpos self.y = ypos self.bitmap = pygame.image.load(filename) self.bitmap.set_colorkey((0,0,0)) def render(self): screen.blit(self.bitmap,(self.x,self.y)) class Menu: def __init__(self, punkts = [400, 350, u'Punkt', (250,250,30), (250,30,250)]): self.punkts = punkts def render(self, poverhnost, font, num_punkt): for i in self.punkts: if num_punkt == i[5]: poverhnost.blit(font.render(i[2], 1, i[4]), (i[0], i[1]-30)) else: poverhnost.blit(font.render(i[2], 1, i[3]), (i[0], i[1]-30)) def menu(self): done = True font_menu = pygame.font.Font(None, 50) pygame.key.set_repeat(0,0) pygame.mouse.set_visible(True) punkt = 0 while done: info.fill((0, 100, 200)) screen.fill((0, 100, 200)) mp = pygame.mouse.get_pos() for i in self.punkts: if mp[0]>i[0] and mp[0]i[0]+155 and mp[1]>i[1] and mp[1]i[1]+50: punkt =i[5] self.render(screen, font_menu, punkt) for e in pygame.event.get(): if e.type == pygame.QUIT: sys.exit() if e.type == pygame.KEYDOWN: if e.key == pygame.K_ESCAPE: sys.exit() if e.key == pygame.K_UP: if punkt > 0: punkt -= 1 if e.key == pygame.K_DOWN: if punkt  len(self.punkts)-1: punkt += 1 if e.type == pygame.MOUSEBUTTONDOWN and e.button == 1: if punkt == 0: done = False elif punkt == 1: exit() window.blit(info, (0, 0)) window.blit(screen, (0, 30)) pygame.display.flip() hero = Sprite(400,600,'hero.png') meta = Sprite(300,0,'object.png') arrow = Sprite(-100,600,'arrow.png') meta.move = True hero.move_right = True hero.move_down = True arrow.push = False meta.speed = 0 arrow.speed = 0.5 score = 0 lifes = 3 pygame.font.init() score_f = pygame.font.SysFont('Arial', 32) lifes_f = pygame.font.SysFont('Arial', 32) end = pygame.font.SysFont('Times new roman', 80) again = pygame.font.SysFont('Times new roman', 40) punkts = [(350, 300, u'Play', (11, 0, 77), (250,250,30), 0), (350, 340, u'Exit', (11, 0, 77), (250,250,30), 1)] game = Menu(punkts) game.menu() done = True pygame.key.set_repeat(1, 1) while done: for e in pygame.event.get(): if e.type == pygame.QUIT: done = False if e.type == pygame.KEYDOWN: if e.key == pygame.K_ESCAPE: game.menu() if e.type == pygame.MOUSEMOTION: pygame.mouse.set_visible(False) p = pygame.mouse.get_pos() if p[0] > 0 and p[0]  760: hero.x = p[0] if p[1]  600 and p[1] > 200: hero.y = p[1] if e.type == pygame.MOUSEBUTTONDOWN: if e.button == 1: if arrow.push == False: arrow.x = hero.x + 4 arrow.y = hero.y arrow.push = True y = -300 x = -200 if meta.move == True: meta.x += meta.speed if meta.x > 760: meta.move = False else: meta.x -= meta.speed if meta.x  -10: meta.move = True if arrow.y  0: arrow.push = False lifes -= 1 if arrow.push == False: arrow.x = -100 arrow.y = 600 else: arrow.y -= arrow.speed if Intersect(arrow.x, meta.x, arrow.y, meta.y, 5, 40) == True: arrow.push = False meta.speed += 0.2 arrow.speed += 0.2 score += 1 if lifes  0: y = 300 x = 450 if e.type == pygame.KEYDOWN: if e.key == pygame.K_SPACE: lifes = 3 score = 0 meta.speed = 0 else: exit() screen.fill((50, 50, 50)) info.fill((45,80,45)) arrow.render() meta.render() hero.render() screen.blit(end.render ('Game Over', 1, (210, 120, 200)), (200, y)) screen.blit(again.render ('Try again?(press Space)', 1, (210, 120, 200)), (200,x)) info.blit(lifes_f.render ('Lifes: ' +str(lifes), 1, (210, 120, 200)), (600, 0)) info.blit(score_f.render ('Score: ' +str(score), 1, (210, 120, 200)), (5, 0)) window.blit(info, (0,0)) window.blit(screen, (0, 30)) pygame.display.flip() pygame.time.delay(5)

P.S Я уже пробвал сделать это, но розмещении if lifes < 0. в цклу программы открываеться меню, всё как надо, но пункт "играть заново" не работает. Помогите плз.

Создание игр с Python + Pygame. Урок 7. Стартовое меню

Как стать программистом? Python, Java, FrontEnd или .NET – что выбрать?

PyCharm с нуля. Лучшая IDE для Python разработки

Как составить резюме, чтобы вас пригласили на собеседование

Создаём игру-бестселлер Тетрис на Python

Как правильно составить резюме для поиска работы в международной IT-компании

Пишем игру Танки 2D на Pygame

Как новичкам найти работу во время войны? Реальный опыт и отзывы

Как стать Full Stack Python разработчиком

Базовые инструменты для анализа данных на Python

Создание игры-шутера на Python с нуля

Разъяснение создания стартового меню. Объяснение цикла while, называемого сценой. Толкование отображения действий в цикле, визуализации на экране как сцены. Разъяснение сцены меню, сцены игры, сцены настроек, примеров. Демонстрация реализации меню в отдельном цикле while, схемы организации сцен, самого простого способа реализации меню в игре.

Покупай подписку с доступом ко всем курсам и сервисам

Библиотека современных IT знаний в удобном формате

Выбирай свой вариант подписки в зависимости от задач, стоящих перед тобой. Но если нужно пройти полное обучение с нуля до уровня специалиста, то лучше выбирать Базовый или Премиум. А для того чтобы изучить 2-3 новые технологии, или повторить знания, готовясь к собеседованию, подойдет Пакет Стартовый.

  • Все видеокурсы на 3 месяца
  • Тестирование по 10 курсам
  • Проверка 5 домашних заданий
  • Консультация с тренером 30 мин

Halloween

  • Все видеокурсы на 1 год
  • Тестирование по 24 курсам
  • Проверка 20 домашних заданий
  • Консультация с тренером 120 мин
  • Скачивание видео уроков
  • Возможность приостановки обучения
  • Все видеокурсы на 6 месяцев
  • Тестирование по 16 курсам
  • Проверка 10 домашних заданий
  • Консультация с тренером 60 мин

Виджет Menu в tkinter

Меню – это виджет, который присутствует во многих пользовательских приложениях. Находится оно под строкой заголовка и представляет собой выпадающие списки под словами-пунктами меню. Пункты конечных списков представляют собой команды, обычно выполняющие какие-либо действия или открывающие диалоговые окна.

В tkinter экземпляр меню создается от класса Menu , далее его надо привязать к виджету, на котором оно будет расположено. Обычно таковым выступает главное окно приложения. Его опции menu присваивается экземпляр Menu через имя связанной с экземпляром переменной.

from tkinter import * root = Tk() mainmenu = Menu(root) root.config(menu=mainmenu) root.mainloop() 

Если выполнить данный код, то никакого меню вы не увидите. Только тонкую полоску под заголовком окна, ведь ни одного пункта меню не было создано. Метод add_command добавляет пункт меню:

… mainmenu.add_command(label='Файл') mainmenu.add_command(label='Справка')

Панель главного меню

В данном случае "Файл" и "Справка" – это команды. К ним можно добавить опцию command , связав тем самым с какой-либо функцией-обработчиком клика. Хотя такой вариант меню имеет право на существование, в большинстве приложений панель меню содержит выпадающие списки команд, а сами пункты на панели командами по сути не являются. Клик по ним приводит лишь к раскрытию соответствующего списка.

В Tkinter проблема решается созданием новых экземпляров Menu и подвязыванием их к главному меню с помощью метода add_cascade .

from tkinter import * root = Tk() mainmenu = Menu(root) root.config(menu=mainmenu) filemenu = Menu(mainmenu, tearoff=0) filemenu.add_command(label="Открыть. ") filemenu.add_command(label="Новый") filemenu.add_command(label="Сохранить. ") filemenu.add_command(label="Выход") helpmenu = Menu(mainmenu, tearoff=0) helpmenu.add_command(label="Помощь") helpmenu.add_command(label="О программе") mainmenu.add_cascade(label="Файл", menu=filemenu) mainmenu.add_cascade(label="Справка", menu=helpmenu) root.mainloop()

Пункты меню

На основное меню mainmenu , добавляются не команды, а другие меню. У filemenu и helpmenu в качестве родительского виджета указывается не root , а mainmenu . Команды добавляются только к дочерним меню. Значение 0 опции tearoff отключает возможность открепления подменю, иначе его можно было бы делать плавающим кликом мыши по специальной линии. В случае tearoff=0 она отсутствует.

Точно также можно подвязывать дочерние меню к filemenu и helpmenu , создавая многоуровневые списки пунктов меню.

… helpmenu = Menu(mainmenu, tearoff=0) helpmenu2 = Menu(helpmenu, tearoff=0) helpmenu2.add_command(label="Локальная справка") helpmenu2.add_command(label="На сайте") helpmenu.add_cascade(label="Помощь",menu=helpmenu2) helpmenu.add_command(label="О программе")

Вложенное меню

Метод add_separator добавляет линию разделитель в меню. Используется для визуального разделения групп команд.

Добавление в меню сепаратора

В tkinter можно создать всплывающее меню, оно же контекстное (если настроить его появление по клику правой кнопкой мыши). Для этого экземпляр меню подвязывается не через опцию menu к родительскому виджету, а к меню применяется метод post , аргументами которого являются координаты того места, где должно появляться меню.

from tkinter import * def circle(): c.create_oval(x, y, x + 30, y + 30) def square(): c.create_rectangle(x, y, x + 30, y + 30) def triangle(): c.create_polygon(x, y, x - 15, y + 30, x + 15, y + 30, fill='white', outline='black') def popup(event): global x, y x = event.x y = event.y menu.post(event.x_root, event.y_root) x = 0 y = 0 root = Tk() c = Canvas(width=300, height=300, bg='white') c.pack() c.bind("", popup) menu = Menu(tearoff=0) menu.add_command(label="Круг", command=circle) menu.add_command(label="Квадрат", command=square) menu.add_command(label="Треугольник", command=triangle) root.mainloop()

Всплывающее меню

Здесь при клике на холсте правой кнопкой мыши в этой точке всплывает меню. При выборе пункта меню рисуется соответствующая фигура в этом же месте.

Практическая работа

Измените программу из практической работы предыдущего урока так, чтобы открытие и сохранение файлов выполнялось не через экземпляры Button , а через Menu . Команду очистки текстового поля поместите в контекстное меню.

Пример программы на Tkinter с меню

Курс с примерами решений практических работ: pdf-версия

X Скрыть Наверх

Tkinter. Программирование GUI на Python

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *