Aceasta este o parte a unei serii de tutoriale cu cinci părți despre realizarea de jocuri cu Python 3 și Pygame. În partea a treia, am invadat în inima breakout-ului și am învățat cum să gestionăm evenimentele, am întâlnit clasa principală Breakout și am văzut cum să mutați diferitele obiecte de joc.
În această parte, vom vedea cum să detectăm coliziunile și ce se întâmplă atunci când mingea lovește obiecte diferite precum paleta, cărămizile, pereții, tavanul și podeaua. În cele din urmă, vom revedea tema importantă a interfeței jocului și, în special, despre crearea unui meniu cu propriile butoane personalizate.
În jocuri, lucrurile se înfundă unul în celălalt. Breakout nu este diferit. În mare măsură, mingea e în buzunar. handle_ball_collisions ()
metoda are o funcție imbricată numită se intersectează ()
, care este folosit pentru a testa dacă mingea a lovit un obiect și unde a lovit obiectul. Se întoarce "stânga", "dreapta", "partea de sus", "partea de jos" sau "Niciuna" dacă mingea nu a lovit obiectul.
def handle_ball_collisions (auto): def intersect (obj, minge): edges = dict (stânga = Rect (obj.left, obj.top, 1, obj.height), right = Rect (obj.right, obj.top, 1 , vârf = Rect (obj.left, obj.top, obj.width, 1), bottom = Rect (obj.left, obj.bottom, obj.width, 1)) coliziuni = set marginea, rect în edges.items () dacă ball.bounds.colliderect (rect)) dacă nu coliziuni: return Nu există dacă len (coliziuni) == 1: lista de întoarcere (coliziuni) [0] dacă "top" în coliziuni: ball.centery> = obj.top: întoarceți 'top' dacă este ball.centerx < obj.left: return 'left' else: return 'right' if 'bottom' in collisions: if ball.centery >= obj.bottom: întoarceți "bottom" dacă este ball.centerx < obj.left: return 'left' else: return 'right'
Atunci când mingea lovește cu zbârcișul, se oprește. Dacă atinge partea superioară a paletei, aceasta va reveni înapoi, dar va păstra aceeași componentă de viteză orizontală.
Dar dacă se lovește de partea laterală a paletei, aceasta va sări spre partea opusă (stânga sau dreapta) și va continua mișcarea în jos până când va atinge podeaua. Codul folosește funcția intersect ().
# Hit paddle s = marginea self.ball.speed = intersectează (self.paddle, self.ball) dacă marginea nu este nici una: self.sound_effects ['paddle_hit'] play () if edge == 'top': speed_x = s [0] speed_y = -s [1] dacă auto.paddle.moving_left: speed_x - = 1 elif self.paddle.moving_left: speed_x + = 1 self.ball.speed = 'dreapta'): self.ball.speed = (-s [0], s [1])
Când cuțitul omite mingea în jos (sau dacă mingea lovește cu zbaturi pe partea laterală), mingea va continua să cadă și în cele din urmă va lovi podeaua. În acest moment, jucătorul pierde o viață și mingea este recreată, astfel încât jocul poate continua. Jocul se termină atunci când jucătorul a ieșit din viață.
# Hit floor dacă self.ball.top> c.screen_height: self.lives - = 1 dacă self.lives == 0: self.game_over = Altceva adevărat: self.create_ball ()
Când mingea lovește un perete sau un tavan, pur și simplu răsfoiește.
# Hit plafon dacă auto.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])
Atunci când o minge lovește o cărămidă, este un eveniment major în Breakout - cărămida dispare, jucătorul primește un punct, mingea se întoarce și alte câteva lucruri se întâmplă (efectul de sunet și, eventual, un efect deosebit) pe care o voi discuta mai tarziu.
Pentru a determina dacă a fost lovită o cărămidă, codul verifică dacă una dintre cărămizi se intersectează cu mingea:
# Caramida pentru cărămizi în caramida: margine = intersect (caramida, auto.bal) dacă nu margine: continuați self.bricks.remove (caramida) self.objects.remove (caramida) self.score + = self.points_per_brick if margine în ('sus', 'jos'): self.ball.speed = (s [0], -s [1]) alt: self.ball.speed = (-s [0], s [1])
Cele mai multe jocuri au unele UI. Breakout are un meniu simplu care are două butoane care spun "PLAY" și "QUIT". Meniul se afișează la începutul jocului și dispare atunci când jucătorul dă clic pe "PLAY". Să vedem cum sunt implementate butoanele și meniul și cum se integrează cu jocul.
Pygame nu are o bibliotecă UI încorporată. Există extensii de la terțe părți, dar am decis să construiesc propriile butoane pentru meniu. Un buton este un obiect de joc care are trei stări: normal, hover și apăsat. Starea normală este când mouse-ul nu este peste buton, iar starea de hover este atunci când mouse-ul este peste buton, dar butonul stâng al mouse-ului nu este apăsat. Starea presată este când mouse-ul este peste buton și jucătorul a apăsat butonul stâng al mouse-ului.
Butonul este implementat ca un dreptunghi cu culoarea de fundal și textul afișat peste el. De asemenea, butonul primește o funcție on_click (implicită la o funcție no lambda) care este apelată la apăsarea butonului.
Pictogramă de import de la game_object Import GameObject de la text_object import TextObject import config ca c clasă Buton (GameObject): def __init __ (auto, x, y, w, h, text, on_click = lambda x: .__ init __ (x, y, w, h) auto.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 (auto, suprafață): pygame.draw.rect (suprafață, auto.back_color, self.bounds) self.text.draw (surface)
Butonul efectuează propriile evenimente ale mouse-ului și își modifică starea internă pe baza acestor evenimente. Când butonul este în stare presată și primește a MOUSEBUTTONUP
eveniment, înseamnă că jucătorul a dat clic pe butonul și on_click ()
funcția este invocată.
def handle_mouse_event (auto, tip, pos): if type == pygame.MOUSEMOTION: self.handle_mouse_move (pos) elif tip == pygame.MOUSEBUTTONDOWN: self.handle_mouse_down (pos) elif type == pygame.MOUSEBUTTONUP: self.handle_mouse_up pos) defect handle_mouse_move (auto, pos): if self.bounds.collidepoint (pos): if self.state! = 'presat': self.state = 'hover' else: self.state = 'normal' def handle_mouse_down , pos): dacă auto.bounds.collidepoint (pos): self.state = 'presat' def handle_mouse_up (self, pos): if self.state == 'presed': self.on_click (self) planare'
culoare de fundal
proprietatea care este utilizată pentru desenarea dreptunghiului de fundal întoarce întotdeauna culoarea care se potrivește cu starea curentă a butonului, astfel că este clar pentru player că butonul este activ:
@property def back_color (auto): returnați dict (normal = c.button_normal_back_color, hover = c.button_hover_back_color, apăsat = c.button_pressed_back_color) [autostate]
create_menu ()
funcția creează un meniu cu două butoane cu textul "PLAY" și "QUIT". Are două funcții imbricate numite on_play ()
și on_quit ()
pe care le oferă la butonul corespunzător. Fiecare buton este adăugat la obiecte
(care urmează să fie trasată) și, de asemenea, la menu_buttons
camp.
Definiți: create_menu (self): pentru i, (text, handler) în enumerate ((('PLAY', on_play), 'QUIT', on_quit)): b = Buton (c.menu_offset_x, c.menu_offset_y + .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)
Când se face clic pe butonul PLAY, se invocă on_play (), care elimină butoanele din obiecte
astfel încât acestea să nu mai fie trase. De asemenea, câmpurile booleene care declanșează începutul jocului-is_game_running
și start_level
-sunt setate la True.
Când se face clic pe butonul QUIT, is_game_running
este setat sa Fals
(oprirea efectivă a jocului) și joc încheiat
este setat la True, declanșând secvența de joc finală.
def on_play (buton): pentru b în self.menu_buttons: self.objects.remove (b) self.is_game_running = adevărat self.start_level = adevărat def on_quit (buton): self.game_over = adevărat self.is_game_running = False
Afișarea și ascunderea meniului este implicită. Când butoanele sunt în obiecte
listă, meniul este vizibil; când sunt îndepărtate, este ascunsă. Simplu ca buna ziua.
Este posibil să creați un meniu imbricat cu o suprafață proprie care redă sub-componente precum butoane și multe altele și apoi adăugați / eliminați acea componentă de meniu, dar nu este necesară pentru acest meniu simplu.
În această parte am descoperit detectarea coliziunilor și ce se întâmplă atunci când mingea lovește diverse obiecte, cum ar fi paleta, cărămizile, pereții, tavanul și podeaua. De asemenea, am creat propriul meniu cu butoane personalizate pe care le ascundem și le afișăm la comandă.
În ultima parte a seriei, vom examina jocul final, ținând tabele pe scor și vieți, efecte sonore și muzică.
Apoi, vom dezvolta un sistem sofisticat de efecte speciale care vor condimenta jocul. În cele din urmă, vom discuta direcția viitoare și potențialele îmbunătățiri.