这是有关使用 Python 3 和 Pygame 制作游戏的五部分教程系列中的第四部分。在第三部分中,我们深入研究了 Breakout 的核心,学习了如何处理事件,了解了主要的 Breakout 类,并了解了如何移动不同的游戏对象。
在这一部分中,我们将了解如何检测碰撞以及当球撞击各种物体(如桨、砖块、墙壁、天花板和地板)时会发生什么。最后,我们将回顾游戏 UI 的重要主题,特别是如何使用我们自己的自定义按钮创建菜单。
在游戏中,事物会相互碰撞。突破也不例外。大多数情况下是球撞到了东西。 handle_ball_collisions()
方法有一个嵌套函数,名为 intersect()
,用于测试球是否击中物体以及击中物体的位置。如果球没有击中物体,则返回“左”、“右”、“上”、“下”或“无”。
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 函数()。
# 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])
大多数游戏都有一些用户界面。 Breakout 有一个简单的菜单,其中有两个按钮,分别是“PLAY”和“QUIT”。该菜单在游戏开始时出现,并在玩家单击“开始”时消失。让我们看看按钮和菜单是如何实现的以及它们如何与游戏集成。
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)
当点击 PLAY 按钮时,会调用 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
列表中时,菜单可见;当它们被删除时,它就被隐藏了。就如此容易。
可以创建一个具有自己的表面的嵌套菜单,该表面呈现按钮等子组件,然后只需添加/删除该菜单组件,但这个简单的菜单不需要它。
在这一部分中,我们介绍了碰撞检测以及当球撞击各种物体(例如桨、砖块、墙壁、天花板和地板)时会发生什么。此外,我们还创建了自己的菜单,其中包含根据命令隐藏和显示的自定义按钮。
在本系列的最后一部分中,我们将研究最终游戏,密切关注得分和生命、音效和音乐。
然后,我们将开发一套复杂的特效系统,为游戏增添趣味。最后,我们将讨论未来的方向和潜在的改进。
以上是使用 Python 3 和 Pygame 创建互动游戏:第 4 部分的详细内容。更多信息请关注PHP中文网其他相关文章!