Games bouwen met Python 3 en Pygame deel 4

Overzicht

Dit is deel vier van een vijfdelige serie tutorials over het maken van spellen met Python 3 en Pygame. In deel drie doken we in het hart van Breakout en leerden we hoe we met evenementen moesten omgaan, we ontmoetten de belangrijkste Breakout-klasse en we zagen hoe we de verschillende game-objecten moesten verplaatsen.

In dit deel zullen we zien hoe botsingen kunnen worden gedetecteerd en wat er gebeurt als de bal verschillende voorwerpen raakt, zoals de peddel, de stenen, de muren, het plafond en de vloer. Ten slotte zullen we het belangrijke onderwerp van de gebruikersinterface van games bespreken en in het bijzonder hoe we een menu kunnen creëren met onze eigen aangepaste knoppen.

Collision Detection

In games botsen de dingen elkaar tegen. Breakout is niet anders. Meestal is het de bal die dingen tegen het lijf loopt. De handle_ball_collisions () methode heeft een geneste functie genaamd snijden(), die wordt gebruikt om te testen of de bal een object raakt en waar het het object raakt. Het retourneert 'left', 'right', 'top', 'bottom' of None als de bal het object niet heeft geraakt.

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: retourneer 'top' als ball.centerx < obj.left: return 'left' else: return 'right' if 'bottom' in collisions: if ball.centery >= obj.bottom: keer 'bottom' terug als ball.centerx < obj.left: return 'left' else: return 'right'

De bal raken met de peddel

Wanneer de bal de peddel raakt, stuitert hij af. Als het de bovenkant van de paddle raakt, springt het weer omhoog maar behoudt het dezelfde horizontale snelheidscomponent. 

Maar als het de zijkant van de peddel raakt, zal het stuiteren naar de andere kant (links of rechts) en de beweging naar beneden voortzetten totdat het op de vloer komt. De code gebruikt de intersect-functie ().

# Hit paddle s = self.ball.speed edge = kruising (self.paddle, self.ball) als edge niet None is: self.sound_effects ['paddle_hit']. Play () if edge == 'top': speed_x = s [0] speed_y = -s [1] als 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])

Op de vloer slaan

Wanneer de peddel de bal mist op zijn weg naar beneden (of als de bal de peddel op zijn kant raakt), zal de bal blijven vallen en uiteindelijk op de grond vallen. Op dit punt verliest de speler een leven en wordt de bal opnieuw gemaakt zodat het spel kan doorgaan. Het spel is afgelopen als de speler geen levens meer heeft.

# Hit floor if self.ball.top> c.screen_height: self.lives - = 1 if self.lives == 0: self.game_over = True else: self.create_ball ()

Het plafond en de muren raken

Wanneer de bal tegen een muur of het plafond botst, stuitert deze gewoon terug. 

# Raak het plafond aan als 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])

Bricks raken

Als een bal een steen raakt, is het een grote gebeurtenis in Breakout - de steen verdwijnt, de speler krijgt een punt, de bal stuitert terug en een paar andere dingen gebeuren (geluidseffect en mogelijk een speciaal effect) dat ik zal bespreken later. 

Om te bepalen of een steen is geraakt, controleert de code of een van de stenen de bal kruist:

# Baksteen voor baksteen in self.bricks: edge = kruisen (brick, self.ball) indien niet edge: ga verder 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])

Het Game-menu programmeren

De meeste spellen hebben wat gebruikersinterface. Breakout heeft een eenvoudig menu met twee knoppen die 'PLAY' en 'QUIT' zeggen. Het menu verschijnt aan het begin van het spel en verdwijnt wanneer de speler op 'PLAY' klikt. Laten we eens kijken hoe de knoppen en het menu worden geïmplementeerd en hoe ze integreren met het spel.

Knoppen maken

Pygame heeft geen ingebouwde UI-bibliotheek. Er zijn extensies van derden, maar ik besloot om mijn eigen knoppen voor het menu te maken. Een knop is een spelobject met drie statussen: normaal, zwevend en ingedrukt. De normale status is wanneer de muis zich niet boven de knop bevindt en de zweeftoestand is wanneer de muis zich boven de knop bevindt maar de linkermuisknop niet is ingedrukt. De ingedrukte status is wanneer de muis zich boven de knop bevindt en de speler op de linkermuisknop heeft gedrukt. 

De knop is geïmplementeerd als een rechthoek met achtergrondkleur en tekst eroverheen. De knop ontvangt ook een on_click-functie (standaard naar een noop lambda-functie) die wordt gebeld wanneer op de knop wordt geklikt.

pygame importeren uit game-object importeren GameObject importeren uit text_object TextObject importeren config als c class Knop (GameObject): def __init __ (zelf, x, y, w, h, tekst, on_click = lambda x: None, padding = 0): super () .__ init __ (x, y, w, h) self.state = 'normaal' 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)

De knop behandelt zijn eigen muisgebeurtenissen en verandert de interne status op basis van deze gebeurtenissen. Wanneer de knop is ingedrukt en ontvangt een MOUSEBUTTONUP gebeurtenis, betekent dit dat de speler op de knop heeft geklikt, en de bij klikken() functie wordt opgeroepen.

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 (zelf , 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 = ' zweven' 

De back_color eigenschap die wordt gebruikt om de achtergrondrechthoek te tekenen, retourneert altijd de kleur die overeenkomt met de huidige status van de knop, dus het is duidelijk voor de speler dat de knop actief is:

@property def back_color (self): return dict (normaal = c.button_normal_back_color, hover = c.button_hover_back_color, pressed = c.button_pressed_back_color) [self.state]

Het menu maken

De create_menu () functie maakt een menu met twee knoppen met de tekst 'PLAY' en 'QUIT'. Het heeft twee geneste functies genaamd on_play () en on_quit () dat het voorziet in de bijbehorende knop. Elke knop is toegevoegd aan de voorwerpen lijst (te tekenen) en ook naar de menu_buttons veld-.

def create_menu (self): voor i, (tekst, handler) in opsomming ((('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) 

Wanneer op de knop PLAY wordt geklikt, wordt on_play () aangeroepen, waardoor de knoppen uit de. Worden verwijderd voorwerpen lijst zodat ze niet meer worden getekend. Ook de Booleaanse velden die het begin van het spel activeren-is_game_running en start_level-zijn ingesteld op Waar. 

Wanneer op de QUIT-knop wordt geklikt, is_game_running ingesteld op vals (in feite het spel pauzeren) en spel is over is ingesteld op True, waardoor de reeks eindgames wordt geactiveerd.

def on_play (button): voor 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

Het spelmenu weergeven en verbergen

Het tonen en verbergen van het menu is impliciet. Wanneer de knoppen zich in de voorwerpen lijst, het menu is zichtbaar; wanneer ze worden verwijderd, is het verborgen. Zo simpel is het. 

Het is mogelijk om een ​​genest menu te maken met een eigen oppervlak dat subcomponenten zoals knoppen en meer weergeeft, en dan gewoon die menucomponent toe te voegen / te verwijderen, maar het is niet nodig voor dit eenvoudige menu.

Conclusie

In dit deel hebben we botsingdetectie besproken en wat er gebeurt als de bal verschillende voorwerpen raakt, zoals de peddel, de stenen, de muren, het plafond en de vloer. We hebben ook ons ​​eigen menu gemaakt met aangepaste knoppen die we verbergen en tonen op commando. 

In het laatste deel van de serie zullen we kijken naar het eindspel, waarbij de scores en levens, geluidseffecten en muziek worden bijgehouden.

Vervolgens zullen we een geavanceerd systeem van speciale effecten ontwikkelen die het spel nog spannender zullen maken. Ten slotte zullen we de toekomstige richting en mogelijke verbeteringen bespreken.