Il s'agit du quatrième d'une série de didacticiels en cinq parties sur la création de jeux avec Python 3 et Pygame. Dans la troisième partie, nous avons plongé dans le cœur de Breakout, appris à gérer les événements, examiné les principales classes de Breakout et vu comment déplacer différents objets du jeu.
Dans cette partie, nous apprendrons comment détecter les collisions et ce qui se passe lorsque la balle touche divers objets tels que des raquettes, des briques, des murs, des plafonds et des sols. Enfin, nous passerons en revue des sujets importants concernant l'interface utilisateur du jeu, en particulier comment créer des menus avec nos propres boutons personnalisés.
Dans le jeu, les choses entrent en collision. La percée ne fait pas exception. La plupart du temps, c'est la balle qui frappe quelque chose. handle_ball_collisions()
方法有一个嵌套函数,名为 intersect()
, utilisé pour tester si la balle touche un objet et où elle touche l'objet. Si la balle manque un objet, renvoie "Gauche", "Droite", "Haut", "Bas" ou "Aucun".
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'
Lorsque la balle touche la raquette, elle rebondit. S'il touche le haut de la pagaie, il rebondira mais conservera la même composante de vitesse horizontale.
Cependant, si elle heurte un côté de la pagaie, elle rebondira de l'autre côté (gauche ou droite) et poursuivra son mouvement vers le bas jusqu'à ce qu'elle touche le sol. Ce code utilise la fonction d'intersection ().
# 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])
Lorsque la raquette manque la balle en descendant (ou que la balle touche le côté de la raquette), la balle continuera de tomber et finira par toucher le sol. À ce stade, le joueur perd une vie et la balle est recréée pour que le jeu puisse continuer. Le jeu se termine lorsque le joueur n'a plus de vie.
# Hit floor if self.ball.top > c.screen_height: self.lives -= 1 if self.lives == 0: self.game_over = True else: self.create_ball()
Lorsque la balle touche un mur ou un plafond, elle rebondit simplement.
# 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])
Quand la balle touche une brique, c'est un événement majeur dans Breakout - la brique disparaît, le joueur marque un point, la balle rebondit et quelques autres choses se produisent (effets sonores et éventuellement effets spéciaux), j'en discuterai ça plus tard.
Pour déterminer si une brique a été touchée, le code vérifie si des briques croisent la balle :
# 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])
La plupart des jeux ont une sorte d'interface utilisateur. Breakout a un menu simple avec deux boutons, "PLAY" et "QUITTER". Ce menu apparaît au démarrage du jeu et disparaît lorsque le joueur clique sur "Démarrer". Voyons comment les boutons et les menus sont implémentés et comment ils s'intègrent au jeu.
Pygame n'a pas de bibliothèque d'interface utilisateur intégrée. Il existe des extensions tierces, mais j'ai décidé de créer mon propre bouton pour le menu. Les boutons sont des objets de jeu avec trois états : normal, survolé et enfoncé. L'état normal est lorsque la souris n'est pas sur le bouton, et l'état de survol est lorsque la souris est sur le bouton mais que le bouton gauche de la souris n'est pas enfoncé. L'état enfoncé est lorsque la souris est sur le bouton et que le joueur appuie sur le bouton gauche de la souris.
Le bouton est implémenté sous la forme d'un rectangle avec une couleur d'arrière-plan et du texte affiché dessus. Le bouton reçoit également une fonction on_click (par défaut une fonction noop lambda), qui est appelée lorsque l'on clique sur le bouton.
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)
Les boutons gèrent leurs propres événements de souris et modifient leur état interne en fonction de ces événements. Lorsque le bouton est enfoncé, la fonction MOUSEBUTTONUP
事件,表示玩家点击了按钮,并调用 on_click()
est reçue.
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'
La propriété back_color
utilisée pour dessiner le rectangle d'arrière-plan renvoie toujours une couleur qui correspond à l'état actuel du bouton, afin que le joueur puisse clairement voir que le bouton est actif :
@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
champs.
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)
Lorsque vous cliquez sur le bouton PLAY, on_play() est appelé, qui est défini de objects
列表中删除按钮,这样就不再绘制它们了。此外,触发游戏开始的布尔字段 is_game_running
和 start_level
à True.
Défini sur True lorsque vous cliquez sur le bouton de sortie, déclenchant la séquence de jeu finale. is_game_running
设置为 False
(有效暂停游戏)并且 game_over
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
Afficher et masquer les menus est implicite. Lorsque les boutons sont dans la liste
, le menu est visible ; lorsqu'ils sont supprimés, il est masqué. C'est aussi simple que cela. objects
Il est possible de créer un menu imbriqué avec sa propre surface qui restitue des sous-composants comme des boutons, puis d'ajouter/supprimer simplement ce composant de menu, mais ce menu simple n'en a pas besoin.
Dans cette section, nous abordons la détection de collision et ce qui se passe lorsque la balle touche divers objets tels que des raquettes, des briques, des murs, des plafonds et des sols. De plus, nous avons créé notre propre menu avec des boutons personnalisés qui se cachent et s'affichent en fonction des commandes.
Dans cette dernière partie de la série, nous examinerons le jeu final, en accordant une attention particulière aux scores et aux vies, aux effets sonores et à la musique.
Ensuite, nous développerons un système d'effets spéciaux complexe pour ajouter de l'intérêt au jeu. Enfin, nous discutons des orientations futures et des améliorations potentielles.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!