Spiele selbst programmieren und Spielecontroller mit wenig Aufwand selbst herstellen - Teil 2
Im ersten Teil hatte ich Ihnen die Idee zum Basteln eigener Spiele-Controller gezeigt und das Schildkrötenrennen vorgestellt, bei dem man so schnell wie möglich seinen Taster (button) drücken musste, um seine Schildkröte als Erste über die Ziellinie zu bringen. Heute möchte ich Ihnen ein Spiel mit analogen Eingabegeräten zeigen.
Pong ist das erste Videospiel mit weltweiter Verbreitung. Die erste Version von 1972 hatte die Firma Atari für Spielhallen entwickelt. Mit der Verbreitung der Home Computer in den 1980-er Jahren gab es auch dafür Versionen. Verschwunden ist dieses Spiel nie, neue Ideen der Implementierung gibt es vor allem durch die Verbreitung des Raspberry Pi und die neue Welle der Begeisterung für das Programmieren.
Das Spielprinzip von Pong ähnelt dem des Tischtennis: Der Ball bewegt sich auf dem Bildschirm von links nach rechts und zurück. Jeder der beiden Spieler steuert einen Schläger mit einem Drehknopf (Paddle) nach oben und unten, um den Ball zurückzuspielen. Gelingt dies nicht, erhält der Gegner einen Punkt.
Benötigte Hardware
Für die Bewegung der Schläger benötigt man zwei Spiele-Controller, bei denen kreative Lösungen gefragt sind, denn die Original-Paddles kann man nicht mehr kaufen. Die Maus könnte eine Lösung sein, wenn der zweite Spieler durch den unschlagbaren Computer ersetzt wird. In diesem Blog wollen wir selbstgebaute Paddles mit 10 kOhm-Potentiometern verwenden (siehe Bild), aber auch auf die Idee mit Ultraschall-Abstandssensoren im Blog der Raspberry Pi Foundation hinweisen.
Sie haben das Basismaterial für die Spiele-Controller sicherlich erkannt. Nachdem man die Schokolade gegessen und die Überraschung entnommen hat, kann man oben und unten Löcher in die gelben „Eier“ bohren und die Potentiometer mit jeweils drei Kabeln versehen einbauen.
Analoge Signale vom Poti in Verbindung mit Raspberry Pi bedeutet, dass wir einen Analog-Digital-Konverter (ADC) wie z.B. den MCP3004 oder MCP3008 benötigen. Das sind die Momente, wo wir Raspianer die Arduino-Fraktion um die analogen Eingänge beneiden. Zum Glück ist der MCP3004 ein einfach zu bedienender Baustein, dessen Auswertung durch das Python-Modul gpiozero unterstützt wird.
Selbstverständlich müssen wir zunächst die SPI-Schnittstelle in der Raspberry Pi-Konfiguration aktivieren. Alternativ kann man mit den notwendigen Änderungen am Programm einen ADS1115 am I2C-Bus benutzen. Dafür verwendet man das Python-Modul von Adafruit ADS1x15.
Für die animierte Grafik benutzen wir das Modul pygamezero (pgzrun), eine neue, abgespeckte Variante von dem sehr umfangreichen und sehr mächtigen Modul Pygame. Eine Einführung in die Programmierung mit pgzrun gibt es von Mark Vanstone in The MagPi magazine, u.a. im Heft 77, wo ich seine Version von pong gesehen und weiterentwickelt habe.
Ich will nicht verhehlen, dass ich nicht alles alleine hinbekommen haben. Die Darstellung des Spielstands in der grafischen Oberfläche am Ende des Spiels bereitete Probleme, und deshalb habe ich Hilfe gesucht im Forum der Raspberry Pi – Organisation. Und bekommen! Aufgrund der Moderation das beste Forum, das ich kenne, allerdings in englischer Sprache.
https://www.raspberrypi.org/forums/viewtopic.php?t=234306
Der Aufbau
Hier nun der Schaltplan mit Raspberry Pi, MCP3008 und zwei Potis. Bitte beachten, der IC benötigt sowohl eine Versorgungsspannung als auch eine Referenzspannung für den ADC. In unserem Fall beide Male 3,3V und GND. DOUT wird mit MISO (pin21), DIN mit MOSI (pin19), CLK mit CLK (pin23) und CS mit CE0 (pin24) verbunden. Die Zählung der analogen Eingänge beginnt bei 0.
In unserem Programmbeispiel verwende ich den MCP3004, die Variante mit vier analogen Eingängen. Der passt zusammen mit einem Raspi-Breadboard-Cobbler auf ein halbes Breadboard (siehe Bild oben).
Ich beschränke mich bei den Erklärungen auf den von mir hinzugefügten Teil. Für die Erklärungen zu pygamezero verweise ich auf den o.g. Link zu The MagPi 77.
Die Software
Mit from gpiozero import MCP3004 importieren wir die Klasse gpiozero.MCP3004 und instanziieren dann damit zwei Objekte pot1=MCP3004(0) und pot2=MCP3004(1). Wie oben bereits erwähnt, beginnt die Zählung der analogen Eingänge bei 0.
Zusätzlich zur BALLSPEED, die wir anfänglich kleiner setzen als im Originalcode und dann alle 60 Sekunden steigern, definieren wir ein Ende, wenn der erste Spieler 10 Punkte erzielt hat, und einen automatischen RESTART.
Hier nun der Programm-Code im Ganzen:
# pong game based on pygamezero # Pong was the first video game with world-wide distribution, published in 1972Pong # source The MagPi magazine issue 77, pages 32 - 35 # modified by Raspberry Pi course at JFS Bad Bramstedt, Germany # with the decisive contribution by MarkyV at raspberrypi.org/forums # also thanks to Lucy Hattersley, editor of The MagPi magazine # BALLSPEED starts rather slow, but increases every 60 seconds # Game over, when one player scores 10 # Restart: Press Space Bar import pgzrun import random from gpiozero import MCP3004 import math from time import time, sleep # new pot1 = MCP3004(0) pot2 = MCP3004(1) # Set up the colours BLACK = (0 ,0 ,0 ) WHITE = (255,255,255) p1Score = p2Score = 0 END = 10 # new: end of game, when first player has scored END RESTART = 15 # restart after xx seconds BALLSPEED = 3 # changed from 5 to 3 p1Y = 300 p2Y = 300 TIME = time() def draw(): global screen, p1Score, p2Score screen.fill(BLACK) screen.draw.line((400,0),(400,600),"green") drawPaddles() drawBall() screen.draw.text(str(p1Score) , center=(105, 40), color=WHITE, fontsize=60) screen.draw.text(str(p2Score) , center=(705, 40), color=WHITE, fontsize=60) if p2Score==END: screen.draw.text("Player2 wins!" , center=(400, 30), color=WHITE, fontsize=60) if p1Score==END: screen.draw.text("Player1 wins!", center=(400, 30), color=WHITE, fontsize=60) def on_key_down(key): global p1Score, p2Score, BALLSPEED if (p2Score == END or p1Score == END) and key.name == "SPACE": p1Score = p2Score = 0 BALLSPEED = 3 print("Restart") def update(): updatePaddles() if p2Score < END and p1Score < END: updateBall() def init(): global ballX, ballY, ballDirX, ballDirY ballX = 400 ballY = 300 a = random.randint(10, 350) while (a > 80 and a < 100) or (a > 260 and a < 280): a = random.randint(10, 350) ballDirX = math.cos(math.radians(a)) ballDirY = math.sin(math.radians(a)) def drawPaddles(): global p1Y, p2Y p1rect = Rect((100, p1Y-30), (10, 60)) p2rect = Rect((700, p2Y-30), (10, 60)) screen.draw.filled_rect(p1rect, "red") screen.draw.filled_rect(p2rect, "red") def updatePaddles(): global p1Y, p2Y p1Y = (pot1.value * 540) +30 p2Y = (pot2.value * 540) +30 if keyboard.up: if p2Y > 30: p2Y -= 2 if keyboard.down: if p2Y < 570: p2Y += 2 if keyboard.w: if p1Y > 30: p1Y -= 2 if keyboard.s: if p1Y < 570: p1Y += 2 def updateBall(): global ballX, ballY, ballDirX, ballDirY, p1Score, p2Score, BALLSPEED, TIME ballX += ballDirX*BALLSPEED ballY += ballDirY*BALLSPEED ballRect = Rect((ballX-4,ballY-4),(8,8)) p1rect = Rect((100, p1Y-30), (10, 60)) p2rect = Rect((700, p2Y-30), (10, 60)) if time() - TIME > 60: #new BALLSPEED += 1 #new TIME = time() #new print("BALLSPEED is now: ", BALLSPEED) if checkCollide(ballRect, p1rect) or checkCollide(ballRect, p2rect): ballDirX *= -1 if ballY < 4 or ballY > 596: ballDirY *= -1 if ballX < 0: p2Score += 1 init() if ballX > 800: p1Score += 1 init() def checkCollide(r1,r2): return ( r1.x < r2.x + r2.w and r1.y < r2.y + r2.h and r1.x + r1.w > r2.x and r1.y + r1.h > r2.y ) def drawBall(): screen.draw.filled_circle((ballX, ballY), 8, "white") pass init() pgzrun.go()
Viel Spaß beim Basteln und Spielen.
1 Kommentar
Bernd-Steffen Großmann
Hallo Herr Albrecht, zunächst einmal herzlichen Dank für den interessanten und unterhaltsamen Beitrag, der die Themen Mikrocontroller, Programmieren und Spielen bedient und sicher bei vielen interessierten Makern das Interesse an dem Raspi, dessen Programmierung und dem Selbstbau der nötigen Hardware weckt bzw. vertieft. Der Blog hat bestimmt einige Reichweite in der DIY-Welt. Mir ist auch bewusst, dass die Darstellung der Schaltung in dem Zusammenhang als „Freiflug“-Verdrahtung mit Steckbrett und Steckkabeln in Fritzing-Grafik üblich geworden ist. Ich finde es aber immer gut, wenn die Beiträge auch mit einem ordentlichen elektrischen Schaltbild versehen werden, wie jeder Elektroniker und Nachrichtentechnik-Student (mancher sogar in der Schule in Physik) gelernt hat. Das als kleine Kritik. Die größere muss ich an die Konsistenz des Beitrags anbringen. In der Schaltung und deren Beschreibung verwenden Sie den MCP3008, aber im Programm und dessen Erläuterung den MCP3004! Jemand, der sich mit Schaltungstechnik und Programmierung schon auskennt, wird es nicht schwer fallen, die Komponente gegen die jeweils andere zu ersetzen, aber diejenigen, die einfach nachbauen wollen, für die wird es eine Herausforderung, der sie wohl nicht immer gewachsen sind. Also: warum haben Sie sich nicht an ein und denselben AD-Wandler in Schaltung und Programmierung gehalten?