Veel ontwikkelaars gaan software-ontwikkeling aan omdat ze games willen bouwen. Niet iedereen kan een professionele game-ontwikkelaar zijn, maar iedereen kan zijn eigen games bouwen voor de lol en misschien ook voor de winst. In deze vijfdelige serie laat ik je zien hoe je 2D-games voor één speler maakt met Python 3 en het uitstekende Pygame-framework.
We zullen een versie van het klassieke Breakout-spel bouwen. Wanneer alles is gezegd en gedaan, heb je een goed begrip van wat er nodig is om je eigen spel te maken, ben je bekend met de mogelijkheden van Pygame en heb je een voorbeeldspel.
Dit zijn de functies en mogelijkheden die we zullen implementeren:
Wat jij zou niet Verwachten is een visueel aantrekkelijke game. Ik ben een programmeur en geen artiest. Ik maak me meer zorgen over de esthetiek van de code. Het resultaat van mijn visuele ontwerp kan best schokkend zijn. Aan de positieve kant, als je wilt verbeteren hoe deze versie van Breakout eruit ziet, heb je tons ruimte voor verbetering. Met die vreselijke waarschuwing uit de weg, hier is een screenshot:
De volledige broncode is hier beschikbaar.
Games gaan over het verplaatsen van pixels op het scherm en het maken van ruis. Vrijwel alle video- / computerspellen hebben de meeste van de volgende elementen. Buiten het bereik van dit artikel vallen client-server games en multi-player games, waarbij ook veel netwerkprogrammering betrokken is.
De hoofdlus van een spel wordt uitgevoerd en vernieuwt het scherm met vaste intervallen. Dit is je framerate en het dicteert hoe soepel de dingen zijn. Normaal gesproken vernieuwen games het scherm 30 tot 60 keer per seconde. Als u langzamer gaat, lijken objecten op het scherm schokkerig.
Binnen de hoofdlus zijn er drie hoofdactiviteiten: het afhandelen van evenementen, het bijwerken van de spelstatus en het tekenen van de huidige status van het scherm.
Gebeurtenissen in een spel bestaan uit alles wat buiten de controle van de spelcode gebeurt, maar is relevant voor de werking van de game. Als bijvoorbeeld in Breakout de speler op de pijl naar links drukt, moet de game de paddle naar links verplaatsen. Typische gebeurtenissen zijn toetsaanslagen (en releases), muisbewegingen, muisknoppen (vooral in menu's) en timergebeurtenissen (een speciaal effect vervalt bijvoorbeeld na 10 seconden).
De kern van elk spel is de staat: het spul dat het bijhoudt en tekent op het scherm. In Breakout omvat de staat de locatie van alle stenen, de positie en snelheid van de bal, en de positie van de paddle, evenals levens en de score.
Er is ook de hulpstatus die helpt bij het beheren van het spel:
Het spel moet zijn status op het scherm weergeven. Dit omvat het tekenen van geometrische vormen, afbeeldingen en tekst.
De meeste spellen simuleren een fysieke omgeving. In Breakout kaatst de bal objecten af en heeft een heel ruw rigid-body fysisch systeem op zijn plaats (als je dat zo mag noemen).
Meer geavanceerde games hebben mogelijk meer geavanceerde en realistische physics-systemen (vooral 3D-games). Merk op dat sommige spellen zoals kaartspellen helemaal geen fysica hebben, en dat is helemaal goed.
Er zijn veel spellen waarbij je speelt tegen een kunstmatige computertegenstander of tegenstanders, of er zijn vijanden die je proberen te doden of erger. Deze hersenspinsels van de game gedragen zich vaak op een schijnbaar intelligente manier in de wereld van de game.
Vijanden zullen je bijvoorbeeld achtervolgen en je locatie kennen. Breakout presenteert geen AI. Je speelt tegen de koude, harde stenen. De AI in games is echter vaak heel eenvoudig en volgt eenvoudige (of complexe) regels om pseudo-intelligente resultaten te bereiken.
Audio afspelen is een ander belangrijk aspect van games. Er zijn over het algemeen twee soorten audio: achtergrondmuziek en geluidseffecten. In Breakout concentreer ik me op geluidseffecten die kort worden afgespeeld wanneer verschillende gebeurtenissen plaatsvinden.
Achtergrondmuziek is gewoon muziek die constant op de achtergrond speelt. Sommige games gebruiken geen achtergrondmuziek en sommigen schakelen het op elk niveau.
De meeste spellen geven je een bepaald aantal levens en wanneer je levens opraken, is het spel afgelopen. Je hebt vaak ook een score die je een idee geeft van hoe goed je het doet en een motivatie om de volgende keer dat je speelt te verbeteren of gewoon opschept met je vrienden over je Breakout-vaardigheden. Veel spellen hebben levels die compleet anders zijn of de moeilijkheidsgraad verhogen.
Voordat we gaan duiken en beginnen met implementeren, laten we wat meer te weten komen over Pygame, die veel van het zware werk voor ons zal doen.
Pygame is een Python-framework voor gameprogrammering. Het is gebouwd op de top van SDL en heeft alle goede dingen:
makkelijk te leren
Type pip pygame installeren
om het te installeren. Als je iets anders nodig hebt, volg dan de instructies in het gedeelte Aan de slag van de Wiki. Als u macOS Sierra uitvoert zoals ik, kunt u problemen ondervinden. Ik kon Pygame probleemloos installeren en de code leek prima te werken, maar het spelvenster kwam nooit opdagen.
Dat is nogal een spelbreker als je een spel speelt. Uiteindelijk moest ik mijn toevlucht nemen tot het draaien op Windows in een VirtualBox VM. Hopelijk is dit probleem opgelost tegen de tijd dat u dit artikel leest.
Games moeten veel informatie beheren en vergelijkbare bewerkingen uitvoeren op veel objecten. Breakout is een minigame, maar alles proberen te beheren in één bestand is overweldigend. In plaats daarvan heb ik ervoor gekozen om een bestandsstructuur en architectuur te maken die geschikt zijn voor veel grotere spellen.
├── Pipfile ├── Pipfile.lock ├── README.md ├── ball.py ├── breakout.py ├── brick.py ├── button.py ├── colors.py ├── config .py ├── game.py ├── game_object.py ├── afbeeldingen │ └── achtergrond.jpg ├── paddle.py ├── sound_effects │ ├── brick_hit.wav │ ├── effect_done.wav │ ├── level_complete.wav │ └── paddle_hit.wav └── text_object.py
De Pipfile en Pipfile.lock zijn de moderne manier om afhankelijkheden in Python te beheren. De afbeeldingenmap bevat afbeeldingen die door het spel worden gebruikt (alleen de achtergrondafbeelding in deze incarnatie), en de map sound_effects bevat korte audiofragmenten die worden gebruikt als (u raadt het al) geluidseffecten.
De bestanden ball.py, paddle.py en brick.py bevatten code die specifiek is voor elk van deze Breakout-objecten. Ik zal ze later in de serie in de diepte bespreken. Het text_object.py-bestand bevat code voor het weergeven van tekst op het scherm en het bestand background.py bevat de Breakout-specifieke gamecellen.
Er zijn echter verschillende modules die een los, universeel skelet vormen. De klassen die daar gedefinieerd zijn, kunnen hergebruikt worden voor andere Pygame-spellen.
Het GameObject vertegenwoordigt een visueel object dat weet hoe zichzelf te renderen, zijn grenzen te behouden en zich te verplaatsen. Pygame heeft eigenlijk een Sprite-klasse die een vergelijkbare rol heeft, maar in deze serie wil ik laten zien hoe dingen op een laag niveau werken en niet afhankelijk zijn van te veel voorverpakte magie. Hier is de GameObject-klasse:
van pygame.rect import Recte klasse GameObject: def __init __ (zelf, x, y, w, h, snelheid = (0,0)): self.bounds = Rect (x, y, w, h) self.speed = snelheid @property def left (self): return self.bounds.left @property def right (self): return self.bounds.right @property def top (self): retourneer self.bounds.top @property def bottom (self): return self.bounds.onderkant @property def width (self): return self.bounds.width @property def height (self): return self.bounds.height @property def center (self): retourneer self.bounds.center @ property def centerx (self): return self.bounds.centerx @property def centery (self): return self.bounds.centery def draw (self, surface): pass def move (self, dx, dy): self.bounds = self .bounds.move (dx, dy) def update (self): if self.speed == [0, 0]: return self.move (* self.speed)
Het GameObject is ontworpen om als basisklasse voor andere objecten te dienen. Het stelt direct veel van de eigenschappen van zijn zelf-bounds-rechthoek bloot, en in zijn bijwerken()
methode verplaatst het object volgens zijn huidige snelheid. Het doet niets in zijn trek()
methode, die door subklassen moet worden overschreven.
De klasse Game is de kern van het spel. Het loopt de hoofdlus. Het heeft veel nuttige functionaliteit. Laten we het methode op methode nemen.
De __in het__()
methode initialiseert Pygame zelf, het lettertypesysteem en de audiomixer. De reden dat u drie verschillende oproepen moet doen, is omdat niet alle Pygame-spellen alle componenten gebruiken, dus u bepaalt welke subsystemen u gebruikt en alleen die met hun specifieke parameters initialiseert. Het maakt de achtergrondafbeelding, het hoofdoppervlak (waar alles wordt getekend) en de gameklok met de juiste framesnelheid.
Het lid self.objects bewaart alle game-objecten die moeten worden gerenderd en bijgewerkt. De verschillende handlers beheren lijsten van de handlerfunctie die moet worden aangeroepen wanneer bepaalde gebeurtenissen plaatsvinden.
import pygame import sys uit collecties import defaultdict class Game: def __init __ (zelf, onderschrift, breedte, hoogte, back_image_bestandsnaam, frame_rate): self.background_image = \ pygame.image.load (back_image_filename) self.frame_rate = frame_rate self.game_over = False self.objects = [] pygame.mixer.pre_init (44100, 16, 2, 4096) pygame.init () pygame.font.init () self.surface = pygame.display.set_mode ((width, height)) pygame. display.set_caption (caption) self.clock = pygame.time.Clock () self.keydown_handlers = defaultdict (lijst) self.keyup_handlers = defaultdict (lijst) self.mouse_handlers = []
De bijwerken()
en trek()
methoden zijn heel eenvoudig. Ze herhalen alleen alle beheerde game-objecten en noemen de bijbehorende methoden. Als twee game-objecten elkaar overlappen, bepaalt de volgorde in de objectenlijst welk object eerst wordt weergegeven en de andere deze geheel of gedeeltelijk bedekt.
def update (self): for o in self.objects: o.update () def draw (self): for o in self.objects: o.draw (self.surface)
De handle_events ()
methode luistert naar gebeurtenissen gegenereerd door Pygame, zoals toets- en muisgebeurtenissen. Voor elke gebeurtenis roept het alle handlerfuncties op die geregistreerd zijn om dit type gebeurtenis af te handelen.
def handle_events (self): voor evenement in pygame.event.get (): if event.type == pygame.QUIT: pygame.quit () sys.exit () elif event.type == pygame.KEYDOWN: voor handler in self.keydown_handlers [event.key]: handler (event.key) elif event.type == pygame.KEYUP: voor handler in self.keydown_handlers [event.key]: handler (event.key) elif event.type in (pygame .MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION): voor handler in self.mouse_handlers: handler (event.type, event.pos)
eindelijk, de rennen()
methode voert de hoofdlus uit. Het loopt tot de spel is over
lid wordt Waar. In elke iteratie wordt de achtergrondafbeelding weergegeven en wordt de achtergrond opgeroepen handle_events ()
, bijwerken()
, en trek()
methoden.
Vervolgens wordt de weergave bijgewerkt, waardoor het fysieke scherm wordt bijgewerkt met alle inhoud die tijdens deze iteratie is weergegeven. Last, but not least, het roept de clock.tick ()
methode om te bepalen wanneer de volgende iteratie wordt aangeroepen.
def run (self): while not self.game_over: self.surface.blit (self.background_image, (0, 0)) self.handle_events () self.update () self.dhand () pygame.display.update () self.clock.tick (self.frame_rate)
In dit deel heb je de basis van gameprogrammering en alle componenten die bij het maken van games horen, geleerd. Daarna keken we naar Pygame zelf en hoe het te installeren. Ten slotte hebben we ons verdiept in de spelarchitectuur en de directorystructuur onderzocht, de GameObject
klasse en de klasse Game.
In deel twee zullen we kijken naar de textobject
klasse die wordt gebruikt om tekst op het scherm weer te geven. We zullen het hoofdvenster maken, inclusief een achtergrondafbeelding, en dan zullen we leren hoe objecten zoals de bal en de paddle te tekenen.
Bekijk ook wat we beschikbaar hebben voor verkoop en voor studie in de Envato-markt en aarzel niet om vragen te stellen en uw waardevolle feedback te geven met behulp van de onderstaande feed.