обзор
Это четвертая часть серии из пяти уроков, посвященных созданию игр с Python 3 и Pygame. В третьей части мы погрузились в сердце Breakout и узнали, как обрабатывать события, встретились с основным классом Breakout и увидели, как перемещать различные игровые объекты.
В этой части мы увидим, как обнаруживать столкновения и что происходит, когда мяч попадает в различные объекты, такие как весло, кирпичи, стены, потолок и пол. Наконец, мы рассмотрим важную тему игрового интерфейса и, в частности, как создать меню с нашими собственными пользовательскими кнопками.
Обнаружение столкновения
В играх вещи сталкиваются друг с другом. Прорыв ничем не отличается. В основном это мяч, который сталкивается с вещами. Метод handle_ball_collisions() имеет вложенную функцию intersect() , которая используется для проверки попадания мяча в объект и его попадания в объект. Он возвращает «влево», «вправо», «сверху», «снизу» или «Нет», если мяч не попал в объект.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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’
|
Удар по мячу с веслом
Когда мяч попадает на весло, он отскакивает. Если он достигнет верхней части весла, он отскочит назад, но останется с той же горизонтальной составляющей скорости.
Но если он коснется стороны весла, он отскочит к противоположной стороне (влево или вправо) и продолжит движение вниз, пока не достигнет пола. Код использует функцию пересечения ().
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
# Hit paddle
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])
|
Ударить по полу
Когда весло пропускает мяч на пути вниз (или если мяч ударяет по веслу сбоку), мяч будет продолжать падать и в конечном итоге попадет на пол. В этот момент игрок теряет жизнь, и мяч воссоздается, поэтому игра может продолжаться. Игра заканчивается, когда у игрока заканчиваются жизни.
|
1
2
3
4
5
6
7
|
# Hit floor
if self.ball.top > c.screen_height:
self.lives -= 1
if self.lives == 0:
self.game_over = True
else:
self.create_ball()
|
Удар по потолку и стенам
Когда мяч попадает в стену или потолок, он просто отскакивает назад.
|
1
2
3
4
5
6
7
|
# Hit ceiling
if self.ball.top < 0:
self.ball.speed = (s[0], -s[1])
# Hit wall
if self.ball.left < 0 or self.ball.right > c.screen_width:
self.ball.speed = (-s[0], s[1])
|
Ударяя кирпичи
Когда мяч попадает в кирпич, это главное событие в Breakout: кирпич исчезает, игрок получает очко, мяч отскакивает, и происходит несколько других вещей (звуковой эффект и, возможно, специальный эффект), о которых я расскажу потом.
Чтобы определить, попал ли кирпич, код проверяет, пересекается ли какой-либо из кирпичей с мячом:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
# Hit brick
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])
|
Программирование игрового меню
Большинство игр имеют некоторый интерфейс. Breakout имеет простое меню, в котором есть две кнопки «PLAY» и «QUIT». Меню появляется в начале игры и исчезает, когда игрок нажимает кнопку «ИГРАТЬ». Давайте посмотрим, как реализованы кнопки и меню и как они интегрируются в игру.
Создание кнопок
Pygame не имеет встроенной библиотеки пользовательского интерфейса. Существуют сторонние расширения, но я решил создать собственные кнопки для меню. Кнопка — это игровой объект, который имеет три состояния: нормальный, зависание и нажатие. Нормальное состояние — когда мышь не находится над кнопкой, а состояние наведения — когда мышь находится над кнопкой, но левая кнопка мыши не нажата. Состояние нажатия — когда мышь находится над кнопкой, а игрок нажал левую кнопку мыши.
Кнопка выполнена в виде прямоугольника с цветом фона и текстом, отображаемым поверх него. Кнопка также получает функцию on_click (по умолчанию функция noop lambda), которая вызывается при нажатии кнопки.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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() функция on_click() .
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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 , используемое для рисования фонового прямоугольника, всегда возвращает цвет, соответствующий текущему состоянию кнопки, поэтому игроку ясно, что кнопка активна:
|
1
2
3
4
5
|
@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 .
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
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, что приводит к завершению последовательности игры.
|
01
02
03
04
05
06
07
08
09
10
|
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 , меню отображается; когда они удалены, это скрыто. Так просто, как, что.
Можно создать вложенное меню со своей собственной поверхностью, которое отображает подкомпоненты, такие как кнопки и т. Д., А затем просто добавить / удалить этот компонент меню, но это не требуется для этого простого меню.
Вывод
В этой части мы рассмотрели обнаружение столкновений и то, что происходит, когда мяч попадает в различные объекты, такие как весло, кирпичи, стены, потолок и пол. Также мы создали собственное меню с пользовательскими кнопками, которые мы скрываем и показываем по команде.
В последней части серии мы рассмотрим игру до конца, следя за счетом и жизнями, звуковыми эффектами и музыкой.
Затем мы разработаем сложную систему спецэффектов, которая оживит игру. Наконец, мы обсудим будущее направление и потенциальные улучшения.