Questa è la quarta parte di una serie di tutorial in cinque parti su come creare giochi con Python 3 e Pygame. Nella terza parte, ci siamo tuffati nel cuore di Breakout e abbiamo imparato come gestire gli eventi, incontrato la lezione principale di Breakout e ho visto come spostare i diversi oggetti di gioco.
In questa parte, vedremo come rilevare le collisioni e cosa succede quando la palla colpisce vari oggetti come la pagaia, i mattoni, i muri, il soffitto e il pavimento. Infine, esamineremo l'argomento importante dell'interfaccia utente del gioco e in particolare come creare un menu con i nostri pulsanti personalizzati.
Nei giochi, le cose si incontrano l'un l'altra. Breakout non è diverso. Principalmente è la palla che si imbatte in roba. Il handle_ball_collisions ()
il metodo ha una funzione nidificata chiamata intersecano ()
, che viene usato per verificare se la palla colpisce un oggetto e dove colpisce l'oggetto. Restituisce 'left', 'right', 'top', 'bottom', o None se la palla non ha colpito l'oggetto.
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 (margine per bordo, rect in edges.items () se ball.bounds.colliderect (rect)) se non collisioni: return None se len (collisions) == 1: return list (collisions) [0] se 'top' in collisioni: if ball.centery> = obj.top: return 'top' se ball.centerx < obj.left: return 'left' else: return 'right' if 'bottom' in collisions: if ball.centery >= obj.bottom: restituisce 'bottom' se ball.centerx < obj.left: return 'left' else: return 'right'
Quando la palla colpisce la paletta, rimbalza. Se colpisce la parte superiore della paletta, rimbalzerà ma manterrà lo stesso componente di velocità orizzontale.
Ma se colpisce il lato della paletta, rimbalzerà sul lato opposto (a sinistra oa destra) e continuerà il suo movimento verso il basso fino a quando non colpisce il pavimento. Il codice utilizza la funzione intersect ().
# Hit paddle s = self.ball.speed edge = intersecare (self.paddle, self.ball) se edge non è None: self.sound_effects ['paddle_hit']. Play () se edge == 'top': speed_x = s [0] speed_y = -s [1] se 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])
Quando la pagaia salta la palla verso il basso (o se la palla colpisce la paletta su un lato), la palla continuerà a cadere e colpirà il pavimento. A questo punto, il giocatore perde una vita e la palla viene ricreata in modo che il gioco possa continuare. Il gioco termina quando il giocatore ha esaurito le vite.
# Hit floor se self.ball.top> c.screen_height: self.lives - = 1 se self.lives == 0: self.game_over = True else: self.create_ball ()
Quando la palla colpisce un muro o il soffitto, semplicemente rimbalza.
# Raggiungi il limite se 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])
Quando una palla colpisce un mattone, è un evento importante in Breakout - il mattone scompare, il giocatore ottiene un punto, la palla rimbalza e alcune altre cose accadono (effetto sonoro e forse anche un effetto speciale) di cui parlerò dopo.
Per determinare se un mattone è stato colpito, il codice controlla se uno qualsiasi dei mattoni si interseca con la palla:
# Colpire mattone per mattoni in self.bricks: edge = intersecare (brick, self.ball) se non edge: continuare 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 maggior parte dei giochi ha un'interfaccia utente. Breakout ha un menu semplice che ha due pulsanti che dicono "GIOCA" e "ESCI". Il menu appare all'inizio del gioco e scompare quando il giocatore fa clic su "PLAY". Vediamo come sono implementati i pulsanti e il menu e come si integrano con il gioco.
Pygame non ha una libreria UI integrata. Esistono estensioni di terze parti, ma ho deciso di creare i miei pulsanti per il menu. Un pulsante è un oggetto di gioco che ha tre stati: normale, al passaggio del mouse e premuto. Lo stato normale è quando il mouse non si trova sopra il pulsante e lo stato di passaggio del mouse si ha quando il mouse si trova sopra il pulsante, ma il pulsante sinistro del mouse non viene premuto. Lo stato premuto è quando il mouse è sopra il pulsante e il giocatore ha premuto il tasto sinistro del mouse.
Il pulsante è implementato come un rettangolo con il colore di sfondo e il testo visualizzato su di esso. Il pulsante riceve anche una funzione on_click (predefinita per una funzione lambda noop) che viene chiamata quando si fa clic sul pulsante.
import pygame da game_object import GameObject da text_object import TextObject import config come c class Button (GameObject): def __init __ (auto, x, y, w, h, testo, 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)
Il pulsante gestisce i propri eventi del mouse e modifica il suo stato interno in base a questi eventi. Quando il pulsante è in stato premuto e riceve a MOUSEBUTTONUP
evento, significa che il giocatore ha fatto clic sul pulsante, e il al clic()
la funzione è invocata.
def handle_mouse_event (self, type, pos): se 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): se self.bounds.collidepoint (pos): if self.state! = 'pressed': self.state = 'hover' else: self.state = 'normal' def handle_mouse_down (self , pos): se self.bounds.collidepoint (pos): self.state = 'pressed' def handle_mouse_up (self, pos): se self.state == 'pressed': self.on_click (self) self.state = ' hover'
Il colore di sfondo
la proprietà che viene utilizzata per disegnare il rettangolo di sfondo restituisce sempre il colore che corrisponde allo stato corrente del pulsante, quindi è chiaro al giocatore che il pulsante è attivo:
@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]
Il create_menu ()
la funzione crea un menu con due pulsanti con il testo "PLAY" e "QUIT". Ha due funzioni annidate chiamate on_play ()
e on_quit ()
che fornisce al pulsante corrispondente. Ogni pulsante è aggiunto al oggetti
lista (da disegnare) e anche al menu_buttons
campo.
def create_menu (self): per i, (testo, gestore) 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, testo, gestore, padding = 5) self.objects.append (b) self.menu_buttons.append (b) self.mouse_handlers.append (b.handle_mouse_event)
Quando si fa clic sul pulsante PLAY, viene richiamato on_play (), che rimuove i pulsanti da oggetti
elenca in modo che non vengano più disegnati. Inoltre, i campi booleani che attivano l'inizio del gioco-is_game_running
e start_level
-sono impostati su True.
Quando si fa clic sul pulsante QUIT, is_game_running
è impostato per falso
(efficacemente interrompendo il gioco) e gioco finito
è impostato su True, attivando la sequenza di fine gioco.
def on_play (pulsante): per b in self.menu_buttons: self.objects.remove (b) self.is_game_running = True self.start_level = True def on_quit (pulsante): self.game_over = True self.is_game_running = False
Mostrare e nascondere il menu è implicito. Quando i pulsanti sono in oggetti
lista, il menu è visibile; quando vengono rimossi, è nascosto. Così semplice.
È possibile creare un menu nidificato con la propria superficie che restituisce sottocomponenti come i pulsanti e altro, quindi aggiungere / rimuovere quel componente di menu, ma non è necessario per questo semplice menu.
In questa parte, abbiamo coperto il rilevamento delle collisioni e cosa succede quando la palla colpisce vari oggetti come la pagaia, i mattoni, i muri, il soffitto e il pavimento. Inoltre, abbiamo creato il nostro menu con pulsanti personalizzati che nascondiamo e mostriamo a comando.
Nell'ultima parte della serie, esamineremo il gioco finale, tenendo d'occhio partiture e vite, effetti sonori e musica.
Quindi svilupperemo un sofisticato sistema di effetti speciali che renderà più piccante il gioco. Infine, discuteremo la direzione futura e i potenziali miglioramenti.