Il s’agit de la quatrième partie d’une série de tutoriels en cinq parties sur la création de jeux avec Python 3 et Pygame. Dans la troisième partie, nous avons plongé au cœur de Breakout et appris à gérer les événements, rencontré la classe principale de Breakout et vu comment déplacer les différents objets du jeu..
Dans cette partie, nous verrons comment détecter les collisions et ce qui se passe lorsque la balle frappe divers objets tels que la pagaie, les briques, les murs, le plafond et le sol. Enfin, nous allons passer en revue l’important sujet de l’interface utilisateur du jeu et en particulier comment créer un menu avec nos propres boutons personnalisés..
Dans les jeux, les choses se bousculent. L'évasion n'est pas différente. Surtout c'est la balle qui cogne dans les choses. le handle_ball_collisions ()
méthode a une fonction imbriquée appelée couper()
, qui est utilisé pour tester si la balle frappe un objet et où elle frappe l'objet. Il retourne 'gauche', 'droite', 'haut', 'bas' ou None si la balle n'a pas touché l'objet.
def handle_ball_collisions (self): def intersection (obj, ball): bords = 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 = défini (bord pour edge, rect dans edge.items () si ball.bounds.colliderect (rect)) sinon collisions: return None si len (collisions) == 1: retourne la liste (collisions) [0] si 'top' dans les collisions: if ball.centery> = obj.top: retourne 'top' si ball.centerx < obj.left: return 'left' else: return 'right' if 'bottom' in collisions: if ball.centery >= obj.bottom: retourne 'bottom' si ball.centerx < obj.left: return 'left' else: return 'right'
Lorsque la balle frappe la raquette, elle rebondit. Si elle frappe le haut de la raquette, elle rebondira mais gardera la même composante de vitesse horizontale.
Mais si elle frappe le côté de la pagaie, elle rebondira sur le côté opposé (à gauche ou à droite) et poursuivra son mouvement vers le bas jusqu'à ce qu'elle touche le sol. Le code utilise la fonction intersection ().
# Appuyez sur paddle s = self.ball.speed edge = intersection (self.paddle, self.ball) si bord n'est pas None: self.sound_effects ['paddle_hit']. Play () si edge == 'top': speed_x = s [0] speed_y = -s [1] si self.paddle.moving_left: speed_x - = 1 elif self.paddle.moving_left: speed_x + = 1 self.ball.speed = speed_x, speed_y elif bord dans ('gauche', 'right'): self.ball.speed = (-s [0], s [1])
Lorsque la raquette manque la balle en descendant (ou si la balle frappe la raquette sur le côté), la balle continue de tomber et finit 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 est terminé lorsque le joueur est à court de vies.
# Hit floor si self.ball.top> c.screen_height: self.lives - = 1 si self.lives == 0: self.game_over = True sinon: self.create_ball ()
Lorsque la balle frappe un mur ou le plafond, elle rebondit simplement.
# Hit plafond si self.ball.top < 0: self.ball.speed = (s[0], -s[1]) # Hit wall if self.ball.left < 0 or self.ball.right > largeur de l'écran: self.ball.speed = (-s [0], s [1])
Quand une balle frappe 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 événements se produisent (effet sonore et éventuellement un effet spécial) dont je parlerai. plus tard.
Pour déterminer si une brique a été touchée, le code vérifie si l'une des briques intersecte la balle:
# Hit brique pour brique dans self.bricks: edge = intersecter (brique, self.ball) sinon bord: continue self.bricks.remove (brique) self.objects.remove (brique) self.score + = self.points_per_brick if bord dans ('haut', 'bas'): self.ball.speed = (s [0], -s [1]) sinon: self.ball.speed = (-s [0], s [1])
La plupart des jeux ont une interface utilisateur. Breakout a un menu simple qui a deux boutons qui disent "PLAY" et "QUIT". Le menu apparaît au début du jeu et disparaît lorsque le joueur clique sur 'PLAY'. Voyons comment les boutons et le menu 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 mes propres boutons pour le menu. Un bouton est un objet de jeu ayant trois états: normal, survol et enfoncé. L'état normal est lorsque la souris n'est pas au-dessus du bouton et l'état de survol lorsque la souris est au-dessus du bouton mais que le bouton gauche de la souris n'est pas enfoncé. L'état enfoncé est lorsque la souris est au-dessus du bouton et que le joueur a appuyé sur le bouton gauche de la souris..
Le bouton est implémenté sous la forme d'un rectangle avec la couleur d'arrière-plan et le texte affiché dessus. Le bouton reçoit également une fonction on_click (définie par défaut sur une fonction noop lambda) appelée lorsque vous cliquez sur le bouton..
importer pygame depuis game_object importer GameObject depuis text_object importer TextObject import config en tant que classe class Button (GameObject): def __init __ (auto, x, y, w, h, texte, on_click = lambda x: Aucune, padding = 0): super () .__ init __ (x, y, w, h) self.state = 'normal' self.on_click = on_click self.text = TextObject (x + remplissage, y + remplissage, lambda: texte, c.button_text_color, c.font_nom_font, c .font_size) def draw (self, surface): pygame.draw.rect (surface, self.back_color, self.bounds) self.text.draw (surface)
Le bouton gère ses propres événements de souris et modifie son état interne en fonction de ces événements. Lorsque le bouton est enfoncé et reçoit un message MOUSEBUTTONUP
événement, cela signifie que le joueur a cliqué sur le bouton, et le sur clic()
la fonction est appelée.
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) type elif == pygame.MOUSEBUTTONUP: pos) def handle_mouse_move (self, pos): si self.bounds.collidepoint (pos): si self.state! = 'appuyé': self.state = 'survol' autre: self.state = 'normal' def handle_mouse_down (auto , pos): if self.bounds.collidepoint (pos): self.state = 'appuyé' def handle_mouse_up (self, pos): si self.state == 'appuyé': self.on_click (self) self.state = ' flotter'
le couleur de fond
La propriété utilisée pour dessiner le rectangle d’arrière-plan renvoie toujours la couleur qui correspond à l’état actuel du bouton. Il est donc clair que le bouton est actif pour le lecteur:
@property def back_color (self): retourne dict (normal = c.button_normal_back_color, survol = c.button_hover_back_color, enfoncé = c.button_pressed_back_color) [self.state]
le create_menu ()
function crée un menu avec deux boutons avec les textes "PLAY" et "QUIT". Il a deux fonctions imbriquées appelées on_play ()
et on_quit ()
qu'il fournit au bouton correspondant. Chaque bouton est ajouté à la objets
liste (à tirer) et aussi à la boutons de menu
champ.
def create_menu (self): pour i (texte, gestionnaire) dans énumérer ((('PLAY', on_play), ('QUIT', on_quit))): b = Bouton (c.menu_offset_x, c.menu_offset_y + (c .menu_button_h + 5) * i, c.menu_button_w, c.menu_button_h, text, gestionnaire, padding = 5) self.objects.append (b) self.menu_buttons.append (b) self.mouse_handlers.append (b.handle_mouse_event)
Lorsque le bouton PLAY est cliqué, on_play () est invoqué, ce qui supprime les boutons du objets
liste afin qu'ils ne soient plus dessinés. En outre, les champs booléens qui déclenchent le début du jeu-is_game_running
et début_niveau
-sont définis sur True.
Lorsque le bouton QUITTER est cliqué, is_game_running
est réglé sur Faux
(mettant effectivement le jeu en pause) et jeu terminé
est défini sur True, déclenchant la séquence de fin du jeu.
def on_play (button): pour b dans 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 le menu est implicite. Quand les boutons sont dans le objets
liste, le menu est visible; quand ils sont enlevés, c'est caché. Aussi simple que cela.
Il est possible de créer un menu imbriqué avec sa propre surface qui restitue des sous-composants tels que des boutons, etc., puis d'ajouter / supprimer ce composant de menu, mais ce n'est pas nécessaire pour ce menu simple..
Dans cette partie, nous avons traité de la détection des collisions et de ce qui se passe lorsque la balle frappe divers objets tels que la pagaie, les briques, les murs, le plafond et le sol. Nous avons également créé notre propre menu avec des boutons personnalisés que nous masquons et montrons sur commande..
Dans la dernière partie de la série, nous examinerons le dernier jeu en gardant un œil sur le score et les lives, les effets sonores et la musique..
Ensuite, nous développerons un système sophistiqué d’effets spéciaux qui pimenteront le jeu. Enfin, nous discuterons de l’orientation future et des améliorations potentielles.