Python game: Maak hier simpel een 2D Multiplayer Shooter met pygame!

Een Python game maken, dat is toch gaaf? Met dit artikel ga jij eenvoudig een “2D Multiplayer Shooter” met pygame maken aan de hand van 11 stappen. De code van deze Python game wordt nauwkeurig uitgelegd, zodat je ook daadwerkelijk begrijpt hoe het werkt. Na het artikel heb je ook inspiratie en kennis gekregen om meer Python games te gaan maken. Game development is erg leuk en niet moeilijk met Python. Lees lekker verder en maak de “2D Multiplayer Shooter” met pygame!

Voorkennis

Voordat we beginnen, is het handig om alvast voorkennis te hebben over de basis van het programmeren en OOP (Object Oriented Programming). In het Nederlands betekent OOP: Object georiënteerd programmeren.

Als je nog helemaal niet bekend bent met programmeren, dan is het handig om in het e-book “Snelcursus Leren Programmeren” eerst de basis van het programmeren te leren.

We gaan tijdens het uitleggen van de code van deze Python game niet (echt) dieper in op de werking van de basisonderdelen van het programmeren. De basisonderdelen van het programmeren leer je in het e-book “Snelcursus Leren Programmeren” in Java, Python en PHP.

In het artikel “Object georiënteerd programmeren: Uitleg + codevoorbeelden” leer je meer over de basis van object georiënteerd programmeren.

Ben je klaar om gelijk aan de slag te gaan? Lees dan lekker verder!

Stap 1: Python, PyCharm en pygame installeren

Zorg er eerst voor dat Python is geïnstalleerd op je computer. In “Python installeren” lees je hoe je dat doet. Test daarna of Python goed is geïnstalleerd.

In “Hello World! in Python” maak je een simpel Python programma en kan je gelijk testen of de Python installatie goed is gegaan. Het simpele Python programma maak je in PyCharm en daardoor heb je dat ook alvast geïnstalleerd voor het maken van de Python game “2D Multiplayer Shooter”.

Nu moeten we pygame nog installeren. Eerst gaan we een nieuw project aanmaken in PyCharm. Open PyCharm en klik in het menu op File > New Project…

Je komt in dit venster terecht:

Create Project venster PyCharm
Klik op de afbeelding om het te vergroten

Geef het project een naam, wij noemen het “2d-multiplayer-shooter” en klik op “Create”. Het nieuwe project wordt nu voor je aangemaakt in PyCharm.

Voordat we echt gaan programmeren, moeten we pygame nog installeren, zoals eerder gezegd. Pygame is een Python library die het voor (Python) developers makkelijker maakt om games te maken. Je hoeft niet opnieuw het wiel uit te vinden.

Pygame is miljoenen keren gedownload en heeft een grote community. Dit betekent dat de library steeds verder wordt ontwikkeld en bugs er snel worden uitgehaald. Python game development zonder deze library is nauwelijks voor te stellen.

Als je het nieuwe aangemaakte project open hebt staan in PyCharm, dan klik je op File > Settings…

Klik op Project: (Naam van je project) en daarna op Project Interpreter. Je komt in het onderstaande scherm terecht:

Project Interpreter venster PyCharm
Klik op de afbeelding om het te vergroten

De witte pijl wijst naar het plusje (+). Klik op het plusje. Je komt in het onderstaande scherm terecht:

Available Packages venster PyCharm
Klik op de afbeelding om het te vergroten

Zoek naar “pygame” en klik op “Install Package”. Is pygame succesvol geïnstalleerd? Dan ben je helemaal gereed om de Python game te gaan maken. Welkom in de game development wereld van Python!

Stap 2: Game window (Spelvenster) aanmaken

We gaan eerst een nieuw Python bestand aanmaken. Selecteer het aangemaakte project, in ons geval “2d-multiplayer-shooter”, en klik met je rechtermuisknop op het project. Ga daarna naar New > Python File. Je komt hier terecht:

New Python file PyCharm

Geef het Python bestand “main” als naam en dubbelklik op “Python File”. In het “main.py” bestand gaan we de game window aanmaken en er komt ook andere belangrijke code van de game te staan.

We gebruiken pygame, dus het is ook de bedoeling dat we deze library importeren. In de eerste regel van het “main.py” bestand komt het volgende te staan:

import pygame

Op de vierde regel gaan we pygame initialiseren:

import pygame

# Pygame initialiseren
pygame.init()

Nu kan je gebruik gaan maken van de functionaliteiten van pygame. Een screen/scherm aanmaken is niet lastig in pygame:

import pygame

# Pygame initialiseren
pygame.init()

# Screen aanmaken
screen = pygame.display.set_mode((700, 500))

Let op de dubbele haakjes bij set_mode(()). 700 is de width (Breedte) en 500 is de length (Lengte). Op de website van pygame staat een diepere uitleg over een screen aanmaken.

Als je nu het programma uitvoert, dan verdwijnt het scherm (Screen) meteen. Klik in PyCharm in het menu op Run > Run… (Alt + Shift + F10) en klik tot slot op het “main” bestand om het programma uit te voeren.

Nadat je dit hebt gedaan, zie je rechtsboven in PyCharm als het goed is een groen pijltje met links daarnaast “main” geselecteerd:

Run "main" PyCharm

Door op dat groene pijltje te klikken, kan je het “main” bestand ook makkelijk uitvoeren.

Maar waarom verdwijnt het scherm meteen? Dit komt omdat na de laatste regel code het programma gelijk eindigt. Gelukkig is dit op te lossen.

Om het scherm zichtbaar te houden zolang als je wil, kan je een infinite loop aanmaken. Onderaan de code voeg je dit toe:

while True:
    pass

Probeer het programma nu eens uit te voeren. Nu hebben we nog een probleem, toch? De loop blijft oneindig doorgaan en we kunnen het programma niet sluiten. Ga in Windows naar “Task Manager” en sluit het draaiende programma af, als het niet op de normale manier lukt. Bij een Mac sluit je een draaiend programma met de “Activity Monitor”.

We willen dat een gebruiker het programma op een normale manier af kan sluiten. Met de volgende code lukt dat:

import pygame

# Pygame initialiseren
pygame.init()

# Screen aanmaken
screen = pygame.display.set_mode((700, 500))

# Game loop
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

De while loop werkt vrij simpel. De loop blijft draaien totdat de gebruiker de game sluit door op de bekende “X” te klikken, rechtsboven in het scherm.

De for loop in regel 12 gaat door alle gebruikershandelingen heen en als de gebruiker op “X” klikt, dan eindigt de while loop en stopt de game.

We hebben nu een mooi scherm aangemaakt. Uiteraard zou je zelf een andere breedte en lengte aan het scherm kunnen geven. Wij houden echter deze breedte en lengte aan.

Stap 3: Classes aanmaken

Deze stap is vrij eenvoudig. We gaan namelijk maar twee classes aanmaken en doen er verder nog niks mee. In latere stappen gaan we de classes verder aanvullen met code.

Je weet nu hoe je een nieuw Python bestand aanmaakt in PyCharm. We gaan twee classes maken, dus we hebben twee nieuwe Python bestanden nodig.

De classes heten “Player” en “Bullet”. De Python bestanden geven we dezelfde naam. Later gaan we van de class “Player” en “Bullet” allebei twee objecten maken. Er zijn namelijk twee Players en elke player heeft een kogel (Bullet).

Maak een Python bestand “player” aan. De class ziet er zo uit:

class Player:

    def __init__(self):
        print('Als je een object aanmaakt, zie je dit op je scherm!')

Daarna maak je een Python bestand “bullet” aan. De class ziet er zo uit:

class Bullet:
    
    def __init__(self):
        print('Als je een object aanmaakt, zie je dit op je scherm!')

In beide classes zie je def __init__(self) terugkomen. Dit is de constructor van de class. Elke keer als je een object aanmaakt, dan wordt deze method automatisch aangeroepen. self is verplicht als eerste argument in de constructor in een Python class.

Stap 4: Achtergrond en titel veranderen

We hebben een scherm aangemaakt, maar het is heel saai met een zwarte achtergrond en de titel van de game staat ook nog standaard op “pygame window”. Dit gaan we nu veranderen.

Laten we beginnen met de achtergrond. De achtergrondafbeelding is van Freepik gedownload en is ontworpen door vectorpocket. Zelf hebben we de achtergrondafbeelding iets lichter gemaakt en precies de afmetingen gegeven die het scherm ook heeft, namelijk een breedte van 700 pixels en een lengte van 500 pixels.

Dit is de achtergrondafbeelding die wij gebruiken. Je zou natuurlijk zelf ook een andere afbeelding kunnen kiezen op Freepik en de afbeelding de juiste afmetingen geven met PicResize. Een afbeelding online bewerken, om het bijvoorbeeld lichter te maken, is eenvoudig mogelijk met Photopea.

Maak een nieuwe map aan binnen je project en noem deze map “images”. Dit doe je door je project te selecteren en daarna klik je in het menu op File > New… > Directory. Geef de map de naam “images” en klik op Enter.

Zorg dat de achtergrondafbeelding in de “images” map staat. Dan gaan we de achtergrondafbeelding laden. Onder de code waarin je het scherm aanmaakt, kan je dit zetten:

# Achtergrondafbeelding laden
background = pygame.image.load('images/war-background.jpg')

Zoals je ziet, wordt er netjes verwezen naar de achtergrondafbeelding die in de “images” map staat. Op deze manier kan de achtergrondafbeelding goed worden geladen. Daarna roep je in de while loop de blit() method aan om de achtergrondafbeelding op het scherm te krijgen. Bovenin de while loop zet je deze code:

# blit() method aanroepen om achtergrondafbeelding op scherm te krijgen
screen.blit(background, (0, 0))

Probeer de game eens uit te voeren. Je ziet nu nog steeds geen achtergrondafbeelding. Dit komt doordat het scherm niet wordt geüpdatet. Zo update je het scherm in de while loop:

# Update het scherm
pygame.display.update()

Zet deze code onderaan neer in de while loop. Het is nu de beurt aan de titel. Dit is makkelijk aan te passen. Zet tussen het aanmaken van het scherm en de achtergrondafbeelding laden deze code neer:

# Titel game
pygame.display.set_caption("2D MultiPlayer Shooter")

Als je de game uitvoert, dan ziet het er momenteel zo uit:

Game uitvoeren stap 4

Voor de volledigheid, dit is alle code in “main.py” tot nu toe:

import pygame

# Pygame initialiseren
pygame.init()

# Screen aanmaken
screen = pygame.display.set_mode((700, 500))

# Titel game
pygame.display.set_caption("2D MultiPlayer Shooter")

# Achtergrondafbeelding laden
background = pygame.image.load('images/war-background.jpg')

# Game loop
running = True
while running:

    # blit() method aanroepen om achtergrondafbeelding op scherm te krijgen
    screen.blit(background, (0, 0))

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # Update het scherm
    pygame.display.update()

Stap 5: Afbeeldingen players toevoegen en laten bewegen door keyboard input

De afbeeldingen voor de players zijn van Flaticon gedownload. We hebben in de editor op Flaticon de afbeelding horizontaal omgedraaid voor de andere player, zodat beide players naar elkaar toe kunnen schieten.

Dit zijn de afbeeldingen voor de players die wij gebruiken:

Voordat we de players op het scherm plaatsen, is het handig om te weten hoe de X- en Y-as van het scherm in elkaar zit:

X- en Y-as pygame game window

Van links naar rechts is de X-as en van boven naar beneden de Y-as. De coördinaten stel je op deze manier op: (X-waarde, Y-waarde). Helemaal linksboven is zowel de X- als de Y-waarde 0, dus zijn de coördinaten ook (0,0).

Stel dat je een player of een ander object precies in het midden wil plaatsen, dan zijn (350, 250) de coördinaten. De X-waarde is 350 en de Y-waarde 250.

De coördinaten helemaal rechtsonder spreken voor zich. Dat zal je nu wel begrijpen. Belangrijk is om het principe van de X- en Y-as van het scherm te begrijpen, zodat je snapt wat er in de code gebeurd.

Nu is het tijd om weer de code in te duiken. Eerst gaan we de afbeeldingen van de players laden. Als het goed is, weet je hoe dat moet:

player1Image = pygame.image.load('images/player1.jpg')
player2Image = pygame.image.load('images/player2.jpg')

Deze code kan je onder de code zetten waarin je de achtergrondafbeelding laadt in “main.py”. De player geven we een startpositie bij het aanmaken van een object van de class “Player”. Dus we gaan naar de class “Player” en gaan daar code toevoegen.

We maken een variable x en y en geven ze beiden als waarde 0. In de constructor maken we de parameters x en y aan en zorgen dat de variables x en y van de class de waardes krijgen van de parameters:

x = 0
y = 0

def __init__(self, x, y):
    self.x = x
    self.y = y

Tot slot maken we in de class een method aan om de afbeelding van een “Player” object op het scherm te krijgen. Als het goed is weet je nog hoe dat werkt van de achtergrondafbeelding. In de “Player” class ziet dat er zo uit:

def draw(self, screen, playerimage):
    screen.blit(playerimage, (self.x, self.y))

Bij het aanroepen van de draw() method met een “Player” object, hebben we de screen variable nodig uit “main.py”. De screen variable maakt een scherm aan, weet je nog? Vandaar dat screen de tweede parameter is in de draw() method. Anders kan je ook niet de blit() method aanroepen, zonder de screen variable uit “main.py”.

De afbeelding hoort in de blit() method en daarom is image de derde parameter in de draw() method. De coördinaten zijn de variables x en y van de class “Player”. Deze roep je aan met self, de eerste parameter in de draw() method.

De volledige “Player” class ziet er momenteel zo uit:

class Player:

    x = 0
    y = 0

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def draw(self, screen, image):
        screen.blit(image, (self.x, self.y))

We gaan twee objecten aanmaken van de “Player” class en geven de startposities mee, dit doen we in het “main.py” bestand. Dit kan je doen onder het laden van de afbeeldingen van de achtergrond en de players:

p1 = Player(626, 400)
p2 = Player(10, 10)

Zet deze code onder import pygame op de derde regel om de class “Player” te importeren:

from player import Player

De players zijn nog niet op het scherm te zien, zou je al weten hoe we dit moeten doen? Voor de laatste regel code in de while loop (Scherm updaten), roepen we met de aangemaakte objecten de draw() method aan:

p1.draw(screen, player1Image)
p2.draw(screen, player2Image)

Zoals je ziet, geven we de screen variable en afbeeldingen van beide players mee als arguments bij het aanroepen van de draw() method. Op deze manier kan de blit() method worden aangeroepen in de draw() method en de players op het scherm geplaatst worden.

Voer de game nu eens uit. Als het goed is, worden de players nu op het scherm geplaatst:

Players op scherm pygame

De player rechtsonder met het rode t-shirt is player 1 en de player linksboven met het blauwe t-shirt is player 2. Het is allemaal leuk en aardig dat de players nu op het scherm staan, maar we willen ze met knoppen op het toetsenbord kunnen laten bewegen.

Gelukkig is dit niet zo lastig. Eerst halen we de status van alle knoppen op het toetsenbord op. Dit stukje code zet je boven de code waarin we met de “Player” objecten de draw() method aanroepen:

keys = pygame.key.get_pressed()

We willen dat player 1 met de pijltjestoets (Pijl omhoog) omhoog kan bewegen en met de pijltjestoets (Pijl omlaag) omlaag kan bewegen. Bij player 2 willen we het met de “w” toets omhoog laten bewegen en met de “s” toets omlaag.

In if-statements kunnen we checken wanneer de players de eerder genoemde toetsen/knoppen gebruiken. Als de players deze toetsen gebruiken, dan laten we de players op het scherm (objecten) naar boven of beneden bewegen. Dit doen we door de y-coördinaat te veranderen van de objecten. De code kan geplaatst worden onder het stukje code waarin we de status van de knoppen op het toetsenbord ophalen:

if keys[pygame.K_UP]:
    p1.y -= 2
if keys[pygame.K_DOWN]:
    p1.y += 2
if keys[pygame.K_w]:
    p2.y -= 2
if keys[pygame.K_s]:
    p2.y += 2

Het aftrekken van 2 van de y-coördinaat van een object zorgt ervoor dat het object omhoog gaat. Optelling zorgt voor het omgekeerde, dus het object gaat omlaag. Je zou de waarde 2 kunnen veranderen als je wil dat het object sneller of langzamer omhoog en omlaag beweegt.

De volledige code in “main.py” ziet er momenteel zo uit:

import pygame

from player import Player

# Pygame initialiseren
pygame.init()

# Screen aanmaken
screen = pygame.display.set_mode((700, 500))

# Titel game
pygame.display.set_caption("2D MultiPlayer Shooter")

# Afbeeldingen laden
background = pygame.image.load('images/war-background.jpg')
player1Image = pygame.image.load('images/player1.png')
player2Image = pygame.image.load('images/player2.png')

# Objecten
p1 = Player(626, 400)
p2 = Player(10, 10)

# Game loop
running = True
while running:

    # blit() method aanroepen om achtergrondafbeelding op scherm te krijgen
    screen.blit(background, (0, 0))

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # De status van alle toetsenbord knoppen ophalen
    keys = pygame.key.get_pressed()

    # Checken welke toetsenbord knoppen worden gebruikt en players verticaal laten bewegen
    if keys[pygame.K_UP]:
        p1.y -= 2
    if keys[pygame.K_DOWN]:
        p1.y += 2
    if keys[pygame.K_w]:
        p2.y -= 2
    if keys[pygame.K_s]:
        p2.y += 2

    # Objecten op scherm plaatsen
    p1.draw(screen, player1Image)
    p2.draw(screen, player2Image)

    # Update het scherm
    pygame.display.update()

Als je de game nu uitvoert, dan kan je de players laten bewegen met de eerder genoemde toetsen:

Players bewegen pygame

Een Python game maken wordt op deze manier steeds leuker, toch? De code van de Python game ga je steeds beter begrijpen en je duikt steeds iets dieper in de game development wereld van Python. Op naar de volgende stap!

Stap 6: Grenzen van game instellen

Het is even tijd voor een adempauze. Deze stap is niet zo lastig en we lopen hier zo doorheen. Misschien heb je al gemerkt dat de players door de boven- en onderkant van het scherm heen kunnen gaan? Dit is natuurlijk niet de bedoeling.

In code is dit gelukkig vrij simpel op te lossen. We gaan een extra method aanmaken in de “Player” class. Deze method kan je onder de laatste regel code zetten:

def boundaries(self, y):
    if y <= 0:
        self.y = 0
    if y >= 436:
        self.y = 436

De if-statements in de boundaries() method zijn simpel. Als de y-coördinaat van een “Player” object kleiner of gelijk is aan 0, dan wordt de y-coördinaat weer teruggezet naar 0. Zo kan een “Player” object nooit door de bovenkant van het scherm gaan.

Als de y-coördinaat van een “Player” object groter of gelijk is aan 436, dan wordt de y-coördinaat weer teruggezet naar 436. Zo kan een “Player” object nooit door de onderkant van het scherm gaan.

Waarom 436 terwijl de lengte van het scherm 500 pixels is? Dit doen we omdat de lengte van de afbeeldingen van de “Player” objecten 64 pixels zijn. Dus bij een y-coördinaat van 436 raakt de afbeelding van een “Player” object al de onderkant van het scherm.

Tot slot moeten we met de beide “Player” objecten nog de boundaries() method aanroepen en de bijbehorende y-coördinaat meegeven als argument. Deze code kan je zetten in de while loop onder de if-statements om te checken welke toetsenbord knoppen er worden gebruikt en boven de code waar de “Player” objecten op het scherm worden geplaatst:

p1.boundaries(p1.y)
p2.boundaries(p2.y)

Voer de game nu eens uit. Je zal zien dat beide “Player” objecten niet door de boven- en onderkant van het scherm kunnen gaan. Top! Op naar de volgende stap.

Stap 7: Kogels maken en laten afschieten

Elke player heeft een kogel en kan deze afschieten. Daarom hebben we twee kogels nodig. De kogels hebben we van Flaticon gedownload en voor beide players de juiste kant opgedraaid in de online editor op Flaticon.

Dit zijn de kogels die we gebruiken in de game:

Als eerste gaan we weer de afbeeldingen laden. Als het goed is, weet je nu hoe dat werkt:

bulletPlayer1Image = pygame.image.load('images/bullet-player1.png')
bulletPlayer2Image = pygame.image.load('images/bullet-player2.png')

Deze code kan je zetten onder de code in “main.py” waar we de andere afbeeldingen laden. Daarna gaan we naar de “Bullet” class. Hier gaan we code toevoegen.

Er komen drie variables in de “Bullet” class. Net zoals de player, heeft de kogel ook logischerwijs een x- en een y-coördinaat. Daarnaast heeft de kogel een status die aangeeft wanneer de kogel “ready” is om afgevuurd te worden en wanneer de kogel afgevuurd is (“fire” status):

x = 0
y = 0
state = "ready"

Later zal je de status van een kogel beter gaan begrijpen en komt er ook een “stop” status bij. In de constructor van de “Bullet” class bepalen we alvast de x-coördinaat. Dit x-coördinaat staat al vast en moet hetzelfde zijn als de x-coördinaat van de player waarbij de kogel hoort:

def __init__(self, x):
    self.x = x

Tot slot voegen we in de “Bullet” class nog een method toe om de kogel op het scherm te laten verschijnen. De draw() method zal je bekend voorkomen van de “Player” class, alleen is er nu een extra y parameter bij gekomen.

De y parameter gaat ervoor zorgen dat de kogel wordt afgevuurd vanuit een logische plek op het scherm, de plek van de player waar de kogel bijhoort. De y parameter moet dus uiteindelijk de y-coördinaat van de bijbehorende player krijgen.

Inclusief de draw() method, ziet alle code er in de “Bullet” class momenteel zo uit:

class Bullet:

    x = 0
    y = 0
    state = "ready"

    def __init__(self, x):
        self.x = x

    def draw(self, screen, bulletimage, y):
        self.y = y
        screen.blit(bulletimage, (self.x, self.y))

We gaan weer naar het “main.py” bestand en gaan daar twee objecten maken van de “Bullet” class. Deze objecten kan je onder de code zetten die de p1 en p2 objecten aanmaken:

b1 = Bullet(p1.x)
b2 = Bullet(p2.x)

Vergeet niet om de “Bullet” class te importeren, net zoals je bij de “Player” class hebt gedaan. Dit stukje code zet je op de derde regel, boven de code die de “Player” class importeert:

from bullet import Bullet

Je ziet dat als arguments de x-coördinaat van de “Player” objecten wordt meegegeven bij het aanmaken van de “Bullet” objecten. Zo worden de kogels zo dadelijk afgevuurd vanaf de juiste x-coördinaat plek van de bijbehorende player. De x-coördinaten van de players veranderen niet in de game, vandaar dat deze code niet in de while loop staat.

b1 is de kogel die hoort bij het object p1 (Player 1) en b2 is de kogel die hoort bij het object p2 (Player 2). Bij een druk op een knop op het toetsenbord willen we checken of de status van de kogel “ready” is, zodat de kogel afgevuurd kan worden.

Als de status van de kogel “ready” is, dan geven we de waarde van de y-coördinaat van het “Player” object dat bij de kogel hoort aan de waarde van de y-coördinaat van de kogel. Zo heeft de kogel naast de x-coördinaat ook de y-coördinaat van de player als startpunt en wordt de kogel door de juiste player geschoten en vanaf de plek waar de player staat op het scherm.

Daarnaast zetten we de status van het “Bullet” object naar “fire”. Player 1 kan schieten met spatie en player 2 kan schieten met “d” op het toetsenbord. De code zetten we in “main.py” onder de if-statements waarin gecheckt wordt welke toetsenbord knoppen gebruikt worden om de players verticaal te laten bewegen:

if keys[pygame.K_SPACE]:
    if b1.state == "ready":
        b1.y = p1.y
        b1.state = "fire"
if keys[pygame.K_d]:
    if b2.state == "ready":
        b2.y = p2.y
        b2.state = "fire"

We zijn al een eindje op weg om allebei de “Player” objecten (p1 en p2) kogels te laten schieten, maar momenteel kunnen ze dat nog steeds niet.

Er wordt pas een kogel afgevuurd als de status van de kogel “fire” is. Bij player 1 verandert de status van de bijbehorende kogel naar “fire” bij een druk op spatie en bij player 2 bij een druk op de “d” toets, zoals we eerder in de code hebben doorgevoerd.

Per “Bullet” object moeten we dus checken of de status “fire” is, voordat de kogel kan worden afgevuurd door de bijbehorende player. Als dat zo is, dan wordt de kogel op het scherm vertoond door de draw() method aan te roepen. Naast de bekende arguments, geven we ook de y-coördinaat van de kogel mee. Eerder in de code hebben we dit y-coördinaat al de waarde gegeven van de y-coördinaat van het bijbehorende “Player” object.

Op deze manier wordt de kogel op de juist plek door de bijbehorende player afgevuurd, vanaf de plek waar de player staat op het scherm. De kogel moet natuurlijk ook bewegen en dat kan door steeds 3 af te trekken van de x-coördinaat van de kogel. Je zou de waarde 3 kunnen verhogen of verlagen om de kogel sneller of langzamer te laten gaan.

Wat hierboven is beschreven, ziet er zo uit in code:

if b1.state == "fire":
    b1.draw(screen, bulletPlayer1Image, b1.y)
    b1.x -= 3
if b2.state == "fire":
    b2.draw(screen, bulletPlayer2Image, b2.y)
    b2.x += 3

Deze code kan je toevoegen voor de laatste regel code waarin het scherm wordt geüpdatet in “main.py” in de while loop. Voer de game nu eens uit. Nu kunnen beide players maar één kogel afvuren. Dat is ook saai, toch?

We willen dat beide players meerdere kogels kunnen afvuren. Als de kogels de zijkant van het scherm raken, dan geven we in code de x-coördinaat van de kogel weer de waarde van de x-coördinaat van de bijbehorende player. Ook veranderen we de status van de kogel naar “ready”.

Voor player 1 is de x-coordinaat 0 de zijkant van het scherm, omdat player 1 naar links schiet. Voor player 2 is x-coordinaat 676 de zijkant van het scherm, omdat player 2 naar rechts schiet. De breedte van het scherm is 700, maar we trekken daar 24 vanaf, omdat de breedte van de kogel 24 pixels is.

Op deze manier begint het proces weer opnieuw. Als player 1 op spatie drukt of player 2 op “d”, dan begint het proces zoals eerder uitgelegd om de kogel af te vuren. Dit blijft doorgaan zolang de game loopt en er nog niemand heeft gewonnen.

Voeg dit stukje code toe onder de if-statements van de status van de kogels, die je zonet in de code hebt gezet, in de while loop in “main.py”:

if b1.x <= 0:
    b1.x = p1.x
    b1.state = "ready"
if b2.x >= 676:
    b2.x = p2.x
    b2.state = "ready"

Voer de game nu eens uit. Je zal zien dat je meerdere kogels kunt afvuren met beide players, zolang de game blijft lopen. Er is misschien nog wel een klein dingetje. Player 2 vuurt de kogel niet helemaal exact af aan het einde van zijn geweer. De kogel lijkt een beetje vanachter zijn hoofd te worden afgeschoten, zoals je hier ook ziet:

Player 2 schieten pygame

Dit kan netter, maar we hebben het niet gefixt in de code en we willen lezers ook graag laten nadenken. Hoe zou jij dit (kleine) probleem oplossen? Laat onderin het artikel een reactie achter met jouw oplossing.

De volledige code in “main.py” ziet er momenteel zo uit:

import pygame

from bullet import Bullet
from player import Player

# Pygame initialiseren
pygame.init()

# Screen aanmaken
screen = pygame.display.set_mode((700, 500))

# Titel game
pygame.display.set_caption("2D MultiPlayer Shooter")

# Afbeeldingen laden
background = pygame.image.load('images/war-background.jpg')
player1Image = pygame.image.load('images/player1.png')
player2Image = pygame.image.load('images/player2.png')
bulletPlayer1Image = pygame.image.load('images/bullet-player1.png')
bulletPlayer2Image = pygame.image.load('images/bullet-player2.png')

# Objecten
p1 = Player(626, 400)
p2 = Player(10, 10)
b1 = Bullet(p1.x)
b2 = Bullet(p2.x)

# Game loop
running = True
while running:

    # blit() method aanroepen om achtergrondafbeelding op scherm te krijgen
    screen.blit(background, (0, 0))

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # De status van alle toetsenbord knoppen ophalen
    keys = pygame.key.get_pressed()

    # Checken welke toetsenbord knoppen worden gebruikt en players verticaal laten bewegen
    if keys[pygame.K_UP]:
        p1.y -= 2
    if keys[pygame.K_DOWN]:
        p1.y += 2
    if keys[pygame.K_w]:
        p2.y -= 2
    if keys[pygame.K_s]:
        p2.y += 2
    # Bij druk op bijbehorende knop checken of bullet state "ready" is, zoja dan y-coördinaat startpositie
    # van player geven aan y-coordinaat van bullet en status veranderen
    if keys[pygame.K_SPACE]:
        if b1.state == "ready":
            b1.y = p1.y
            b1.state = "fire"
    if keys[pygame.K_d]:
        if b2.state == "ready":
            b2.y = p2.y
            b2.state = "fire"

    # Grenzen scherm checken en players niet door grenzen heen laten gaan
    p1.boundaries(p1.y)
    p2.boundaries(p2.y)

    # Objecten op scherm plaatsen
    p1.draw(screen, player1Image)
    p2.draw(screen, player2Image)

    # Als state van bullet "fire" is, dan bullet op scherm laten vertonen en schieten
    if b1.state == "fire":
        b1.draw(screen, bulletPlayer1Image, b1.y)
        b1.x -= 3
    if b2.state == "fire":
        b2.draw(screen, bulletPlayer2Image, b2.y)
        b2.x += 3
    # Bullets resetten als zijkant van scherm wordt geraakt, zodat player daarna opnieuw kan schieten
    if b1.x <= 0:
        b1.x = p1.x
        b1.state = "ready"
    if b2.x >= 676:
        b2.x = p2.x
        b2.state = "ready"

    # Update het scherm
    pygame.display.update()

Stap 8: Botsing tussen kogel en player vaststellen

Python game development wordt op deze manier steeds leuker, toch? Stap voor stap leer je de Python code van deze game begrijpen en kan je na het maken van deze game ook zelf gave Python games maken.

Deze stap en de stappen die nog gaan komen, zijn niet ingewikkeld meer. Het zijn de laatste loodjes, dus we zijn er bijna! Vanaf nu gaan we alleen code toevoegen in “main.py”.

Om een botsing te vast te stellen tussen een “Player” en “Bullet” object, gaan we eerst de rechthoekige coördinaten van alle “Player” en “Bullet” objecten opslaan die we hebben aangemaakt. Deze code kan je voor de laatste regel code zetten waarin het scherm wordt geüpdatet in de while loop:

player1Rect = pygame.Rect(p1.x, p1.y, 64, 64)
player2Rect = pygame.Rect(p2.x, p2.y, 64, 64)
bullet1Rect = pygame.Rect(b1.x, b1.y, 24, 24)
bullet2Rect = pygame.Rect(b2.x, b2.y, 24, 24)

In pygame is het met het Rect objects heel makkelijk om de rechthoekige coördinaten van objecten te bepalen. Op de website van pygame lees je meer over het Rect object. Het Rect object tekent als het ware een soort onzichtbare rechthoek om een object heen.

Bij de arguments zie je dat we eerst de x- en y-coördinaat van een object meegeven en daarna de breedte en lengte. Op deze manier wordt er een mooi onzichtbare rechthoek om het object heen getekend.

In pygame is er ook een mooie method colliderect() die kan vaststellen wanneer twee rectangles (vierkanten) elkaar overlappen. Op deze manier kunnen we vaststellen wanneer een kogel met een player botst:

if bullet1Rect.colliderect(player2Rect):
    print("Botsing tussen kogel van player 1 en player 2")
    b1.x = p1.x
    b1.state = "ready"
if bullet2Rect.colliderect(player1Rect):
    print("Botsing tussen kogel van player 2 en player 1")
    b2.x = p2.x
    b2.state = "ready"

Deze code kan je onder de vorige code zetten, waarin we rechthoekige coördinaten bepalen, in de while loop. De code zal vrij duidelijk voor je zijn als je tot nu toe alles hebt gevolgd.

Als de kogel van player 1 botst met player 2, dan zie je dat door de print() function terug in de console. Ook zetten we de waarde van de x-coördinaat van de kogel weer naar de waarde van de x-coördinaat van de bijbehorende player. De status van de kogel wordt teruggezet naar “ready” en is daarom weer klaar om afgevuurd te worden.

Het andere if-statement werkt hetzelfde, maar dan checken we of de kogel van player 2 in botsing komt met player 1. De rest van de code spreekt voor zich en zal je wel begrijpen na de uitleg over het eerste if-statement.

Voer de game eens uit en test of alles werkt. Als je op een player schiet en raakt, dan zal je dat terugzien in de console. Ook wordt de kogel dan weer gereset. Zonder het resetten zou de kogel helemaal door de player heengaan en pas gereset wordt als het de zijkant van het scherm raakt.

Ook zou je dan in de console zien dat de player meerdere keren wordt geraakt door de kogel. Dit is ook niet handig voor later als we een score gaan toevoegen aan de game. Gelukkig hebben we deze problemen nu niet door het resetten van de kogel, gelijk als het een player raakt.

De game ziet er momenteel zo uit:

Players schieten en botsing checken pygame

Stap 9: Schietgeluid en achtergrondmuziek instellen

(Schiet)geluiden en achtergrondmuziek maakt een (Python) game toch iets leuker en spannender. Het is daarom leuk om te weten hoe je dat in Python game development toepast, in dit geval in pygame.

Het grootste gedeelte van deze Python game code hebben we al staan. Schietgeluid en achtergrondmuziek instellen is gelukkig niet zo lastig met pygame en kan je al doen met weinig regels code.

Het schietgeluid en de achtergrondmuziek die we gebruiken voor deze game kan je op Google Drive eenvoudig downloaden en ook gebruiken.

Eerst gaan we de mixer module importeren. Met de mixer module kan je eenvoudig geluiden laden en afspelen. Bovenaan bij de imports kan je deze code zetten, boven de imports van de classes hebben wij het gezet in “main.py”:

from pygame import mixer

Daarna gaan we de achtergrondmuziek laden en afspelen. Ook laden we alvast het schietgeluid, die we later gaan laten afspelen als een player schiet.

Tussen het laden van de afbeeldingen en het aanmaken van de objecten in “main.py” zetten we deze code neer:

mixer.music.load('sounds/background-music.ogg')
mixer.music.play(-1)
shotSound = mixer.Sound('sounds/shot-sound.ogg')

De code spreekt vrij voor zich. Opvallend is wel dat we bij de achtergrondmuziek een music module gebruiken en bij het schietgeluid een Sound object. Op de website van pygame lees je meer over music en Sound.

Het komt er kort op neer dat music meer geschikt is voor langere muziekbestanden en Sound is meer geschikt voor kortere geluidsbestanden.

Bij de play() method zie je dat we -1 als argument meegeven. Op deze manier wordt de achtergrondmuziek niet één keer afgespeeld, maar creëren we een loop waardoor de achtergrondmuziek wordt herhaald totdat de game stopt.

In “main.py” staan if-statements om te checken of er op spatie en/of “d” wordt gedrukt op het toetsenbord. Player 1 kan met spatie schieten en player 2 met “d”. Als de players op de “schietknop” drukken, dan willen we het schietgeluid laten afspelen.

Kan je al raden waar de regel code moet om het schietgeluid af te spelen? Binnenin de if-statements om te checken of er op spatie en/of “d” wordt gedrukt op het toetsenbord, staat ook een if-statement om te checken of de kogel “ready” is, weet je nog?

Binnenin de if-statements om te checken of de kogel “ready” is, zet je deze regel code bovenaan neer:

shotSound.play()

We hebben het schietgeluid namelijk al geladen en op deze manier wordt het afgespeeld als player 1 en/of player 2 op de “schietknop” drukt.

Voer de game eens uit en schiet met beide players. Het werkt, toch? Maar je zal misschien ook merken dat er een beetje vertraging zit in het afspelen van het schietgeluid. Het schietgeluid wordt niet onmiddellijk afgespeeld als een player op de “schietknop” drukt.

Gelukkig kunnen we dit oplossen. Door pygame.mixer.pre_init(44100, -16, 1, 512) aan te roepen voor pygame.init() lossen we dit probleem op. Je kan dus al raden waar de code moet komen te staan in “main.py”:

pygame.mixer.pre_init(44100, -16, 1, 512)

Op Stack Overflow hebben we de oplossing van dit probleem gevonden en daar lees je er ook iets meer over. Op de website van pygame lees je meer over de pre_init() method.

Voer nu de game nog eens uit en je zal zien dat er (vrijwel) geen vertraging meer is bij het afschieten van de kogels door de players bij een druk op de “schietknop” en het afspelen van het schietgeluid. Dit is toch wat netter.

We zijn er bijna! Nog twee stappen te gaan en dan kan je lekker met zijn tweeën jouw eigen gemaakte game gaan spelen!

Stap 10: Score optellen en weergeven

Een “2D Multiplayer Shooter” is toch niet voor te stellen zonder score? Daarom gaan we daar nu aan werken. We houden de punten bij van beide players om uiteindelijk in stap 11 de winnaar te kunnen bepalen.

Eerst gaan we onder de code waar de objecten van de kogels en players worden aangemaakt, in “main.py”, een score variable aanmaken voor player 1 en player 2. Ook maken we een font aan:

scorePlayer1 = 0
scorePlayer2 = 0
fontScore = pygame.font.Font('freesansbold.ttf', 40)

De font module in pygame maakt het heel makkelijk om een font aan te maken. “freesansbold.ttf” is al beschikbaar binnen pygame zelf en daarom gebruiken we deze font. We geven de font een lettergrootte van 40 pixels.

Als de player met zijn kogel de andere player raakt, dan krijgt de player een punt erbij. We hebben al twee if-statements gemaakt die de botsing checken tussen een kogel en een player. Hierin moeten we de score optellen.

In deze if-statements vervang je de print() functions en tel je de score op voor de bijbehorende player die een punt heeft gescoord. De if-statements zien er dan zo uit:

if bullet1Rect.colliderect(player2Rect):
    scorePlayer1 += 1
    b1.x = p1.x
    b1.state = "ready"
if bullet2Rect.colliderect(player1Rect):
    scorePlayer2 += 1
    b2.x = p2.x
    b2.state = "ready"

Als de kogel van player 1 in botsing komt met player 2, dan krijgt player 1 een punt erbij. Logischerwijs als de kogel van player 2 in botsing komt met player 1, dan krijgt player 2 een punt erbij. Niet lastig, toch?

We zijn er echter nog niet. Nu moeten we de score op renderen en op het scherm zien te krijgen. Hiervoor gaan we een nieuwe function aanmaken, onder de code waarin we zojuist twee score variables en een font hebben aangemaakt.

De function ziet er zo uit:

def display_score_players(x, y):
    score = fontScore.render(str(scorePlayer2) + " - " + str(scorePlayer1), True, (0, 0, 0))
    screen.blit(score, (x, y))

De render() method maakt het heel makkelijk binnen pygame om tekst te renderen. Op de website van pygame lees je meer over deze method.

In de render() method geven we als eerste argument de score van beide players mee. De str() function wordt gebruikt om de nummers om te zetten naar een string. Op deze manier kan de score in de " - " string worden bijgevoegd.

Het tweede argument is True. Doordat het True is, hebben de tekens in de tekst gladde randen. De kleur geven we mee in het laatste argument in RGB code. (0, 0, 0) is zwart.

De blit() method ken je vast nog wel. Met deze method zorgen we ervoor dat de score op het scherm terechtkomt, op de juiste plek die we aangeven met de x- en y-coördinaten.

Als laatste moeten we de display_score_players() function nog aanroepen in de while loop. Dit doen we voor de laatste regel code in “main.py”, waarin het scherm geüpdatet wordt:

display_score_players(300, 450)

300 geven we mee als x-coördinaat en 450 als y-coördinaat. Op deze manier komt de score onderin het scherm ongeveer in het midden te staan.

Voer de game uit en test of alles werkt. Zo niet, lees dan nog een keer terug om te kijken waar het mis is gegaan en/of zet print() functions neer om bijvoorbeeld te kijken of de score wel juist wordt opgeteld.

De game ziet er momenteel zo uit:

Werkende score pygame

Stap 11: Winnaar op scherm laten vertonen en game beëindigen

We zijn bij de laatste stap aangekomen! Deze stap is ook niet lastig meer. Onder het aanmaken in “main.py” van fontScore gaan we een font aanmaken waarin de tekst staat welke player er heeft gewonnen:

fontWinner = pygame.font.Font('freesansbold.ttf', 64)

Ook maken we weer een function waarin we de tekst over wie er gewonnen heeft renderen en met de blit() method op het scherm krijgen. Deze function kan je zetten in “main.py” onder de display_score_players() function:

def display_winner(x, y, winnerplayer):
    winner = fontScore.render(winnerplayer + " heeft gewonnen!", True, (0, 0, 0))
    screen.blit(winner, (x, y))

Als argument zie je winnerPlayer staan. Op deze manier kunnen we zo makkelijk de juiste winnaar in de tekst krijgen. Als laatste gaan we met if-statements bekijken wie er gewonnen heeft. Deze code kan je in “main.py” voor de laatste regel code zetten in de while loop, waarin het scherm wordt geüpdatet:

if scorePlayer1 == 3:
    b1.state = "stop"
    b2.state = "stop"
    display_winner(120, 200, "Player 1")
if scorePlayer2 == 3:
    b1.state = "stop"
    b2.state = "stop"
    display_winner(120, 200, "Player 2")

We checken met deze if-statements welke player als eerste 3 punten heeft gehaald. Als een player 3 punten heeft gehaald, dan geven we beide “Bullet” objecten een “stop” status, zodat beide players niet meer kunnen schieten. Op deze manier wordt de game beëindigd.

Ook wordt de juiste winnaar op het scherm vertoond, doordat bij het aanroepen van de display_winner() function het laatste argument de naam bevat van de winnaar.

De x-coördinaat van 120 en de y-coördinaat van 200 zorgen ervoor dat de tekst met de winnaar ongeveer in het midden van het scherm komt te staan.

Voer de game uit en test of alles werkt. Als er een winnaar is, ziet dat er in de game zo uit:

Einde game winnaar pygame

Dit was het! We hebben alle stappen doorlopen. Je hebt meer kennis opgedaan over Python game development. Als je een beginnende programmeur bent, zal je ook meer kennis over programmeren hebben opgedaan. Hopelijk is alles gelukt. Je kan de hele code gelukkig eenvoudig bekijken. In het volgende hoofdstuk lees je daar meer over.

Je hebt nu een mooie basis staan, maar je kan natuurlijk ook je eigen invulling geven aan de “2D Multiplayer Shooter” Python game. Laat bijvoorbeeld de players en de kogels sneller bewegen voor meer spanning. Voeg extra functionaliteiten toe. Leef je lekker uit en maak de game nog leuker en beter!

Volledige code van de Python game: 2D Multiplayer Shooter

Misschien ben je onderweg wel de draad kwijtgeraakt en wil je heel de code van deze Python game bekijken? We raden niet aan om gelijk heel de code te bekijken, beter is om eerst stap voor stap de code te implementeren met de 11 stappen die hierboven staan.

Op GitHub kan je heel de code van deze Python game bekijken en downloaden. Weet je nog niet goed wat GitHub is en wil je er meer over weten? Dan is het artikel “Wat is GitHub?” heel interessant.

Ook staat er hier op ProgrammeerPlaats een GitHub tutorial voor beginners.

Meer over Python (game development) leren

Ben je enthousiast geraakt en wil je meer leren over Python (game development)? Pluralsight is echt een heel goed platform waar je dieper in Python (game development) kan duiken. Je kan Pluralsight zelfs 10 dagen helemaal gratis uitproberen.

In “Pluralsight review en ervaringen” staat een uitgebreide review over het platform. Het is zeker de moeite waard om even een kijkje te nemen!

In het artikel “Python leren” lees je meer over Python zelf, de mogelijkheden en hoe je Python kan leren. De beste goedkope Python cursussen staan ook op ProgrammeerPlaats.

Wil je echt verder gaan met Python? Dan is het heel interessant om eenvoudig een portfolio website te maken en daarin je Python projecten te zetten. Met het artikel “Portfolio website maken (Programmeur/developer)” heb je simpel en snel een portfolio website gemaakt en kan je eventueel gaan solliciteren.

Heb je verder nog vragen en/of opmerkingen over de Python game “2D Multiplayer Shooter” die we hebben gemaakt met pygame? Laat dan hieronder een reactie achter of neem contact op.

6 gedachten over “Python game: Maak hier simpel een 2D Multiplayer Shooter met pygame!”

      • Er staat dat je aan het begin dit ziet class Bullet:

        def __init__(self):
        print(‘Als je een object aanmaakt, zie je dit op je scherm!’)
        Maar bij mij was het leeg

        Beantwoorden
        • Ah oke. Als je een object aanmaakt, zou je die tekst in de console moeten zien. Het is een test of het object correct is aangemaakt.

          Later in de code is de print() function eruit gehaald in de constructor van beide classes.

          Aangezien alles werkt bij jou, zullen de objecten ook goed zijn aangemaakt. Je zou zelf wel eventueel de print() functions terug kunnen zetten om het alsnog te checken als je dat wil.

          Gr. Bas

          Beantwoorden

Plaats een reactie