总览
这是有关使用Python 3和Pygame制作游戏的五部分系列教程的第四部分。 在第三部分中,我们深入探讨Breakout的核心,学习了如何处理事件,遇到了Breakout主班,并了解了如何移动不同的游戏对象。
在这一部分中,我们将看到如何检测碰撞以及当球撞击桨,砖块,墙壁,天花板和地板等各种物体时发生的情况。 最后,我们将回顾游戏UI的重要主题,尤其是如何使用我们自己的自定义按钮创建菜单。
碰撞检测
在游戏中,事物相互碰撞。 突破没有什么不同。 通常是碰到东西的球。 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'
用球拍击球
当球击中桨时,球反弹。 如果它碰到了桨的顶部,它将回弹,但保持相同的水平速度分量。
但是,如果它碰到了桨的侧面,它将弹向相反的一侧(向左或向右)并继续向下运动,直到碰到地板。 该代码使用相交的function()。
# 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])
打地板
当桨在下降途中未击中球时(或如果球在侧面击中桨),则球将继续下落并最终击中地面。 在这一点上,玩家丧命,并且重新制作了球,因此游戏可以继续进行。 当玩家的生命耗尽时,游戏结束。
# Hit floor
if self.ball.top > c.screen_height:
self.lives -= 1
if self.lives == 0:
self.game_over = True
else:
self.create_ball()
击中天花板和墙壁
当球碰到墙壁或天花板时,它会弹回来。
# 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中的重要事件-砖头消失,玩家得到一个点,球反弹回来,并且发生了其他一些事情(声音效果,也可能是特殊效果),我将讨论后来。
为了确定是否击中了砖块,代码检查一下是否有任何砖块与球相交:
# 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])
编程游戏菜单
大多数游戏都有一些UI。 Breakout有一个简单的菜单,其中包含两个按钮,分别为“ PLAY”和“ QUIT”。 菜单在游戏开始时显示,并在玩家单击“ PLAY”时消失。 让我们看看如何实现按钮和菜单以及它们如何与游戏集成。
制作按钮
Pygame没有内置的UI库。 有第三方扩展,但是我决定为菜单构建自己的按钮。 按钮是具有三种状态的游戏对象:正常,悬停和按下。 正常状态是鼠标悬停在按钮上方时,悬停状态是鼠标悬停在按钮上方但未按下鼠标左键时。 按下状态是当鼠标悬停在按钮上方且播放器按下鼠标左键时。
该按钮实现为矩形,上面带有背景颜色和文本。 该按钮还接收一个on_click函数(默认为noop lambda函数),该函数在单击按钮时被调用。
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)
单击“播放”按钮时,将调用on_play(),这会将按钮从objects
列表中删除,因此不再绘制它们。 同样,触发游戏开始的布尔值字段is_game_running
和start_level
设置为True。
单击“退出”按钮时, 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
列表中时,菜单可见。 当它们被删除时,它是隐藏的。 就如此容易。
可以使用其自己的表面创建一个嵌套菜单,以呈现诸如按钮等之类的子组件,然后只需添加/删除该菜单组件,但是此简单菜单不需要它。
结论
在这一部分中,我们介绍了碰撞检测以及当球撞击桨,砖块,墙壁,天花板和地板等各种物体时发生的情况。 此外,我们还创建了带有自定义按钮的菜单,这些按钮可在命令中隐藏和显示。
在本系列的最后一部分,我们将研究最终游戏,密切关注乐谱,生活,音效和音乐。
然后,我们将开发一个复杂的特殊效果系统,为游戏增添趣味。 最后,我们将讨论未来的方向和潜在的改进。
翻译自: https://code.tutsplus.com/tutorials/building-games-with-python-3-and-pygame-part-4--cms-30084