Dit is deel vijf van een vijfdelige serie tutorials over het maken van spellen met Python 3 en PyGame. In deel vier hebben we botsingen gedetecteerd, gereageerd op het raken van verschillende objecten in de bal en een gamemenu gemaakt met aangepaste knoppen.
In dit laatste deel behandelen we diverse onderwerpen, zoals het eindspel, het beheren van levens en score, geluidseffecten, muziek en zelfs een flexibel speciaal effectensysteem. Als dessert bespreken we mogelijke verbeteringen en toekomstige richtingen.
Uiteindelijk moet het spel eindigen. In deze versie van Breakout eindigt het spel op twee manieren: de speler verliest zijn hele leven of raakt alle stenen. Er is geen volgend niveau (hoewel het gemakkelijk zou zijn toe te voegen).
Het veld game_over van de klasse Game is ingesteld op False in de __in het__()
methode van de klasse Game. De hoofdlus gaat rond en rond tot de spel is over
variabele is ingesteld op True:
class Game: def __init __ (zelf, bijschrift, breedte, hoogte, back_image_filename, frame_rate): ... self.game_over = False ... def run (self): while not self.game_over: self.surface.blit (self.background_image, (0 , 0)) self.handle_events () self.update () self.dang () pygame.display.update () self.clock.tick (self.frame_rate)
Dat gebeurt allemaal in de klasse Breakout in de volgende gevallen:
def on_quit (button): self.game_over = True self.is_game_running = False def handle_ball_collisions (self): ... # Hit floor if self.ball.top> c.screen_height: self.lives - = 1 if self.lives == 0 : self.game_over = True if not self.bricks: self.show_message ('YOU WIN !!!', centralized = True) self.is_game_running = False self.game_over = Echte return def update (zelf): ... if not self. bricks: self.show_message ('YOU WIN !!!', centralized = True) self.is_game_running = False self.game_over = Echte opbrengst
Meestal, als het spel eindigt, willen we niet dat het spelvenster zomaar verdwijnt. De uitzondering is als u op de knop STOPPEN in het menu klikt. Wanneer de speler zijn laatste leven verliest, geeft Breakout het traditionele 'SPEL OVER!' bericht, en wanneer de speler wint, verschijnt 'YOU WIN!'
De toon bericht()
functie wordt in beide gevallen gebruikt. Het toont de tekst bovenaan het huidige scherm (de game wordt gepauzeerd) en wacht een paar seconden voordat hij terugkeert. In de volgende iteratie van de gamelus wordt de controle voor de spel is over
veld zal bepalen dat het waar is en het programma zal afsluiten.
Hier is de toon bericht()
functie:
def show_message (self, text, color = colors.WHITE, font_name = "Arial", font_size = 20, centralized = False): message = TextObject (c.screen_width // 2, c.screen_height // 2, lambda: text, color, font_name, font_size) self.draw () message.draw (self.surface, centralized) pygame.display.update () time.sleep (c.message_duration)
In deze versie houd ik de hoogste score niet omdat er slechts één niveau is en de score van iedereen hetzelfde zal zijn als alle stenen worden gewist. Over het algemeen kan het lokaal worden gedaan door de hoogste score in een bestand op te slaan en vervolgens een ander bericht weer te geven als de speler de hoogste score heeft gebroken.
Games zijn een audiovisuele ervaring. De meeste spellen hebben geluidseffecten die bestaan uit korte geluidsbytes die worden afgespeeld wanneer de speler een monster doodt, een schat vindt of vreselijk explodeert. Sommige games hebben ook achtergrondmuziek, wat bijdraagt aan de atmosfeer. Breakout heeft alleen geluidseffecten, maar ik laat je zien hoe je achtergrondmuziek speelt in je games.
U hebt geluidsbestanden (vergelijkbaar met afbeeldingsbestanden) nodig om te spelen als geluidseffecten. Deze bestanden kunnen de formaten .wav, .mp3 of .ogg hebben. Breakout behoudt zijn geluidseffecten in de geluidseffecten
map:
~ / git / pygame-breakout> tree sound_effects / sound_effects / ├── brick_hit.wav ├── effect_done.wav ├── level_complete.wav └── paddle_hit.wav
Laten we eens kijken hoe deze geluidseffecten op het juiste moment worden geladen en afgespeeld. Ten eerste, om geluidseffecten (of achtergrondmuziek) te spelen, moet je het geluidssysteem van Pygame initialiseren. Dat gebeurt in de klasse Game: pygame.mixer.pre_init (44100, 16, 2, 4096)
Vervolgens worden in de klasse Breakout alle geluidseffecten vanuit de config geladen in de pygame.mixer.Sound
object en worden opgeslagen in een woordenboek:
# In config.py sounds_effects = dict (brick_hit = "sound_effects / brick_hit.wav", effect_done = "sound_effects / effect_done.wav", paddle_hit = "sound_effects / paddle_hit.wav", level_complete = "sound_effects / level_complete.wav",) # In breakout.py class Breakout (Game): def __init __ (self): ... self.sound_effects = naam: pygame.mixer.Sound (geluid) voor naam, geluid in c.sounds_effects.items () ...
Nu kunnen we de geluidseffecten spelen wanneer er iets interessants gebeurt. Bijvoorbeeld als de bal een steen raakt:
# Baksteen voor baksteen in self.bricks: edge = kruisen (brick, self.ball) indien niet edge: ga door met self.sound_effects ['brick_hit']. Play ()
Het geluidseffect wordt asynchroon afgespeeld, wat betekent dat het spel niet bevriest terwijl het geluid wordt afgespeeld. Meerdere geluidseffecten kunnen tegelijkertijd worden gespeeld.
Het opnemen van uw geluidseffecten is zowel gemakkelijk als lonend. In tegenstelling tot het ontwerpen van visuele middelen, heeft het niet veel talent nodig. Iedereen kan "Kaboom!" of "Boing" of schreeuw "Je bent dood. Volgende keer beter!"
Ik vraag mijn kinderen vaak om geluidseffecten op te nemen, evenals gesproken berichten die bij tekstberichten zoals 'YOU WIN!' of 'SPEL OVER!' Je verbeelding is de enige beperking.
Achtergrondmuziek zou constant moeten spelen. In theorie kun je een heel loooooooong geluidseffect hebben, maar een meer algemene benadering is gewoon om de achtergrondmuziek in een loop te spelen. Muziekbestanden kunnen de indeling .wav, .mp3 of .midi hebben. Hier is hoe het gedaan is:
muziek = pygame.mixer.music.load ('background_music.mp3') pygame.mixer.music.play (-1, 0.0)
U kunt slechts één stuk achtergrondmuziek tegelijkertijd afspelen. Maar er kunnen meerdere geluidseffecten over de achtergrondmuziek worden gespeeld. Dat is waar het bij mengen om draait.
Laten we ons verbeelden. Het breken van stenen met een bal is cool, maar het wordt behoorlijk snel oud. Wat dacht je van een generiek speciaal effectensysteem? We zullen een uitbreidbaar systeem van speciale effecten ontwikkelen die geassocieerd worden met bepaalde stenen en activeren wanneer de bal de steen raakt.
Dit is het plan. Effecten hebben een leven lang. Het effect begint wanneer de steen breekt en eindigt wanneer de duur van het effect verstrijkt. Wat gebeurt er als de bal een andere baksteen met een speciaal effect raakt? In theorie zou je samengestelde effecten kunnen hebben, maar om dingen voor de eerste implementatie te vereenvoudigen, zal het actieve effect stoppen en zal het nieuwe effect zijn plaats innemen.
Een speciaal effect kan op de meest algemene manier worden gedefinieerd als twee functies. De eerste functie activeert het effect en de tweede functie reset het. We willen effecten op stenen plakken en de speler duidelijk maken welke stenen speciaal zijn, zodat ze kunnen proberen ze te raken of te vermijden op bepaalde punten.
Het volgende dict van de module breakout.py definieert onze speciale effecten. Elk effect heeft een naam (bijvoorbeeld long_paddle) en een waarde, die bestaat uit de kleur die de steen zal hebben, evenals de twee functies. De functies zijn gedefinieerd als lambda-functies die een Game-instantie innemen, die alles bevat wat een speciaal effect in Breakout mogelijk wil wijzigen.
special_effects = dict (long_paddle = (colors.ORANGE, lambda g: g.paddle.bounds.inflate_ip (c.paddle_width // 2, 0), lambda g: g.paddle.bounds.inflate_ip (-c.paddle_width // 2 , 0)), slow_ball = (colors.AQUAMARINE2, lambda g: g.change_ball_speed (-1), lambda g: g.change_ball_speed (1)), tripple_points = (colors.DARKSEAGREEN4, lambda g: g.set_points_per_brick (3) , lambda g: g.set_points_per_brick (1)), extra_life = (colors.GOLD1, lambda g: g.add_life (), lambda g: None))
Wanneer de stenen zijn gemaakt, kunnen ze een van de speciale effecten krijgen toegewezen. Hier is de code:
def create_bricks (self): w = c.brick_width h = c.brick_height brick_count = c.screen_width // (w + 1) offset_x = (c.screen_width - brick_count * (w + 1)) // 2 bricks = [] voor rijbereik (c.row_count): voor col in bereik (brick_count): effect = Geen brick_color = c.brick_color index = random.randint (0, 10) als index < len(special_effects): x = list(special_effects.values())[index] brick_color = x[0] effect = x[1:] brick = Brick(offset_x + col * (w + 1), c.offset_y + row * (h + 1), w, h, brick_color, effect) bricks.append(brick) self.objects.append(brick) self.bricks = bricks
De Brick-klasse heeft een effectveld dat meestal Geen is, maar kan (30% kans) een van de speciale effecten krijgen die hierboven zijn gedefinieerd. Merk op dat deze code niet weet welke effecten beschikbaar zijn. Het krijgt gewoon het effect en de baksteenkleur en wijst ze indien nodig toe.
In deze versie van Breakout activeer ik alleen effecten als een steen wordt geraakt, maar je kunt je andere scenario's voorstellen die gebeurtenissen kunnen activeren. Het vorige effect wordt gereset (als er een was) en vervolgens wordt het nieuwe effect gelanceerd. De reset-functie en de starttijd van het effect worden opgeslagen voor later.
if brick.special_effect is not None: # Reset vorig effect indien aanwezig indien self.reset_effect niet None is: self.reset_effect (self) # Trigger special effect self.effect_start_time = datetime.now () brick.special_effect [0] (self) # Stel de huidige reset-effectfunctie in self.reset_effect = brick.special_effect [1]
Als er geen nieuw effect is geactiveerd, moeten we de huidige gebeurtenis opnieuw instellen wanneer deze vervalt. Dat gebeurt in de bijwerken()
methode. In elk frame is de reset-functie van het huidige effect toegewezen aan de reset_effect
veld. Als de tijd sinds het huidige effect begon de effectduur overschreed, dan is de reset_effect ()
functie wordt aangeroepen en de reset_effect
veld is ingesteld op Geen (er is momenteel geen actief effect).
# Reset speciaal effect indien nodig als self.reset_effect: elapsed = datetime.now () - self.effect_start_time if elangesed> = timedelta (seconds = c.effect_duration): self.reset_effect (self) self.reset_effect = None
Het lange paddle-effect werkt door de peddel 50% op te blazen. De reset-functie maakt het formaat van het apparaat weer normaal. De baksteenkleur is Oranje:
long_paddle = (colors.ORANGE, lambda g: g.paddle.bounds.inflate_ip (c.paddle_width // 2, 0), lambda g: g.paddle.bounds.inflate_ip (-c.paddle_width // 2, 0)),
Een ander effect dat helpt bij het achtervolgen van de bal is het langzame baleffect, dat eenvoudig de balsnelheid met één eenheid vertraagt. De baksteenkleur is Aquamarine.
slow_ball = (colors.AQUAMARINE2, lambda g: g.change_ball_speed (-1), lambda g: g.change_ball_speed (1)),
Als je grote getallen wilt, dan vind je het triple-points-effect dat je drie punten geeft voor elke steen die je raakt in plaats van het standaard één punt. De baksteenkleur is donkergroen.
tripple_points = (colors.DARKSEAGREEN4, lambda g: g.set_points_per_brick (3), lambda g: g.set_points_per_brick (1)),
Tot slot, een zeer nuttig effect is het effect van extra levens. Het geeft je gewoon een extra leven. Geen reset nodig eigenlijk. De baksteenkleur is goud.
extra_life = (colors.GOLD1, lambda g: g.add_life (), lambda g: None))
Er zijn verschillende natuurlijke richtingen om Breakout uit te breiden. Als u geïnteresseerd bent in het toevoegen van meer mogelijkheden en functies, volgen hier enkele ideeën.
Om Breakout een serieus spel te maken, heeft het levels nodig. Het spelen van slechts één scherm is niet genoeg. Aan het begin van elk niveau, zal je het scherm opnieuw instellen, maar de score behouden en leven zoals het is. Om het spel moeilijker te maken, kun je de balsnelheid op elk niveau iets verhogen of een andere laag stenen toevoegen.
Het toevoegen van een tweede bal als een tijdelijk effect levert heel wat chaos op. Het lastige hier is om beide ballen als gelijk te behandelen, ongeacht welke het origineel was. Als een bal weg is, gaat het spel verder met de bal die nog over was. Er gaat geen leven verloren.
Als je levels hebt met toenemende moeilijkheidsgraden, wordt de hoogste score een felbegeerde prijs. U kunt de hoge score in een bestand behouden om tussen de games te blijven bestaan. Wanneer een speler de hoogste score breekt, kun je een beetje pizazz toevoegen of zijn / haar naam laten schrijven (traditioneel slechts drie karakters).
In de huidige implementatie zijn alle speciale effecten gebonden aan stenen, maar je kunt effecten (goed en slecht) toevoegen die uit de lucht vallen en de speler moet ze verzamelen of ze vermijden.
Het ontwikkelen van Breakout met Python 3 en Pygame was een zeer lonende ervaring. Het is een zeer krachtige combinatie voor 2D-games (en ook 3D-games). Als je van Python houdt en je eigen games wilt maken, kun je niet verkeerd gaan met Pygame.
Ik ben absoluut van plan meer games met Python en Pygame te maken.
Tot slot, onthoud dat we veel inhoud van Python beschikbaar hebben voor verkoop en voor studie in de Envato-markt.